(I'm posting this question as S.O. ("Answer your own question") format to make this recipe easy to find, and to get any other helpful perspectives).
Question: What is a convenient way to set a specific content size UIViews?
Use case driving the question:
I'm working with an app with many UIStackView
instances that display custom icons next to each other.
The stack view items are often UILabel
other times, simply UIView
.
Apple Docs say UILabel
is a type that sets its own intrinsic content size. And in many case that works out okay. However, for certain UILabel
, instances, the intrinsic content size is not set optimally, which can be challenging for getting UIStackView
to layout precisely.
In other cases, I create a UIView
where it is not optimal to subclass each UIView
I create, but rather render a CALayer
into the view after the view's creation. Thus the UIView
cannot set its intrinsic size properly. Thus, when I use that as an item in UIStackView
, it sees the content CGSize as (0, 0), regardless of frame setting, and so UIStackView
doesn't handle it properly even with spacing and custom spacing options.
Since I know the size of the layer whose graphics I'm rendering in advance, it would be helpful to be able to construct the UIView
with a predetermined size, similarly to the way people generally construct UIView(frame: CGRectMake(x, y, w, h)
, but in a way UIStackView
recognizes (e.g. intrinsic content size).
The OP and I briefly discussed this design and I pointed out some potential sources of ambiguity:
size
and a frame
and that frame
could have a different size (which is potentially useful but more likely a source of errors in my opinion). I suggested it should take an origin
and a size
..zero
.Having thought about it some more, here’s a few more things I think could be improved:
frame
) initialiser saves the initial size, there isn’t really much value in having the convenience initialiser take an origin. Similarly, anyone wanting to specify a zero size could use init()
or specify a frame so the convenience initialiser could require a size
without any loss of features.invalidateIntrinsicContentSize()
.The OP is obviously happy with their version and invited me to create my own answer. Here’s a version which addresses my comments:
class IntrinsicContentSizeView : UIView {
var size : CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override init(frame: CGRect) {
size = frame.size
super.init(frame: frame)
}
convenience init(size: CGSize) {
self.init(frame: CGRect(origin: .zero, size: size))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize { size }
}