5
votes

Context:

I have a UIView containing a UITextView. I want to move that UIView up when the keyboard is visible. So the idea is to change the Y position.

Problem:

Sometimes, the UIView goes back to its initial Y position while the keyboard is still visible. And it doesn't come back anymore even if I focus the input. I really don't know why.

Illustration (gif):

https://d17oy1vhnax1f7.cloudfront.net/items/2K0z3P1j1x2f0X2X1B2v/ci.gif

PS: The keyboard appears 3 times in the Gif. It is looping so it is hard to distinguish the end.

Code

What I did, first I've added notification observer:

NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillChangeFrame), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)

Then I've defined the keyboard toggle handler :

func keyboardWillChangeFrame(notification: NSNotification) {

    let info = notification.userInfo!
    let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue

    self.keyboardInfo["isVisible"] = self.view.frame.size.height - keyboardFrame.origin.y != 0
    self.keyboardInfo["height"] = keyboardFrame.size.height
    self.keyboardInfo["animationDuration"] = info[UIKeyboardAnimationDurationUserInfoKey] as! Double
    self.keyboardInfo["animationCurve"] = info[UIKeyboardAnimationCurveUserInfoKey] as! UInt

    if self.keyboardInfo["isVisible"] as! Bool {
        self.moveCommentInputUp()
    } else {
        self.moveCommentInputDown()
    }

}

After that, I've defined the methods which move up/down the comment input view:

func moveCommentInputUp() {

    print("Moving up... ", self.commentFormView.frame.origin.y, " to ", self.view.frame.size.height - (self.keyboardInfo["height"] as! CGFloat) - self.commentFormView.frame.size.height)

    UIView.beginAnimations(nil, context: nil)
    UIView.setAnimationDuration(self.keyboardInfo["animationDuration"] as! TimeInterval)
    UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: Int(self.keyboardInfo["animationCurve"] as! UInt))!)
    UIView.setAnimationBeginsFromCurrentState(true)

    ViewUtil.changeViewFrame(view: self.commentFormView, yPosition: self.view.frame.size.height - (self.keyboardInfo["height"] as! CGFloat) - self.commentFormView.frame.size.height)
    ViewUtil.removeShadowToView(self.commentFormLauncherButton)

    UIView.commitAnimations()

}

func moveCommentInputDown() {

    print("Moving down... ", self.commentFormView.frame.origin.y, " to ", self.view.frame.size.height)

    UIView.beginAnimations(nil, context: nil)
    UIView.setAnimationDuration(self.keyboardInfo["animationDuration"] as! TimeInterval)
    UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: Int(self.keyboardInfo["animationCurve"] as! UInt))!)
    UIView.setAnimationBeginsFromCurrentState(true)

    ViewUtil.changeViewFrame(view: self.commentFormView, yPosition: self.view.frame.size.height)
    ViewUtil.addShadowToView(commentFormLauncherButton, position: CGSize(width: 0, height: 5))

    UIView.commitAnimations()

}

The action of the button is

@IBAction func showCommentForm(_ sender: UIButton) {

    self.commentInput.becomeFirstResponder()

}

Logs (please refer to the Gif) are showing that the view moves from 568 - bottom of the screen - to 568 again

// The numbers show the initial and the final value of the keyboard Y position

// Click on button to focus the input
Moving up...  568.0  to  282.0
// Tap anywhere
Moving down...  282.0  to  568.0

// Click again on button to focus the input
Moving up...  568.0  to  282.0
///// Write "hhh". The view is gone, I don't know why.
// Tap anywhere
Moving down...  568.0  to  568.0    // The view seems to move from 568 to 568, hugh

// Click on button to focus the input
Moving up...  568.0  to  282.0      // This didn't work apparently
// Tap anywhere
Moving down...  568.0  to  568.0    // 568 to 568 again

Did I miss something?

System: iOS 9, Xcode 8, Swift 3

2

2 Answers

4
votes

Okay so this is a solution which doesn't require CocoaPods.

My approach involves setting the constant of the textField's bottom constraint when the keyboard appears and disappears. This means you need to create a reference to the textField's bottom constraint.

@IBOutlet weak var textFieldBottomConstraint: NSLayoutConstraint!

I started with setting the observers on NSNotification.Name.UIKeyboardWillShow and UIKeyboardWillHide in viewDidLoad, instead of UIKeyboardWillChangeFrame:

NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

Next up I set the selector and its function to set the constant of the textField's bottom constraint:

func handleKeyboardNotification(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        let keyboardFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)
        let keyboardFrame = keyboardFrameValue?.cgRectValue

        let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow

        textFieldBottomConstraint.constant = isKeyboardShowing ? keyboardFrame!.height + 4 : 4
        self.view.layoutIfNeeded()
    }
}

Note that textFieldBottomConstraint is the reference variable to the bottom constraint.

What this does is makes textField set it's bottom constraint whenever the keyboard appears or disappears, and doesn't suffer from the weird glitches that you are experiencing.

Hope this helps and don't hesitate to ask for clarification!

Credits to YouTuber Let's Build that App! for providing the solution through this video:

https://www.youtube.com/watch?v=p8IaS5lmhuM

2
votes

To give you a different idea of how to bypass any kind of problems with the keyboard and the problem with input fields, have a look at:

pod 'IQKeyboardManagerSwift'

IQKeyboardManagerSwift / Git Link

I used to move everything manually like you, but with this Framework, you only need to implement it once within the AppDelegate and it works completely 100% automatically. No matter if in vertical or horizontal. If in TextFields or Views or what ever. I'm using it in every App ever since.

enter image description here

To have a UIView attached on the Keyboard like you want it, you need to use a InputAccessoryView.

    @IBOutlet weak var textField: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()

    let customView = UIView(frame: CGRectMake(0, 0, 10, 100))
    customView.backgroundColor = UIColor.redColor()
    textField.inputAccessoryView = customView
}

If you prefer using a Framework to safe time, I recommend you to use the CHGInputAccessoryView

enter image description here

I hope this helps you.