skunkmb skunkmb - 5 months ago 33
iOS Question

Adjusting UILabel text with Autoshrink

Background



I am creating an application, and inside it I have an animation that repositions a
UILabel
and a
UITextField
when the keyboard is presented. It is set up like this:

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(raiseTextField), name: UIKeyboardWillShowNotification, object: nil)


And this is the function:

func raiseTextField(sender: NSNotification){
UIView.animateWithDuration(0.25, animations: { () -> Void in
self.userInputTextFieldBottomContstraint.constant = sender.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue().size.height
self.view.layoutIfNeeded()
})
}


My storyboard looks like this:
Storyboard

I have constraints set up so that the
friendInputView
is the right size, and it has Autoshrink so the font adjusts accordingly.

Problem



When I call
raiseTextField()
, everything works fine, except the font-size of the
friendInputView
takes a long time to change. Instead of updating while the animation is happening, it updates afterwards.

It does eventually adjust, just not at the preferred speed.

I tried adding
self.friendInputLabel.adjustsFontSizeToFitWidth = true
inside of
animateWithDuration()
, but it did't seem to change anything.

Examples



Before raising keyboard:



(text adjusted correctly)

Before

Immediately after raising keyboard:



(text-size too big)

Middle

A second or so after raising keyboard:



(text adjusted correctly)

After

The Bottom Line



Is there any way to make a
UILabel
resize its text programmatically, rather than just waiting for it to update itself? Similarly to how
layoutIfNeeded()
re-adjusts constraints, is there any function like that for re-adjusting Autoshrink?

Edit 1



I ended up getting it to work based on an answer I got that said to use a
UITextFieldDelegate
instead of
NSNotificationCenter
. I replaced the
raiseTextField()
function with the following code:

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {

self.userInputTextFieldBottomContstraint.constant = 250
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.25, animations: { () -> Void in

self.view.layoutIfNeeded()
})

return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()

self.userInputTextFieldBottomContstraint.constant = 10
self.view.setNeedsUpdateConstraints()

UIView.animateWithDuration(0.25, animations: { () -> Void in
self.view.layoutIfNeeded()
})

return true
}


However, now I have a new question: How can I get it to use the real keyboard size? Assuming that the keyboard size is always 250 isn't very helpful.

I tried doing so by creating this variable:

var keyboardHeight: CGFloat?


Putting this in
viewDidLoad()
:

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(setTheKeyboardHeight), name: UIKeyboardWillShowNotification, object: nil)


And creating this function:

func setTheKeyboardHeight(sender: NSNotification){
keyboardHeight = sender.userInfo![UIKeyboardFrameEndUserInfoKey]?.CGRectValue().height
}


Then I added it to
textFieldShouldBeginEditing()
like this:

self.userInputTextFieldBottomContstraint.constant = keyboardHeight!

self.view.setNeedsUpdateConstraints()

UIView.animateWithDuration(0.25, animations: { () -> Void in
self.view.layoutIfNeeded()
})

return true


However, it ends up just giving the error:

fatal error: unexpectedly found nil while unwrapping an Optional value


I think it happens because the
keyboardHeight
gets set after
textFieldShouldBeginEditing()
is called, but I don't know how to fix it.

Is there any way that I can access the keyboard height while using
textFieldShouldBeginEditing()
, or do I have to use
NSNotificationCenter
?

Answer

I have done same demo what you have done, and have observed some weird behaviour of UIKeyboardNotification, i'm not sure that it is weird or it is like that only,

What i have observed is when you change any constraint in UIKeyboardNotification it changes the constraint constant (or frame) with animation, even though you have not used UIView animationWithDuration block. So i think this delay might be due to that animation effect added by KeyboardNotification,

func raiseTextField(sender: NSNotification){

    self.bottomConstraintTextField.constant = 250//sender.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue().size.height
    self.view.setNeedsUpdateConstraints()

    //As you can see i have commented the animation block, still it changes the constraint with animation.
    //UIView.animateWithDuration(0.25, animations: { () -> Void in

        self.view.layoutIfNeeded()
    //})
}

So in your scenario i have added the animation code in textFields delegates and it it working as expected,

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {

    self.bottomConstraintTextField.constant = 250//sender.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue().size.height
    self.view.setNeedsUpdateConstraints()
    UIView.animateWithDuration(0.25, animations: { () -> Void in

    self.view.layoutIfNeeded()
    })

    return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()

    self.bottomConstraintTextField.constant = 10
    self.view.setNeedsUpdateConstraints()

    UIView.animateWithDuration(0.25, animations: { () -> Void in
        self.view.layoutIfNeeded()
    })

    return true
}

Here is the result,

enter image description here

Hope it helps you.

Update:

I have played arround it and found one workaround, so instead of taking a variable to hold keyboard height you can do it as below,

func raiseTextField(sender: NSNotification){

    self.bottomConstraintTextField.constant = sender.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue().size.height
    self.view.setNeedsUpdateConstraints()

    self.view.layoutIfNeeded()
}


func textFieldShouldBeginEditing(textField: UITextField) -> Bool {

    self.bottomConstraintTextField.constant = 200
    self.view.setNeedsUpdateConstraints()

    UIView.animateWithDuration(0.25, animations: { () -> Void in

    self.view.layoutIfNeeded()
    })

    return true
}

So have both textField delegate and KeyboardNotification and change the bottom constraint in textField delegate it will get called before keyboard notification and by doing this you will not observe the delay in shrinking the label text.

Comments