'UIButton not respect Aspect Fill contentMode after resizing animation

I am use auto layout. Following is the initial state of the view.

In the centre is a button contained in a view. The button has contentMode Aspect Fill, and the image is set as the background image of the button.

enter image description here

Then I use the following code to transform the view, which will enlarge the centre card to fill the screen, and move the image to the top of the view:

cardTrailingSpaceConstraint.constant = 0
cardLeadingSpaceConstraint.constant = 0
cardView.removeConstraint(cardAspectRatioConstraint)
let cardHeightConstraint = NSLayoutConstraint(item: cardView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1.0, constant: 0)
view.addConstraint(cardHeightConstraint)

dishImageButton.removeConstraint(dishButtonBottomSpaceConstraint)
let dishButtonHeightConstraint = NSLayoutConstraint(item: dishImageButton, attribute: .Height, relatedBy: .Equal, toItem: cardView, attribute: .Height, multiplier: 0.2, constant: 0)
cardView.addConstraint(dishButtonHeightConstraint)

cardView.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: nil, animations: { [unowned self] () -> Void in
    self.cardHeader.alpha = 0
    self.cardView.layer.cornerRadius = 0
    self.cardView.layoutIfNeeded()

    }) { [unowned self] (finished) -> Void in

        }

The result is:

enter image description here

However, it is not what I want. The button is not respecting the contentMode and the image then get stretched.

Can anyone tell me how to maintain the Aspect Fill contentMode of the button?



Solution 1:[1]

  1. Set the button type to UIButtonTypeCustom (“Custom” in a storyboard or xib).
  2. Set the button's image, not the button's background image.
  3. In viewDidLoad, set button.imageView.contentMode to UIViewContentMode.ScaleAspectFill.

Solution 2:[2]

Swift 3


Going off of Rob's answer:

    let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 80, height: 30))
    btn.setImage(UIImage(named: "albumsBtn"), for: UIControlState.normal)
    btn.imageView?.contentMode = UIViewContentMode.scaleAspectFit
    btn.addTarget(self.navigationController, action: #selector(CustomGalleryViewController.showAlbums(_:)), for:  UIControlEvents.touchUpInside)
    let item = UIBarButtonItem(customView: btn)
    self.navigationItem.leftBarButtonItem = item

Solution 3:[3]

In Xcode 10.1 you can use interface builder. In the attributes inspector on the right hand side, use the Control section as follows:

ios swift xcode 10.1 uibutton scale to fill

Solution 4:[4]

Try this before [btn setImage:forState:] usage:

    btn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
    btn.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
  • Swift 4
//Center
btn.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.center

//Left
btn.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left

//Right
btn.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.right

Solution 5:[5]

Swift 2.x version:

let myButton = UIButton(type: .Custom)
myButton.frame = CGRectMake(0, 0, 200, 20)
myButton.backgroundColor = UIColor.blackColor()
myButton.imageView.contentMode = .ScaleAspectFill // ALTERNATIVE:  .ScaleAspectFit
myButton.setImage(UIImage(named: "myImageName"), forState: .Normal)
myButton.addTarget(self, action: #selector(buttonAction), forControlEvents: .TouchUpInside)
view.addSubview(myButton)

Solution 6:[6]

Swift 2

button.imageView?.contentMode = UIViewContentMode.ScaleAspectFit

all contentMode:

  .ScaleToFill
   .ScaleAspectFit
   .ScaleAspectFill
   .Redraw 
   .Center 
   .Top
   .Bottom
   .Left
   .Right
   .TopLeft
   .TopRight
   .BottomLeft
   .BottomRight

Solution 7:[7]

Swift 4.2

Extension

// Inspectable image content mode
extension UIButton {
    /// 0 => .ScaleToFill
    /// 1 => .ScaleAspectFit
    /// 2 => .ScaleAspectFill
    @IBInspectable
    var imageContentMode: Int {
        get {
            return self.imageView?.contentMode.rawValue ?? 0
        }
        set {
            if let mode = UIViewContentMode(rawValue: newValue),
                self.imageView != nil {
                self.imageView?.contentMode = mode
            }
        }
    }
}

UPDATE: This and other answers doesn't work for me in ios 11. The most closest answer is by @Jaro but i think it's better to make an UIImageView and over it add a button, or create a custom class of UIImageView that would have a gesture recognizer and click animation.

Solution 8:[8]

The language seems to have had an update.

I had to dig a little deeper to get xscoder solution for:

myButton.imageView.contentMode = .ScaleAspectFill 

the updated version is as follows:

myButton.imageView?.contentMode = UIView.ContentMode.scaleAspectFit

Solution 9:[9]

Swift

for button has contentMode Aspect Fill:

btn.contentHorizontalAlignment = .fill
btn.contentVerticalAlignment = .fill
btn.imageView?.contentMode = .scaleAspectFill

Solution 10:[10]

Swift 5.0 code for a custom UIButton with .scaleAspectFill

var mainImageButton : UIButton = {
    var imageButton = UIButton()
    imageButton.translatesAutoresizingMaskIntoConstraints = false
    imageButton.contentMode = .scaleAspectFit
    imageButton.backgroundColor = .white
    imageButton.layer.cornerRadius = 80 / 2

    // The short way
    imageButton.contentMode = .scaleAspectFit

    // The long way
    imageButton.imageView?.contentMode = UIView.ContentMode.scaleAspectFill

    imageButton.clipsToBounds = true
    return imageButton
}()

Solution 11:[11]

might help someone

button.subviews.first?.contentMode = .scaleAspectFit

Solution 12:[12]

You can subclass the button and add these:

class FitButton: UIButton {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)


    }

    override func layoutSubviews() {
        self.imageView?.contentMode = .scaleAspectFill
        self.contentHorizontalAlignment = .fill
        self.contentVerticalAlignment = .fill
        super.layoutSubviews()


    }

}

Solution 13:[13]

This solution worked for me when I was using the Custom Button for the BarButtonItem

1. Loading image from Server
2. Crop image
3. Set the image for the button

Here is the function for crop image:

extension UIImage {
  func crop(to:CGSize) -> UIImage {
    guard let cgimage = self.cgImage else { return self }

    let contextImage: UIImage = UIImage(cgImage: cgimage)

    let contextSize: CGSize = contextImage.size

    //Set to square
    var posX: CGFloat = 0.0
    var posY: CGFloat = 0.0
    let cropAspect: CGFloat = to.width / to.height

    var cropWidth: CGFloat = to.width
    var cropHeight: CGFloat = to.height

    if to.width > to.height { //Landscape
        cropWidth = contextSize.width
        cropHeight = contextSize.width / cropAspect
        posY = (contextSize.height - cropHeight) / 2
    } else if to.width < to.height { //Portrait
        cropHeight = contextSize.height
        cropWidth = contextSize.height * cropAspect
        posX = (contextSize.width - cropWidth) / 2
    } else { //Square
        if contextSize.width >= contextSize.height { //Square on landscape (or square)
            cropHeight = contextSize.height
            cropWidth = contextSize.height * cropAspect
            posX = (contextSize.width - cropWidth) / 2
        }else{ //Square on portrait
            cropWidth = contextSize.width
            cropHeight = contextSize.width / cropAspect
            posY = (contextSize.height - cropHeight) / 2
        }
    }

    let rect: CGRect = CGRect(x : posX, y : posY, width : cropWidth, height : cropHeight)

    // Create bitmap image from context using the rect
    let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!

    // Create a new image based on the imageRef and rotate back to the original orientation
    let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)

    cropped.draw(in: CGRect(x : 0, y : 0, width : to.width, height : to.height))

    return cropped
  }
}

Solution 14:[14]

Anyone experiencing this problem for IOS 15 should check out Matt's answer in this other question.

Xcode has changed a default style value for UIButtons. Switching style in the Buttons attributes from plain to default solves the issue.