Dynamically resizing labels in Swift

One thing that iOS does poorly is allowing dynamic layouts when it comes to text. When you create a label that can have variable length it’s difficult to set layout constraints to allow that label to grow and shrink as needed. This is going to become an even bigger issue with the release of the iPhone 6. Screens will become wider so you can’t count on a constant width anymore.

A solution that was often implemented in Objective-C was to do some calculation on the text in the label to find how much room it would need. After this computation, the frame would be updated so that it was able to show all of the text. Below is my approach to this issue in Swift.

import Foundation

extension UILabel {
    func resizeHeightToFit(heightConstraint: NSLayoutConstraint) {
        let attributes = [NSFontAttributeName : font]
         numberOfLines = 0
         lineBreakMode = NSLineBreakMode.ByWordWrapping
         let rect = text.boundingRectWithSize(CGSizeMake(frame.size.width, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)
        heightConstraint.constant = rect.height
        setNeedsLayout()
    }
}

I like to make sure my layout constraints are always right, so my method takes an NSLayoutConstraint for the height of the label. This keeps the label from shrinking back down if the screen is redrawn for some reason.

There are a few other things to note here. I am grabbing the font already set to the label because I have my desired font and color set in my StoryBoard. You could programmatically set those here if you prefer.

The other important thing to remember is when to call this. I call this method on my labels in viewDidAppear. The reason for this is because I want to use the width determined by my layout constraints. If you try to use frame.size.width before viewDidAppear, it won’t be correct (it will be the same width as the StoryBoard has it laid out). This causes a redraw immediately when the screen is loaded. I’m currently looking into a way around this and will update this post.

UPDATE

The simplest way I found to get the layout constraints to take effect before the view appears is to call view.layoutIfNeeded() from viewWillAppear. This will layout the view according to the layout constraints for you. Then you can call resizeHeightToFit on your labels from viewWillAppear (after you call layoutIfNeeded) and your labels will resize before the view becomes visible to the user.

Written on August 21, 2014