Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITextField text jumps iOS 9

Link for previous question: UITextField text jumps

Briefly: I have ViewController with 2 UITextField elements. When loginField is firstResponder, after

self.passwordField.becomeFirstResponder()

text in login field jumps to the top left corner and back. And what's more strange: this glitch reproduces only first time, then you need recreate ViewController to observe this behavior

Here is video of the glitch http://tinypic.com/player.php?v=6nsemw%3E&s=8#.VgVb3cuqpHx

I ended up with this (doesn't work for iOS 9):

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if textField === self.loginField {
        self.loginField.resignFirstResponder()
        // Shitty workaround. Hi, Apple!
        self.loginField.setNeedsLayout()
        self.loginField.layoutIfNeeded()

        self.passwordField.becomeFirstResponder()
        return false
    }

    return true
}

Is there anybody who has been stucked with this bug? Any suggestions?

Keyboard notifications handlers

My main view is UIScrollView, for which I change bottom space to superview, so user can scroll all the content even when keyboard is shown

func keyboardWillShow(notification : NSNotification) {
    let keyboardInfo = notification.userInfo!
    let keyboardFrame = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue
    let animDuration = keyboardInfo[UIKeyboardAnimationDurationUserInfoKey]!.doubleValue!

    UIView.animateWithDuration(animDuration, animations: {
        self.scrollViewBottom.constant = keyboardFrame.height
        self.view.layoutIfNeeded()

        let offsetY = CGRectGetMaxY(self.loginButton.frame) + 10 - self.scrollView.frame.height
        if offsetY > 0 {
            self.scrollView.contentOffset = CGPointMake(0, offsetY)
        }
    })
}

func keyboardWillHide(notification : NSNotification) {
    self.scrollViewBottom.constant = 0
    self.view.layoutIfNeeded()
}

As I discovered keyboard notifications in iOS7, 8 and 9 very differ. So, in iOS 9 notifications are sent while changing firstResponder even if keyboard will not Show/Hide. Also, when I change firstResponder with tapping on textField (not tapping Next on keyboard which is handled by my code), there is only KeyboardWillShow notification and no KeyboardWillHide. And as for me, userInfo has some trash frame values, here is log when changing first responder using next button (works ok, without glitches):

2015-10-07 12:54:13.870 keyboardWillHide: [UIKeyboardFrameBeginUserInfoKey: NSRect: {{0, 352}, {320, 216}}, UIKeyboardCenterBeginUserInfoKey: NSPoint: {160, 460}, UIKeyboardFrameEndUserInfoKey: NSRect: {{0, 568}, {320, 216}}, UIKeyboardCenterEndUserInfoKey: NSPoint: {160, 676}, UIKeyboardAnimationDurationUserInfoKey: 0.25, UIKeyboardIsLocalUserInfoKey: 1, UIKeyboardBoundsUserInfoKey: NSRect: {{0, 0}, {320, 216}}, UIKeyboardAnimationCurveUserInfoKey: 7] 2015-10-07 12:54:13.896 keyboardWillShow: [UIKeyboardFrameBeginUserInfoKey: NSRect: {{0, 352}, {320, 216}}, UIKeyboardCenterBeginUserInfoKey: NSPoint: {160, 460}, UIKeyboardFrameEndUserInfoKey: NSRect: {{0, 352}, {320, 216}}, UIKeyboardCenterEndUserInfoKey: NSPoint: {160, 460}, UIKeyboardAnimationDurationUserInfoKey: 0.25, UIKeyboardIsLocalUserInfoKey: 1, UIKeyboardBoundsUserInfoKey: NSRect: {{0, 0}, {320, 216}}, UIKeyboardAnimationCurveUserInfoKey: 7]

And here is log when I tap on the second textField:

2015-10-07 12:55:13.879 keyboardWillShow:[UIKeyboardFrameBeginUserInfoKey: NSRect: {{0, 352}, {320, 216}}, UIKeyboardCenterBeginUserInfoKey: NSPoint: {160, 460}, UIKeyboardFrameEndUserInfoKey: NSRect: {{0, 352}, {320, 216}}, UIKeyboardCenterEndUserInfoKey: NSPoint: {160, 460},
UIKeyboardAnimationDurationUserInfoKey: 0.25, UIKeyboardIsLocalUserInfoKey: 1, UIKeyboardBoundsUserInfoKey: NSRect: {{0, 0}, {320, 216}}, UIKeyboardAnimationCurveUserInfoKey: 7]

Resolution

I discovered that I have another Keyboard Controller that receives keyboard notifications and make some animations. That's where the problem lies

like image 493
user3237732 Avatar asked Oct 07 '15 08:10

user3237732


2 Answers

Based on your edited question I can see this, when you tap next button on keyboard:

  1. You're calling resignFirstResponder() and then becomeFirstResponder(). This calls keyboardWillHide notification and then keyboardWillShow notification
  2. In keyboardWillHide you have self.view.layoutIfNeeded() which layouts the view (and subviews - textfields) without animation.

Because of this the textfield layout is "fixed" and when you do animation in keyboardWillShow the text in textfield doesn't "jump" anymore, because you did layout in keyboardWillHide.

But when you just tap another textfield, only keyboardWillShow is called, layout is not "fixed" in textfield and when you animate the view, the text does a "jump" animation.

That's why it doesn't jump when you tap next on keyboard but it does jump when you just tap another textfield.

So I would advise to change it to this:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if textField === self.loginField {
        self.passwordField.becomeFirstResponder()
        return false
    }

    return true
}

func keyboardWillShow(notification : NSNotification) {
    let keyboardInfo = notification.userInfo!
    let keyboardFrame = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue
    let animDuration = keyboardInfo[UIKeyboardAnimationDurationUserInfoKey]!.doubleValue!

    self.loginField.layoutIfNeeded()
    self.passwordField.layoutIfNeeded()

    if keyboardFrame.height != self.scrollViewBottom.constant {
        UIView.animateWithDuration(animDuration, animations: {
            self.scrollViewBottom.constant = keyboardFrame.height
            self.view.layoutIfNeeded()

            let offsetY = CGRectGetMaxY(self.loginButton.frame) + 10 - self.scrollView.frame.height
            if offsetY > 0 {
                self.scrollView.contentOffset = CGPointMake(0, offsetY)
            }
        })
    }
}
like image 59
haluzak Avatar answered Oct 20 '22 01:10

haluzak


Tnx to haluzak:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if textField === self.loginField {
        self.passwordField.becomeFirstResponder()
        return false
    }

    return true
}

func keyboardWillShow(notification : NSNotification) {
    let keyboardInfo = notification.userInfo!
    let keyboardFrame = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue
    let animDuration = keyboardInfo[UIKeyboardAnimationDurationUserInfoKey]!.doubleValue!

    UIView.performWithoutAnimation({
        self.loginField.layoutIfNeeded()
        self.passwordField.layoutIfNeeded()
    })

    if keyboardFrame.height != self.scrollViewBottom.constant {
        UIView.animateWithDuration(animDuration, animations: {
            self.scrollViewBottom.constant = keyboardFrame.height
            self.view.layoutIfNeeded()

            let offsetY = CGRectGetMaxY(self.loginButton.frame) + 10 - self.scrollView.frame.height
            if offsetY > 0 {
                self.scrollView.contentOffset = CGPointMake(0, offsetY)
            }
        })
    }
}
like image 6
user3237732 Avatar answered Oct 20 '22 00:10

user3237732