Ah - I've seen this before.
I don't know why, but using a SF Symbol in a UIImageView
will change the image view's height!!!
You can easily confirm this:
- add a view controller in Storyboard
- add a
UIImageView
- set content mode to Center (doesn't really matter)
- constrain Width: 60 Height: 60 centerX and centerY
- Check the Size Inspector - it will show
60 x 60
Now:
- use the drop-down to set the image to "globe"
- Check the Size Inspector - it will show
60 x 59
Now:
- use the drop-down to set the image to "envelope"
- Check the Size Inspector - it will show
60 x 56.5
No apparent constraint conflicts... no apparent reason for this.
As far as I can tell (unless this changes with iOS 14), we need to embed the image view in a UIView
... constrain it centerX & centerY ... set the background color and corner-radius properties of that view to make it round.
Edit -- just as an exercise...
Two horizontal stack views:
- Alignment: Fill
- Distribution: FillEqually
- Spacing: 0
- each constrained to
Width: 300
and Height: 100
- each filled with 3
UIImageView
s
Gives us 3 100 x 100
square image views. For the second stack, set each image view to a system image, with UIImage.SymbolConfiguration(pointSize: 60, weight: .regular)
. Should still give us 3 100 x 100
squares.
Instead, this is the result:
Here's the code to produce that:
class SystemImageTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stack1 = UIStackView()
stack1.axis = .horizontal
stack1.alignment = .fill
stack1.distribution = .fillEqually
let stack2 = UIStackView()
stack2.axis = .horizontal
stack2.alignment = .fill
stack2.distribution = .fillEqually
// add stack views to the view
[stack1, stack2].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
stack1.widthAnchor.constraint(equalToConstant: 300.0),
stack1.heightAnchor.constraint(equalToConstant: 100.0),
stack1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack2.topAnchor.constraint(equalTo: stack1.bottomAnchor, constant: 20.0),
stack2.widthAnchor.constraint(equalToConstant: 300.0),
stack2.heightAnchor.constraint(equalToConstant: 100.0),
stack2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
let colors: [UIColor] = [
UIColor(red: 0.75, green: 0.00, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
]
for c in colors {
let v = UIImageView()
v.backgroundColor = c
stack1.addArrangedSubview(v)
}
let configuration = UIImage.SymbolConfiguration(pointSize: 60, weight: .regular)
let names: [String] = [
"globe",
"bandage",
"envelope",
]
for (c, n) in zip(colors, names) {
let v = UIImageView()
v.backgroundColor = c
v.contentMode = .center
v.tintColor = .white
let img = UIImage(systemName: n, withConfiguration: configuration)
v.image = img
stack2.addArrangedSubview(v)
}
}
}
Edit 2 - just for the heck of it...
Implementing a couple classes for a SystemImageView
and CircularSystemImageView
:
class SystemImageView: UIView {
override var contentMode: UIView.ContentMode {
didSet {
imageView.contentMode = contentMode
}
}
override var tintColor: UIColor! {
didSet {
imageView.tintColor = tintColor
}
}
var image: UIImage = UIImage() {
didSet {
imageView.image = image
}
}
let imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
class CircularSystemImageView: SystemImageView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.masksToBounds = true
self.clipsToBounds = true
self.layer.cornerRadius = bounds.size.width * 0.5
}
}
class SystemImageTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stack1 = UIStackView()
stack1.axis = .horizontal
stack1.alignment = .fill
stack1.distribution = .fillEqually
let stack2 = UIStackView()
stack2.axis = .horizontal
stack2.alignment = .fill
stack2.distribution = .fillEqually
let stack3 = UIStackView()
stack3.axis = .horizontal
stack3.alignment = .fill
stack3.distribution = .fillEqually
let stack4 = UIStackView()
stack4.axis = .horizontal
stack4.alignment = .fill
stack4.distribution = .fillEqually
// add stack views to the view
[stack1, stack2, stack3, stack4].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
stack1.widthAnchor.constraint(equalToConstant: 300.0),
stack1.heightAnchor.constraint(equalToConstant: 100.0),
stack1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack2.topAnchor.constraint(equalTo: stack1.bottomAnchor, constant: 20.0),
stack2.widthAnchor.constraint(equalToConstant: 300.0),
stack2.heightAnchor.constraint(equalToConstant: 100.0),
stack2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack3.topAnchor.constraint(equalTo: stack2.bottomAnchor, constant: 20.0),
stack3.widthAnchor.constraint(equalToConstant: 300.0),
stack3.heightAnchor.constraint(equalToConstant: 100.0),
stack3.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack4.topAnchor.constraint(equalTo: stack3.bottomAnchor, constant: 20.0),
stack4.widthAnchor.constraint(equalToConstant: 300.0),
stack4.heightAnchor.constraint(equalToConstant: 100.0),
stack4.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
let colors: [UIColor] = [
UIColor(red: 0.75, green: 0.00, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.75, blue: 0.00, alpha: 1.0),
UIColor(red: 0.00, green: 0.00, blue: 1.00, alpha: 1.0),
]
for c in colors {
let v = UIImageView()
v.backgroundColor = c
stack1.addArrangedSubview(v)
}
let configuration = UIImage.SymbolConfiguration(pointSize: 60, weight: .regular)
let names: [String] = [
"globe",
"bandage",
"envelope",
]
for (c, n) in zip(colors, names) {
let v = UIImageView()
v.backgroundColor = c
v.contentMode = .center
v.tintColor = .white
if let img = UIImage(systemName: n, withConfiguration: configuration) {
v.image = img
}
stack2.addArrangedSubview(v)
}
for (c, n) in zip(colors, names) {
let v = SystemImageView()
v.backgroundColor = c
v.contentMode = .center
v.tintColor = .white
if let img = UIImage(systemName: n, withConfiguration: configuration) {
v.image = img
}
stack3.addArrangedSubview(v)
}
for (c, n) in zip(colors, names) {
let v = CircularSystemImageView()
v.backgroundColor = c
v.contentMode = .center
v.tintColor = .white
if let img = UIImage(systemName: n, withConfiguration: configuration) {
v.image = img
}
stack4.addArrangedSubview(v)
}
}
}
Results:
or, with a little more practical let configuration = UIImage.SymbolConfiguration(pointSize: 40, weight: .regular)
to show the sizes are not being determined by the SF Symbol size: