Move TextField up when the keyboard has appeared in SwiftUI

move textfield up when keyboard appears swiftui
swift move textfield up with keyboard
move uitableview up when keyboard appears
swiftui multiline text field
xcode keyboard covers textfield
swifui textfield
swift keyboard avoidance
hackingwithswift keyboard

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:

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)

struct ContentView: View {
    @ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 1)
    @State private var name = Array<String>.init(repeating: "", count: 3)

    var body: some View {

        VStack {
            Group {
                Text("Some filler text").font(.largeTitle)
                Text("Some filler text").font(.largeTitle)
            }

            TextField("enter text #1", text: $name[0])
                .textFieldStyle(RoundedBorderTextFieldStyle())

            TextField("enter text #2", text: $name[1])
                .textFieldStyle(RoundedBorderTextFieldStyle())

            TextField("enter text #3", text: $name[2])
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .background(GeometryGetter(rect: $kGuardian.rects[0]))

        }.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
    }

}
Second Example (show only the active field)

struct ContentView: View {
    @ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 3)
    @State private var name = Array<String>.init(repeating: "", count: 3)

    var body: some View {

        VStack {
            Group {
                Text("Some filler text").font(.largeTitle)
                Text("Some filler text").font(.largeTitle)
            }

            TextField("text #1", text: $name[0], onEditingChanged: { if $0 { self.kGuardian.showField = 0 } })
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .background(GeometryGetter(rect: $kGuardian.rects[0]))

            TextField("text #2", text: $name[1], onEditingChanged: { if $0 { self.kGuardian.showField = 1 } })
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .background(GeometryGetter(rect: $kGuardian.rects[1]))

            TextField("text #3", text: $name[2], onEditingChanged: { if $0 { self.kGuardian.showField = 2 } })
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .background(GeometryGetter(rect: $kGuardian.rects[2]))

            }.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
    }.onAppear { self.kGuardian.addObserver() } 
.onDisappear { self.kGuardian.removeObserver() }

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

struct GeometryGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { geometry in
            Group { () -> AnyView in
                DispatchQueue.main.async {
                    self.rect = geometry.frame(in: .global)
                }

                return AnyView(Color.clear)
            }
        }
    }
}

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

import SwiftUI
import Combine

final class KeyboardGuardian: ObservableObject {
    public var rects: Array<CGRect>
    public var keyboardRect: CGRect = CGRect()

    // keyboardWillShow notification may be posted repeatedly,
    // this flag makes sure we only act once per keyboard appearance
    public var keyboardIsHidden = true

    @Published var slide: CGFloat = 0

    var showField: Int = 0 {
        didSet {
            updateSlide()
        }
    }

    init(textFieldCount: Int) {
        self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)

    }

    func addObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
}

func removeObserver() {
 NotificationCenter.default.removeObserver(self)
}

    deinit {
        NotificationCenter.default.removeObserver(self)
    }



    @objc func keyBoardWillShow(notification: Notification) {
        if keyboardIsHidden {
            keyboardIsHidden = false
            if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
                keyboardRect = rect
                updateSlide()
            }
        }
    }

    @objc func keyBoardDidHide(notification: Notification) {
        keyboardIsHidden = true
        updateSlide()
    }

    func updateSlide() {
        if keyboardIsHidden {
            slide = 0
        } else {
            let tfRect = self.rects[self.showField]
            let diff = keyboardRect.minY - tfRect.maxY

            if diff > 0 {
                slide += diff
            } else {
                slide += min(diff, 0)
            }

        }
    }
}

Move TextField up when the keyboard has appeared in SwiftUI , 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. Here is a snippet that observes NotificationCenter notifications related the keyboard, and changes the height of a spacer view based on the computed keyboard height.

To build off of @rraphael 's solution, I converted it to be usable by today's xcode11 swiftUI support.

import SwiftUI

final class KeyboardResponder: ObservableObject {
    private var notificationCenter: NotificationCenter
    @Published private(set) var currentHeight: CGFloat = 0

    init(center: NotificationCenter = .default) {
        notificationCenter = center
        notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    deinit {
        notificationCenter.removeObserver(self)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height
        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        currentHeight = 0
    }
}

Usage:

struct ContentView: View {
    @ObservedObject private var keyboard = KeyboardResponder()
    @State private var textFieldInput: String = ""

    var body: some View {
        VStack {
            HStack {
                TextField("uMessage", text: $textFieldInput)
            }
        }.padding()
        .padding(.bottom, keyboard.currentHeight)
        .edgesIgnoringSafeArea(.bottom)
        .animation(.easeOut(duration: 0.16))
    }
}

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.

Moving TextField Up When Keyboard Appears Using SwiftUI , In this video i'm going to show how to Move TextField Up When Keyboard is Appears Using Duration: 3:46 Posted: Dec 9, 2019 The resolution for the problem with the keyboard padding is like E.coms suggested. Also the class written here by kontiki can be used: How to make the bottom button follow the keyboard display in SwiftUI

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.

struct KeyboardHost<Content: View>: View {
    let view: Content

    @State private var keyboardHeight: CGFloat = 0

    private let showPublisher = NotificationCenter.Publisher.init(
        center: .default,
        name: UIResponder.keyboardWillShowNotification
    ).map { (notification) -> CGFloat in
        if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
            return rect.size.height
        } else {
            return 0
        }
    }

    private let hidePublisher = NotificationCenter.Publisher.init(
        center: .default,
        name: UIResponder.keyboardWillHideNotification
    ).map {_ -> CGFloat in 0}

    // Like HStack or VStack, the only parameter is the view that this view should layout.
    // (It takes one view rather than the multiple views that Stacks can take)
    init(@ViewBuilder content: () -> Content) {
        view = content()
    }

    var body: some View {
        VStack {
            view
            Rectangle()
                .frame(height: keyboardHeight)
                .animation(.default)
                .foregroundColor(.clear)
        }.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in
            self.keyboardHeight = height
        }
    }
}

You can then use the view like so:

var body: some View {
    KeyboardHost {
        viewIncludingKeyboard()
    }
}

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.

SwiftUI, In this video, we learn, how to Move TextField up when the keyboard has appeared by using Duration: 10:56 Posted: Apr 16, 2020 Add padding to the bottom of the view, which will make it move up and down with the keyboard. Update the view state with the latest value emitted by the keyboardHeight publisher. Now, when focusing and defocusing the text field, the view will move up and down: Extracting Keyboard Avoidance Behavior into SwiftUI ViewModifier

I tried many of the proposed solutions, and even though they work in most cases, I had some issues - mainly with safe area (I have a Form inside TabView's tab).

I ended up combining few different solutions, and using GeometryReader in order to get specific view's safe area bottom inset and use it in padding's calculation:

import SwiftUI
import Combine

struct AdaptsToKeyboard: ViewModifier {
    @State var currentHeight: CGFloat = 0

    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .padding(.bottom, self.currentHeight)
                .animation(.easeOut(duration: 0.16))
                .onAppear(perform: {
                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification)
                        .merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification))
                        .compactMap { notification in
                            notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect
                    }
                    .map { rect in
                        rect.height - geometry.safeAreaInsets.bottom
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))

                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification)
                        .compactMap { notification in
                            CGFloat.zero
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
                })
        }
    }
}

Usage:

struct MyView: View {
    var body: some View {
        Form {...}
        .modifier(AdaptsToKeyboard())
    }
}

TextField in SwiftUI and Keyboard …, Seems to be a fairly simple solution to implement. https://stackoverflow.com/​questions/56491881/move-textfield-up-when-thekeyboard-has-appeared-by-​using  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.

I have created a really simple to use view modifier.

Add a Swift file with the code below and simply add this modifier to your views:

.keyboardResponsive()
import SwiftUI

struct KeyboardResponsiveModifier: ViewModifier {
  @State private var offset: CGFloat = 0

  func body(content: Content) -> some View {
    content
      .padding(.bottom, offset)
      .onAppear {
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notif in
          let value = notif.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
          let height = value.height
          let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom
          self.offset = height - (bottomInset ?? 0)
        }

        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { notif in
          self.offset = 0
        }
    }
  }
}

extension View {
  func keyboardResponsive() -> ModifiedContent<Self, KeyboardResponsiveModifier> {
    return modifier(KeyboardResponsiveModifier())
  }
}

Handling the Keyboard Animations in SwiftUI with ObservedObject, Imagine we have a chat app in which a TextField is on the lower half of the view. So, the question is how would we adjust the current layout, by moving the text field upwards in order (depending on the height of the screen type) if the keyboard appears. Fantastic Web page, Keep up the very good job. For example, if the list is scrolled and the field is too high, when the keyboard appears, the textfield moves up and away off the screen. – kontiki Jun 22 '19 at 20:03 What I do like about your answer, is having the keyboard observer on a separate class, instead of on the view struct itself.

Move Textfield When Keyboard Shows up on Screen : SwiftUI, I had 0 knowledge of programming at that time. First i have used the playground app from Apple. Then i register for SwiftUI training courses on Udemy. I found  How to hide keyboard using SwiftUI for below cases? Case 1. I have TextField and I need to hide the keyboard when the user clicks the return button. Case 2. I have TextField and I need to hide the keyboard when the user taps outside. How I can do this using SwiftUI? Note: I have not asked a question regarding UITextField.

How to move a UITextField when the keyboard appears in SwiftUI?, The Process is simple, similar to UIKit, we need to listen to Keyboard Events and Sign up and get an extra one for free. These Notifications have associated dictionary with the following keys: completly sync keyboard animation with Scrollview move but In SwiftUI I cannot find any way to use the same. Handling the Keyboard Animations in SwiftUI with ObservedObject Published by iOS App Templates on March 23, 2020 While working on the SwiftUI Chat tutorial, we ran into the issue of handling the keyboard animations by adjusting the layout of all the views visible on the screen, depending on whether the keyboard is visible or not.

Move view when keyboard is shown (guide), Most likely you have worked on an app with multiple textfields, and One solution for this is to move the view up when a keyboard is presented. if the text field will be covered by the keyboard when the keyboard appear, if it  I have setup a UI in which there are 6 fields in the ScrollView as the following: ScrollView { IndicatorTextField( index: 0, h

Comments
  • You may use ScrollView. developer.apple.com/documentation/swiftui/scrollview
  • @PrashantTukadiya Thanks for the quick response. I have added TextField inside Scrollview but still facing the same issue.
  • @DimaPaliychuk This won't work. it is SwiftUI
  • @DimaPaliychuk. IQKeyboardManager is not worked with SwiftUI. It only works with UIKit based component. By the way thanks for replay :D
  • The showing of the keyboard and it obscuring content on the screen has been around since what, the first Objective C iPhone app? This is problem that is constantly being solved. I for one am disappointed that Apple has not addressed this with SwiftUi. I know this comment is not helpful to anyone, but I wanted to raise this issue that we really should be putting pressure on Apple to provide a solution and not rely on the community to always supply this most common of problems.
  • Is it possible to attach GeometryGetter as a view modifier than a background by making it conform to ViewModifier protocol?
  • It is possible, but what's the gain? You would be attaching it like this: .modifier(GeometryGetter(rect: $kGuardian.rects[1])) instead of .background(GeometryGetter(rect: $kGuardian.rects[1])). Not much of a difference (only 2 characters less).
  • For me the animation didn't work until I wrapped my Content in a ScrollView. Just putting it our there if anyone has similar issues.. also for Beta 4 you need to: - update didChange -> willChange - change basic -> easeInOut or a other animation
  • In some situations you could get a SIGNAL ABORT from the program inside the GeometryGetter when assigning the new rectangle if you are navigating away from this screen. If that happens to you just add some code to verify that the size of the geometry is greater than zero (geometry.size.width > 0 && geometry.size.height > 0) before assigning a value to self.rect
  • @JulioBailon I don't know why but moving geometry.frame out of DispatchQueue.main.async helped with SIGNAL ABORT, now will test your solution. Update: if geometry.size.width > 0 && geometry.size.height > 0 before assigning self.rect helped.
  • I like this answer for its simplicity. I added .animation(.easeOut(duration: 0.16)) to try to match the speed of the keyboard sliding up.
  • Why have you set a max height of 340 for the keyboard?