I have seven TextField
inside my main ContentView
. When user open keyboard some of the TextField
are hidden under the keyboard frame. So I want to move all TextField
up respectively when the keyboard has appeared.
I have used the below code to add TextField
on the screen.
struct ContentView : View {
@State var textfieldText: String = ""
var body: some View {
VStack {
TextField($textfieldText, placeholder: Text("TextField1"))
TextField($textfieldText, placeholder: Text("TextField2"))
TextField($textfieldText, placeholder: Text("TextField3"))
TextField($textfieldText, placeholder: Text("TextField4"))
TextField($textfieldText, placeholder: Text("TextField5"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField7"))
}
}
}
Output:
Answer copied from here: TextField always on keyboard top with SwiftUI
I've tried different approaches, and none of them worked for me. This one below is the only one that worked for different devices.
Add this extension in a file:
}
In your view, you need a variable to bind offsetValue:
I created a View that can wrap any other view to shrink it when the keyboard appears.
It's pretty simple. We create publishers for keyboard show/hide events and then subscribe to them using
onReceive
. We use the result of that to create a keyboard-sized rectangle behind the keyboard.You can then use the view like so:
To move the content of the view up rather than shrinking it, padding or offset can be added to
view
rather than putting it in a VStack with a rectangle.You need to add a
ScrollView
and set a bottom padding of the size of the keyboard so the content will be able to scroll when the keyboard appears.To get the keyboard size, you will need to use the
NotificationCenter
to register for keyboards event. You can use a custom class to do so:The
BindableObject
conformance will allow you to use this class as aState
and trigger the view update. If needed look at the tutorial forBindableObject
: SwiftUI tutorialWhen you get that, you need to configure a
ScrollView
to reduce its size when the keyboard appear. For convenience I wrapped thisScrollView
into some kind of component:All you have to do now is to embed your content inside the custom
ScrollView
.Edit: The scroll behaviour is really weird when the keyboard is hiding. Maybe using an animation to update the padding would fix this, or you should consider using something else than the
padding
to adjust the scroll view size.This is adapted from what @kontiki built. I have it running in an app under beta 8 / GM seed, where the field needing scrolled is part of a form inside a NavigationView. Here's KeyboardGuardian:
Then, I used an enum to track the slots in the rects array and the total number:
KeyboardSlots.count.rawValue
is the necessary array capacity; the others as rawValue give the appropriate index you'll use for .background(GeometryGetter) calls.With that set up, views get at the KeyboardGuardian with this:
The actual movement is like this:
attached to the view. In my case, it's attached to the entire NavigationView, so the complete assembly slides up as the keyboard appears.
I haven't solved the problem of getting a Done toolbar or a return key on a decimal keyboard with SwiftUI, so instead I'm using this to hide it on a tap elsewhere:
You attach it to a view as
Some views (e.g., pickers) don't like having that attached, so you may need to be somewhat granular in how you attach the modifier rather than just slapping it on the outermost view.
Many thanks to @kontiki for the hard work. You'll still need his GeometryGetter above (nope, I didn't do the work to convert it to use preferences either) as he illustrates in his examples.
Code updated for the Xcode, beta 7.
You do not need padding, ScrollViews or Lists to achieve this. Although this solution will play nice with them too. I am including two examples here.
The first one moves all textField up, if the keyboard appears for any of them. But only if needed. If the keyboard doesn't hide the textfields, they will not move.
In the second example, the view only moves enough just to avoid hiding the active textfield.
Both examples use the same common code found at the end: GeometryGetter and KeyboardGuardian
First Example (show all textfields)
Second Example (show only the active field)
GeometryGetter
This is a view that absorbs the size and position of its parent view. In order to achieve that, it is called inside the .background modifier. This is a very powerful modifier, not just a way to decorate the background of a view. When passing a view to .background(MyView()), MyView is getting the modified view as the parent. Using GeometryReader is what makes it possible for the view to know the geometry of the parent.
For example:
Text("hello").background(GeometryGetter(rect: $bounds))
will fill variable bounds, with the size and position of the Text view, and using the global coordinate space.Update I added the DispatchQueue.main.async, to avoid the possibility of modifying the state of the view while it is being rendered.***
KeyboardGuardian
The purpose of KeyboardGuardian, is to keep track of keyboard show/hide events and calculate how much space the view needs to be shifted.
Update: I modified KeyboardGuardian to refresh the slide, when the user tabs from one field to another
To build off of @rraphael 's solution, I converted it to be usable by today's xcode11 swiftUI support.
Usage:
The published
currentHeight
will trigger a UI re-render and move your TextField up when the keyboard shows, and back down when dismissed. However I didn't use a ScrollView.