Stick button at bottom when keyboard appears - ios

I'm making a screen when I ask the user his name and a Button to the bottom of the page.
My problem is, when I'm focusing the Textfield, the keyboard appears but push up the button. How can I stick my button to the bottom of my view and be hidden by the keyboard ?
Before the keyboard appears:
When the keyboard showed up:
Thanks a lot !

You can use .ignoresSafeArea(.keyboard) modifier:
struct ContentView: View {
#State var text = "Test"
var body: some View{
VStack {
Text("Hello, world")
Spacer()
TextField("", text: $text)
Spacer()
Button("Submit") {}
}.ignoresSafeArea(.keyboard)
}
}
This has to be applied to the surrounding parent stack -- applying it to the Button element alone in the above example has no effect.

Related

Present sheet with a TextField and its keyboard in a single animation?

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!
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.

SwiftUI: strange offset on tap when reopening app with a `sheet` open

I am facing out a strange behavior that really looks like a SwiftUI bug.
When I leave the app with a .sheet open and reopen it, all content from parent has an offset on tap. It is difficult to explain (and English is not my mother tongue) so here is a really simple example:
struct ContentView: View {
#State private var isOpen = false
var body: some View {
Button(action: {
isOpen.toggle()
}, label: {
Text("Open sheet")
.foregroundColor(.white)
.padding()
.background(.blue)
})
.sheet(isPresented: $isOpen, content: {
Text("Sheet content")
})
}
}
To reproduce the issue follow those steps:
Tap just below to the top border of blue button Open sheet: the sheet opens as expected.
When the sheet is open, close the app (go back to Springboard, cmd+shift+H on iOS Simulator).
Reopen the app. You're still on the sheet view.
Close the sheet. You're back on main view with blue button. Here is the bug:
Tap again on the top of blue button, right below the top border. Nothing happens. You have to click few pixels below. There is an offset that makes all tappable items on main view not aligned.
Does anyone have seen this bug also? Is there something I do wrong?
Other notices:
When closing the app from main view, the bug doesn't appear. And even when the bug is here and I close the app from main view and reopen, the bug disappears.
If I use a .fullScreenCover instead of .sheet, the bug doesn't appear.
It really looks like a bug with .sheets open.
EDIT:
I have tried two workarounds but both don't work:
Embed the Button in an external View.
Replace Button with only the Text and add .onTapGesture{ ... } modifier to toggle isOpen #State property.
EDIT 2:
After hours of tries I could find something interesting: if, in the sheet content, I add a button to dismiss the sheet, the bug doesn't appear anymore. But if I dismiss the sheet with finger (drag from top to bottom), it still appears.
Here is modified code:
struct ContentView: View {
#State private var isOpen = false
var body: some View {
Button(action: {
isOpen.toggle()
}, label: {
Text("Open sheet")
.foregroundColor(.white)
.padding()
.background(.blue)
})
.sheet(isPresented: $isOpen, content: {
SheetContent()
})
}
}
struct SheetContent: View {
#Environment(\.dismiss) var dismiss
var body: some View {
Button(action: { dismiss() }, label: {
Text("Dismiss sheet")
})
}
}
It looks like there is something with calling (or not) the #Environment(\.dismiss) var dismiss.
The current state is a bit better as few days ago as the bug only appears when user dismiss the sheet by dragging down. But there is still something wrong.
Is there a way to programmatically call dismiss() when sheet is closed by dragging down?

FocusState changes in SwiftUI cause the keyboard to bounce

I'm making a sign-in interface for iOS in SwiftUI. The user should be able to easily switch from the username text field to the password text field by tapping the "next" button on the software keyboard. It's working well but the keyboard always bounces a little when switching between the two text fields for some reason. Edit: As suggested in this answer I've added a Spacer into the VStack to make it fill the available space. The text fields aren't bouncing anymore but the keyboard unfortunately still is. I've updated the code and the GIF to reflect my changes.
After googling a little it seemed like this wasn't a very common issue. This question seemed to be similar to what happens to me but following the answer and wrapping the text fields in a ScrollView or a GeometryReader did not change anything at all. This is my code:
struct AuthenticationView: View {
#State var userName: String = ""
#State var userAuth: String = ""
#FocusState var currentFocus: FocusObject?
enum FocusObject: Hashable { case name, auth }
var body: some View {
VStack(spacing: 8) {
TextField("Username", text: $userName)
.focused($currentFocus, equals: .name)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 8)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = .auth }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.username)
.submitLabel(.next)
SecureField("Password", text: $userAuth)
.focused($currentFocus, equals: .auth)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 16)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = nil }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.password)
.submitLabel(.done)
Spacer() // This fixes the text fields
// But it does not fix the keyboard
}.padding(32)
}
}
Your current layout says:
Put the edit fields into a VStack.
Layout the VStack in the parent view by centering it in the available space. Note, that the VStack only uses a minimum size.
Now, when the keyboard appears, the available space of the parent view, i.e. its height, will be reduced accordingly.
Because the VStack is layout in the center, the text fields bounce up and down.
There are a couple of options:
Ensure the VStack extends its height and the text fields are aligned at the top. For example using a Spacer:
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
Spacer()
}.padding(32)
Using a ScrollView:
ScrollView {
Spacer(minLength: 80) // give some space at the top
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
}.padding(32)
}
It may not look pretty, but it should give you an idea, where to work on this issue (you may want to use a GeometryReader and a possibly a ScrollView to perfect your layout).
Another option is to use a Form. Put your fields into there, and with a Form you get also a head start which looks pretty nice. The reason why a Form works is because the same reasons why it works with a Spacer (aligns fields on top) and because of a ScrollView.
The fact that the keyboard disappears temporarily when you tap "Next" is unfortunate. I have no solution for this, so far.

SwiftUI TextEditor View content is hidden behind Navigation Bar

I'm working on a SwiftUI View with a NavigationBar. The view is very simple, it's a full-page TextEditor:
struct NotesEditingScreen: View {
#State var text: String
var body: some View {
TextEditor(text: $text)
.padding(.horizontal)
.navigationBarTitle("Editing")
}
}
The issue I'm seeing, is that when landing on this screen (via a NavigationLink) the top of the TextEditor is covered up by Navigation Bar:
My desired behavior is that the TextEditor content appears beneath the Navigation Bar, like it appears after you manually scroll to the top to reveal the text.
Is there a solution/workaround to this issue? I was hoping for either some offset, a setting on NavigationBar, or some programmatic scroll behavior that could be done onAppear. Any suggestions welcome.
I think it's an unexpected behavior.
You can try this:
GeometryReader { geo in
ScrollView {
TextEditor(text: $text)
.frame(height: geo.size.height)
}
}

Add View in navigationBarTitle - SwiftUI

I am able to add Title using .navigationBarTitle(Text((msgDetails.name))) But i wanted to add subtitle under the title in the navigationbar. Looks like title will not accept the View and it accepts only Text. I tried \n in the title but it is not working. IS there any way i can add the subtitle in navigation bar. I used leading and trailing to add left and right button in the navigation bar. I wanted to show title and subtitle along with this left and right button
Navigation Bar
If you look in SwiftUI documentation you'll see only a few overloads of navigationBarTitle function. All of them requires special parameters, like Text or StringProtocol. So you can't just put some View into navigation bar.
I can propose one strange, but working version. It's about using .navigationBarItems(leading:... - it requires some view, which you can customize (within reason). Here is simple example:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Main view")
.navigationBarItems(leading:
HStack {
Button(action: {}) {
Image(systemName: "return")
}
VStack {
Text("Title")
.bold()
.font(.system(size: 30))
Text("Subtitle")
.italic()
.font(.system(size: 15))
}
.padding(.horizontal, 100) // mb it's better to use GeometryReader for centering
})
}
}
}
and you'll achieve something like this:

Resources