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
: