0

I'm building a SwiftUI to-do app. You tap an Add button that pulls up a partial-height sheet where you can enter and save a new to-do. The Add sheet's input (TextField) should be focused when the sheet appears, so in order to keep things feeling fast and smooth, I'd like the sheet and the keyboard to animate onscreen together, at the same time. After much experimentation and Googling, I still can't figure out how to do it.

It seems like there are two paths to doing something like this:

(1) Autofocus the sheet I can use @FocusState and .onAppear or .task inside the sheet to ensure the TextField is focused as soon as it comes up. It's straightforward functionally, but I can't find a permutation of it that will give me that single animation: it's sheet, then keyboard, presumably because those modifiers don't fire until the sheet is onscreen.

(2) Keyboard accessory view / toolbar The .toolbar modifier seems tailor-made for a view of custom height that sticks to the keyboard--you lose the nice sheet animation but you gain the ability to have the view auto-size. However, .toolbar is designed to present controls alongside a TextField that itself isn't stuck to the keyboard. That is, the field has to be onscreen before the keyboard so it can receive focus...I don't know of a way to put the input itself inside the toolbar. Seems like chat apps have found a way to do this but I don't know what it is.

Any help would be much appreciated! Thanks!

Dave Feldman
  • 104
  • 1
  • 9
  • 28
  • You should look into UIKit, SwiftUI is a once she fits most framework. – lorem ipsum Feb 13 '23 at 14:34
  • 1
    I've actually done something like this in UIKit before, and agree it can work—but the solution used there felt a bit hacky—messing around with UIWindows to create the effect. Worst case, I imagine I can do something simpler and equally hacky in SwiftUI (though I haven't tested it yet) with a ZStack or a Window or something? – Dave Feldman Feb 17 '23 at 15:17
  • If it was hacky in SwiftUI it would only be worse with SwiftUI. – lorem ipsum Feb 17 '23 at 15:30

1 Answers1

2

Regarding option (1), I think there is no way to sync the animation. I decided to do it this way and don't worry about the delay between sheet and keyboard animation. Regarding option (2), you could try something like this:

struct ContentView: View {
    @State var text = ""
    @FocusState var isFocused: Bool
    @FocusState var isFocusedInToolbar: Bool

    var body: some View {
        Button("Show Keyboard") {
            isFocused = true
        }
        .opacity(isFocusedInToolbar ? 0 : 1)

        TextField("Enter Text", text: $text)            // Invisible Proxy TextField
            .focused($isFocused)
            .opacity(0)
            .toolbar {
                ToolbarItem(placement: .keyboard) {
                    HStack {
                        TextField("", text: $text)          // Toolbar TextField
                            .textFieldStyle(.roundedBorder)
                            .focused($isFocusedInToolbar)
                        Button("Done") {
                                isFocused = false
                                isFocusedInToolbar = false
                                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                        }
                    }
                }
            }
            .onChange(of: isFocused) { newValue in
                if newValue {
                    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.05) {
                        isFocusedInToolbar = true
                    }
                }
            }
    }
}

The trick is, that you need a TextField in your content that triggers the keyboard initally and then switch focus to the TextField in the toolbar. Otherwise you won't get the keyboard to show up.

viedev
  • 869
  • 1
  • 6
  • 16
  • It's a start! But does have a couple issues: (1) It requires there be a TextField onscreen when the "sheet" isn't visible. So for example, I don't see how to adapt this to a scenario (which is what I'm ultimately doing) where you launch the sheet from a button. (2) It doesn't always work as intended. As far as I can tell, it works every _other_ time—that is, if you dismiss it and then re-focus the input, the focus fails to shift to the toolbar (though it does appear). If you do it again, it shifts as expected. – Dave Feldman Feb 17 '23 at 16:00
  • Triggering the keyboard through a button is easy, see revised code above (you still need an invisible proxy `TextField`). It seems the inconsistent behavior can be fixed by adding a minimal delay before changing focus. I mean it's a somewhat dirty solution, but it seems to do the trick as far as I can tell. – viedev Feb 17 '23 at 16:37
  • Nice! I agree the delay is hacky—and there's something interesting about the fact that without it it's consistently focused _every other time_ you pull up the keyboard. Maybe there's a less hacky hack at least? – Dave Feldman Feb 18 '23 at 19:55