8

In a UIViewController I have several text fields and a button is on the bottom of the UIViewController. For the button, I have set a bottom constraint with a constant of 0. Then I made an outlet from the bottom constraint to the UIViewController.

When I run my code, the button does not move upwards. I have seen suggestions on stackoverflow that I should add UIScrollView, but that means, I would have to delete all the objects on the UIViewController, put the UIScrollView and then put my objects on the UIVIewController again.

@IBOutlet weak var bottomConstraint: NSLayoutConstraint!

// When tapping outside of the keyboard, close the keyboard down
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.view.endEditing(true)
}

// Stop Editing on Return Key Tap. textField parameter refers to any textfield within the view
func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

// When keyboard is about to show assign the height of the keyboard to bottomConstraint.constant of our button so that it will move up
func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        if let keyboardSize: CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            bottomConstraint.constant = keyboardSize.size.height
            view.setNeedsLayout()
        }
    }
}

// When keyboard is hidden, move the button to the bottom of the view
func keyboardWillHide(notification: NSNotification) {
    bottomConstraint.constant = 0.0
    view.setNeedsLayout()
}

enter image description here

fhe
  • 6,099
  • 1
  • 41
  • 44
bibscy
  • 2,598
  • 4
  • 34
  • 82

5 Answers5

9

The typical way to address this would be to move the keyboard with code like this:

in ViewController class:

  func keyboardWillShow(notification: NSNotification) {

        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            if view.frame.origin.y == 0{
                let height = keyboardSize.height

                self.view.frame.origin.y += height
            }

        }

    }

    func keyboardWillHide(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            if view.frame.origin.y != 0 {
                let height = keyboardSize.height
                self.view.frame.origin.y -= height
            }

        }
    }

in ViewDidLoad method:

  NotificationCenter.default.addObserver(self, selector: Selector("keyboardWillShow:"), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
  NotificationCenter.default.addObserver(self, selector: Selector("keyboardWillHide:"), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

Please Read This: The way you are trying to solve your problem is not allowed. In the code above, if you change view to your button variable name, the button will shoot up and then fall back down. This is because Auto Layout and Programmatic layout do not work together, it is one or the other. The way you fix this is by programmatically creating that button (with CGRect), then using the code above to move only that button on keyboard press. (Do that by changing view to your button variable name.

   func keyboardWillShow(notification: NSNotification) {

    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        if view.frame.origin.y == 0{
            let height = keyboardSize.height
            self.yourBtn.frame.origin.y += height
        }
    }
}

func keyboardWillHide(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        if view.frame.origin.y != 0 {
            let height = keyboardSize.height
            self.yourBtn.frame.origin.y -= height
        }
    }
}

To programmatically create the button you would use code similar to this:

myButton.frame = CGRect(...)
John Warlow
  • 2,922
  • 1
  • 34
  • 49
Ryan Cocuzzo
  • 3,109
  • 7
  • 35
  • 64
  • Could you please tell me why are you saying that the way I am doing it is weird? Do you think this is exposing my code to certain bugs? – bibscy Sep 01 '16 at 02:00
  • @bibscy I figured it out. Check out my updated answer. And to be honest I do believe this will be exposed to layout bugs (particularly on the iPad layout or smaller iPhones) when the text fields themselves take up more than half the screen and the button goes directly over them. To solve that, I would embed your text fields in a scroll view. – Ryan Cocuzzo Sep 01 '16 at 13:46
  • I also switched the `keyboardWillShow` and `keyboardWillHide` implementations before on accident, but I fixed that now so it should not make things disappear anymore. – Ryan Cocuzzo Sep 01 '16 at 13:47
  • I have emebedded my text filed in a scroll View, added the button programmtically. 1. The button does not lift up with the keyboard when I start editing the textfield. 2. when I click outside of the keyboard, the keyboard does not resign. (it did before adding the scroll View) – bibscy Sep 01 '16 at 18:12
  • I tried what you suggested but the button stays under the scroll View. Please see my new questions http://stackoverflow.com/questions/42543593 . Many thanks – bibscy Mar 02 '17 at 23:43
5

Complimentary to Ryan's answer above, this can be done all with auto-layout and no need for frames and CGRect.

Swift 5

In your view, constrain your button as you normally would but add a reference to the constraint for modification when the keyboard hides/shows:

var bottomButtonConstraint = NSLayoutConstraint()

bottomButtonConstraint = yourButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -12)
bottomButtonConstraint.isActive = true

In your ViewController's viewDidLoad():

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)

Also in your ViewController:

@objc private func keyboardWillShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        self.yourCustomView.bottomButtonConstraint.constant -= keyboardSize.height
    }
}

@objc private func keyboardWillHide(notification: NSNotification) {
    self.yourCustomView.bottomButtonConstraint.constant = -12
}
Justin Vallely
  • 5,932
  • 3
  • 30
  • 44
3

You need add(viewDidLoad) observers to call your functions:

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillShow), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillHide), name: UIKeyboardDidHideNotification, object: nil)
Klevison
  • 3,342
  • 2
  • 19
  • 32
  • I added the observers and it worked. However, @Ryan said that my implementation will be exposed to unexpected behaviour because of different iphone screen sizes. He suggested a method, but it did not work. The button does not lift with the keyboard, the keyboard no longer resigns when tapping outside of it, when tapping outside of keyboard, the programmatic button i have now created no longer shows, On iphone 6 the button is pinned to the bottom of the screen, but it looks different on other screens, ` nextButton.frame = CGRectMake(0, 620, 375, 50)`. Can you help? – bibscy Sep 01 '16 at 18:43
0

Consider using this pod: https://cocoapods.org/pods/IQKeyboardManager In AppDelegate.swift, just import IQKeyboardManagerSwift framework and enable IQKeyboardManager.

import IQKeyboardManagerSwift

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  IQKeyboardManager.shared.enable = true

  return true
}

}

Nedim Karavdic
  • 212
  • 1
  • 2
  • 10
  • IQKeyboardManager does update the text field if the text field is hidden by the keyboard. But it doesn't answer the question. – Ankur Lahiry Feb 22 '22 at 11:43
0

I know I'm a bit to late to the party, but since last answer was around 2 years ago I think I can provide more value to this question. My example works on Swift 5.5+, but should be okay with lower versions as well.

I my scenario I needed some sort of more universal, yet configurable solution, that would allow me to move view with keyboard and control spacings in any UIViewController class that I have in project.

Here are steps, that you need to make in order for it to work:

Step one:

Create outlet for your view bottom constraint and define custom spacings for keyboard and your source view:

@IBOutlet weak var <<yourViewName>>BottomConstraint: NSLayoutConstraint! // Your view's bottom constraint, that should be connected to safe/area or superview

private let <<yourViewName>>BottomSpacing: CGFloat = 56 // Spacing between view and safe area/superview when keyboard is hidden
private let <<yourViewName>>KeyboardSpacing: CGFloat = 22 // Spacing between active keyboard and your view

Step two:

Register observers in our view controller that will track show/hide of keyboard (you can call this method in viewDidLoad or init depending on your needs) Please mind, that you have to remove observers manually in i.e. deinit in older versions of Swift:

private func addKeyboardObservers() {
    // Notifications for when the keyboard show/hides
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardWillShow),
                                           name: UIResponder.keyboardWillShowNotification,
                                           object: nil)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardWillHide),
                                           name: UIResponder.keyboardWillHideNotification,
                                           object: nil)
}

Step three:

Add this code to your UIViewController class. This code will be called as a result of NotificationCenter observers:

@objc private func keyboardWillShow(_ notification: NSNotification) {
    moveViewWithKeyboard(notification: notification,
                         keyboardWillShow: true,
                         viewBottomConstraint: <<yourViewName>>BottomConstraint,
                         activeKeyboardToViewSpacing: <<yourViewName>>KeyboardSpacing,
                         hiddenKeyboardToViewSpacing: <<yourViewName>>BottomSpacing)
}

@objc private func keyboardWillHide(_ notification: NSNotification) {
    moveViewWithKeyboard(notification: notification,
                         keyboardWillShow: false,
                         viewBottomConstraint: <<yourViewName>>BottomConstraint,
                         activeKeyboardToViewSpacing: <<yourViewName>>KeyboardSpacing,
                         hiddenKeyboardToViewSpacing: <<yourViewName>>BottomSpacing)
}

Step Four:

Last part of puzzle, method, that gets keyboard size, calculates spacings and moves view accordingly.

Usually, in my projects I have a file named UIViewController+Keyboard.swift with this code in extension to UIViewController. You can also add other code, that corresponds to keyboard management, like hiding it when user taps around, as in example below:

extension UIViewController {

func hideKeyboardWhenTappedAround() {
    let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
    tap.cancelsTouchesInView = false
    view.addGestureRecognizer(tap)
}

@objc
func dismissKeyboard() {
    view.endEditing(true)
}

func moveViewWithKeyboard(notification: NSNotification,
                          keyboardWillShow: Bool,
                          viewBottomConstraint: NSLayoutConstraint,
                          activeKeyboardToViewSpacing: CGFloat,
                          hiddenKeyboardToViewSpacing: CGFloat) {
    
    guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
    let keyboardHeight = keyboardSize.height
    let keyboardAnimationDuration = notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
    let keyboardAnimationCurve = UIView.AnimationCurve(rawValue: notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! Int)!
    
    // Modifying spacing constants
    if keyboardWillShow {
        let safeAreaExists = self.view?.window?.safeAreaInsets.bottom != 0
        // Default value in case something goes wrong with bottom spacings
        let bottomConstant: CGFloat = 20
        viewBottomConstraint.constant = keyboardHeight + (safeAreaExists ? 0 : bottomConstant) + activeKeyboardToViewSpacing
    } else {
        viewBottomConstraint.constant = hiddenKeyboardToViewSpacing
    }
    
    // Animating the view the same way the keyboard animates
    let animator = UIViewPropertyAnimator(duration: keyboardAnimationDuration, curve: keyboardAnimationCurve) { [weak self] in
        self?.view.layoutIfNeeded()
    }
    
    animator.startAnimation()
} }

After implementation of all this steps you should have desired and reusable behaviour with configurable spacings. Hope that helps!

Taras Tomchuk
  • 331
  • 7
  • 19