Is it possible to set the contentMode for my UIImage to .scaleAspectFit and .bottom simultaneously ?
This is how my image looks like at the moment:

UIImageView:
let nightSky: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "nightSky")
v.translatesAutoresizingMaskIntoConstraints = false
v.contentMode = .scaleAspectFit
return v
}()
Constraints:
nightSky.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
nightSky.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -120).isActive = true
nightSky.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
nightSky.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
Here is a custom class that allows Aspect Fit and Alignment properties.
It is marked @IBDesignable so you can see it in Storyboard / Interface Builder.
The @IBInspectable properties are:

Select the image as you would for a normal UIImageView.
Valid values for HAlign are "left" "center" "right" or leave blank for default (center).
Valid values for VAlign are "top" "center" "bottom" or leave blank for default (center).
"Aspect Fill" is On or Off (True/False). If True, the image will be scaled to Aspect Fill instead of Aspect Fit.
@IBDesignable
class AlignedAspectFitImageView: UIView {
enum HorizontalAlignment: String {
case left, center, right
}
enum VerticalAlignment: String {
case top, center, bottom
}
private var theImageView: UIImageView = {
let v = UIImageView()
return v
}()
@IBInspectable var image: UIImage? {
get { return theImageView.image }
set {
theImageView.image = newValue
setNeedsLayout()
}
}
@IBInspectable var hAlign: String = "center" {
willSet {
// Ensure user enters a valid alignment name while making it lowercase.
if let newAlign = HorizontalAlignment(rawValue: newValue.lowercased()) {
horizontalAlignment = newAlign
}
}
}
@IBInspectable var vAlign: String = "center" {
willSet {
// Ensure user enters a valid alignment name while making it lowercase.
if let newAlign = VerticalAlignment(rawValue: newValue.lowercased()) {
verticalAlignment = newAlign
}
}
}
@IBInspectable var aspectFill: Bool = false {
didSet {
setNeedsLayout()
}
}
var horizontalAlignment: HorizontalAlignment = .center
var verticalAlignment: VerticalAlignment = .center
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
addSubview(theImageView)
}
override func layoutSubviews() {
super.layoutSubviews()
guard let img = theImageView.image else {
return
}
var newRect = bounds
let viewRatio = bounds.size.width / bounds.size.height
let imgRatio = img.size.width / img.size.height
// if view ratio is equal to image ratio, we can fill the frame
if viewRatio == imgRatio {
theImageView.frame = newRect
return
}
// otherwise, calculate the desired frame
var calcMode: Int = 1
if aspectFill {
calcMode = imgRatio > 1.0 ? 1 : 2
} else {
calcMode = imgRatio < 1.0 ? 1 : 2
}
if calcMode == 1 {
// image is taller than wide
let heightFactor = bounds.size.height / img.size.height
let w = img.size.width * heightFactor
newRect.size.width = w
switch horizontalAlignment {
case .center:
newRect.origin.x = (bounds.size.width - w) * 0.5
case .right:
newRect.origin.x = bounds.size.width - w
default: break // left align - no changes needed
}
} else {
// image is wider than tall
let widthFactor = bounds.size.width / img.size.width
let h = img.size.height * widthFactor
newRect.size.height = h
switch verticalAlignment {
case .center:
newRect.origin.y = (bounds.size.height - h) * 0.5
case .bottom:
newRect.origin.y = bounds.size.height - h
default: break // top align - no changes needed
}
}
theImageView.frame = newRect
}
}
Using this image:

Here's how it looks with a 240 x 240 AlignedAspectFitImageView with background color set to yellow (so we can see the frame):

Properties can also be set via code. For example:
override func viewDidLoad() {
super.viewDidLoad()
let testImageView = AlignedAspectFitImageView()
testImageView.image = UIImage(named: "bkg640x360")
testImageView.verticalAlignment = .bottom
view.addSubview(testImageView)
// set frame / constraints / etc
testImageView.frame = CGRect(x: 40, y: 40, width: 240, height: 240)
}
To show the difference between "Aspect Fill" and "Aspect Fit"...
Using this image:

We get this result with Aspect Fill: Off and VAlign: bottom:

and then this result with Aspect Fill: On and HAlign: right:

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With