Interface Builder: automatically set aspect ratio

2019-08-21 09:14发布

问题:

The share icon (image is white) below is 114x128 pixels.

Fixing the height in Interface Builder to 23 pixels with AutoLayout then using Aspect Fit for the Content Mode does not change the frame rectangle, though.

This causes issues with alignment since if you want the icon 15 pixels from the trailing edge, it's now hard to do so.

One option is to manually set the aspect ratio (i.e., 114/128) as an AutoLayout constraint within IB.

But this is extremely fragile. If you change the image dimensions, you must remember to adjust the aspect ratio.

Is there a way where IB automatically adjusts the frame rectangle as well as the content?

The last option is to do everything within code, but doing everything within IB would be ideal.

Frame rectangle not changing in IB:

Share icon (white icon so not visible here):

回答1:

I believe you'll have to do this programmatically. If you don't want to do it view by view, just define some subclass (which you can specify as the base class in IB) that sets constraints programmatically

class RatioImageView: UIImageView {

    private var ratioConstraint: NSLayoutConstraint?

    override var image: UIImage? {
        didSet { updateRatioConstraint() }
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        updateRatioConstraint()
    }

    private func updateRatioConstraint() {
        if let ratioConstraint = ratioConstraint {
            removeConstraint(ratioConstraint)
        }

        guard superview != nil else { return }

        let ratio: CGFloat
        if let image = image {
            ratio = image.size.width / image.size.height
        } else {
            ratio = 1
        }

        ratioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: ratio)
        ratioConstraint?.isActive = true
    }
}

This does it on an image view, but you could presumably do the same with UIButton or whatever, too.


Or in another variation of the theme, you could control this with intrinsic size, where perhaps you explicitly set the height (and because this is @IBInspectable, you can do this right in IB), and it will return the intrinsic size scaled for that height:

@IBDesignable
class RatioImageView: UIImageView {

    @IBInspectable var height: CGFloat = 0

    override var intrinsicContentSize: CGSize {
        guard let image = image else { return .zero }
        guard height > 0 else { return image.size }

        let ratio = image.size.width / image.size.height

        return CGSize(width: height * ratio, height: height)
    }

}