Tuesday, December 18, 2018

How to auto resize a button's image according to the button's size in Swift 3

I'm developing a pet project called MyCalculator using Swift 3.


When the screen is rotated from portrait to landscape, more buttons appears then each button become smaller to fit the screen and does its image.


Without any additional codes, the image does not get smaller if its size is smaller than the button's in both portrait and landscape modes. This does not look good. What i want is, for example, the space between the image's top and the button's top should always equal 30% of the button's height and the image should get smaller or bigger according to the space.

I created a custom UIButton as following:

       
import Foundation
import UIKit

public class CustomButton : UIButton {
    private let offset = CGFloat(2.0) // 2 point

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        self.imageView?.contentMode = .scaleAspectFit
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        let edge = bounds.height / 3 - offset
        self.imageEdgeInsets = UIEdgeInsets(top: edge, left: 0, bottom: edge, right: 0)
    }   
}
       

The following line means the button's image scales to fit the available space by maintaining the aspect ratio. In other words, if the image's height changes, its width changes too proportional to the height.


self.imageView?.contentMode = .scaleAspectFit 
       

The imageEdgeInsets property is used to set the spaces around the image on the button. A button can have both image and title (text) at the same time. The titleEdgeInsets property is used to define the spaces around the title. There is another related property called contentEdgeInsets, which is used to set the spaces around both image and title together. I only specified the values of the top and the bottom because I need the top and bottom spaces to be equal.

The layoutSubviews() method is called when bounds or frame property changes, according to https://forums.developer.apple.com/message/317152?et=watches.email.thread#317152.


Unreliable Solution


It works for my case but it's not guaranteed since the bounds property is a computed property, not a stored property so it might have its own instance variable, which might be set internally without going through the bounds property then didSet and willSet would not be called.

import Foundation
import UIKit

public class CustomButton : UIButton {
    private let offset = CGFloat(2.0) // 2 point
    
    public override var bounds: CGRect {
        didSet {
            self.boundDidChange()
        }
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        self.imageView?.contentMode = .scaleAspectFit
    }
    
    public func boundDidChange() {
        let edge = bounds.height / 3 - offset
        self.imageEdgeInsets = UIEdgeInsets(top: edge, left: 0, bottom: edge, right: 0)
    }
}
       



References
https://developer.apple.com/documentation/uikit/uiview/contentmode/scaleaspectfit
https://developer.apple.com/documentation/uikit/uibutton/1624034-imageedgeinsets

No comments:

Post a Comment