My Swift Tool Belt (Part 3): Extending UILabel

tool-belt

The third item in my Swift Tool Belt is a couple of extensions on UILabel. These extensions will allow easy access to modify the edge insets on a label (the extra padding all around the text). There’s also an extension to add a rotation transform to a label. All of these extensions will be IBDesignable and IBInspectable for easy modification from Xcode’s Interface Builder.

Rotation

The first extension is pretty simple. It adds a rotation transform to a UILabel. The setter for the rotation converts the degrees into radians and then constructs a transform to rotate the label. The getter is not necessary here, so I just return zero.

You could extend this extension to do more than just rotation. Any ability you have with CGAffineTransform, such as translation or scale, can be done in a similar way.


import UIKit

extension UILabel {
    @IBInspectable
    var rotation: Int {
        get {
            return 0
        } set {
            let radians = CGFloat(CGFloat(Double.pi) * CGFloat(newValue) / CGFloat(180.0))
            self.transform = CGAffineTransform(rotationAngle: radians)
        }
    }
}

I’ve added a basic label and a gray background to my storyboard so you can see the outline of the UILabel.

With the extension compiled in our project, a rotation setting will show up in Interface Builder’s Attribute Inspector. However, you will not see your rotation changes take effect until we change the class of our UILabel to DesignableLabel. (See my first post in this series to get an explanation of DesignableLabel.) If we don’t change the class type, the label will still rotate at runtime–you just won’t be able to see it in your storyboard.

attribute inspector setting

Now, let’s set the class type of our label to DesignableLabel.

set class to designable label

At this point, you should be able to see the label rotated in the storyboard.

You may notice clipping on your label text after it is rotated. Unfortunately, Interface Builder does not do a great job of rendering the rotated label, but this will not be the case at runtime. The label will not be clipped when you run your project.

If you can’t see the rotated label, you may have to refresh the storyboard view in Xcode. To do this, select Editor->Refresh All Views.

This is what the rotated label looks like running on the simulator.

Edge Insets

The next extension on UILabel is actually a combination of an extension and an override of UILabel. You cannot set edge insets on a label easily. In order to accomplish it, you have to draw the text manually. The class override will take care of the drawing, and the extension provides easy access to the edge insets in Interface Builder.

I did not come up with this solution. Thanks go to Nikolai Ruhe answering this Stack Overflow question. I’ve changed the name of the class from his NRLabel, and I moved the @IBDesignable to the class vs. the extension.

As I mentioned in my first post in this series, putting the attribute on an extension can be hit and miss.


import UIKit

// From http://stackoverflow.com/questions/21167226/resizing-a-uilabel-to-accomodate-insets/21267507#21267507

@IBDesignable
class EdgeInsetLabel: UILabel {
    var textInsets = UIEdgeInsets.zero {
        didSet { invalidateIntrinsicContentSize() }
    }

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        let insetRect = UIEdgeInsetsInsetRect(bounds, textInsets)
        let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines)
        let invertedInsets = UIEdgeInsets(top: -textInsets.top,
                left: -textInsets.left,
                bottom: -textInsets.bottom,
                right: -textInsets.right)
        return UIEdgeInsetsInsetRect(textRect, invertedInsets)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, textInsets))
    }
}

extension EdgeInsetLabel {
    @IBInspectable
    var leftTextInset: CGFloat {
        set { textInsets.left = newValue }
        get { return textInsets.left }
    }

    @IBInspectable
    var rightTextInset: CGFloat {
        set { textInsets.right = newValue }
        get { return textInsets.right }
    }

    @IBInspectable
    var topTextInset: CGFloat {
        set { textInsets.top = newValue }
        get { return textInsets.top }
    }

    @IBInspectable
    var bottomTextInset: CGFloat {
        set { textInsets.bottom = newValue }
        get { return textInsets.bottom }
    }
}

To demonstrate the override and extension to UILabel, I’ve added a couple of labels in Xcode and changed the background so you can see the edge inset.

Change the class name of your label to EdgeInsetLabel.

After you change the class for your label, you will see the new attributes that we added in the extension show up in the Attributes inspector.

I like this solution and use it on almost every project. Thanks again to Nikolai Ruhe. I hope you find it useful as well.


My Swift Tool Belt

Catch up on the other posts in this series: