I've got a custom UIView subclass where I'm adding a couple of subviews programmatically. I'm setting up all the layout code with AutoLayout.
The problem comes when I override my UIView's layoutSubviews()
method to try to get my subview frames, as they always return .zero
as their frame.
However, If I go to the View Hiearchy Debugger in XCode, all the frames are calculated and shown correctly.
Here is the console output I log inside the layoutSubviews()
method:
layoutSubviews(): <PrologueTextView: 0x7fa50961abc0; frame = (19.75 -19.5; 335.5 168); clipsToBounds = YES; autoresize = RM+BM; layer = <CAShapeLayer: 0x60000022be20>>
layoutSubviews(): <Label: 0x7fa509424c60; baseClass = UILabel; frame = (0 0; 0 0); text = 'This is'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d160>>
layoutSubviews(): <Label: 0x7fa509424f60; baseClass = UILabel; frame = (0 0; 0 0); text = 'some sample'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d2a0>>
layoutSubviews(): <Label: 0x7fa509425260; baseClass = UILabel; frame = (0 0; 0 0); text = 'text for you'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400028d3e0>>
And here is my UIView subclass relevant code:
internal class PrologueTextView: UIView {
internal var labels: [UILabel] = []
internal let container: UIVisualEffectView = UIVisualEffectView()
// region #Properties
internal var shapeLayer: CAShapeLayer? {
return self.layer as? CAShapeLayer
}
internal override class var layerClass: AnyClass {
return CAShapeLayer.self
}
// endregion
// region #Initializers
internal override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
internal required init?(coder: NSCoder) {
super.init(coder: coder)
self.setup()
}
// endregion
// region #UIView lifecycle
internal override func layoutSubviews() {
super.layoutSubviews()
let mask: UIBezierPath = UIBezierPath()
for label in self.labels {
let roundedCorners = self.roundedCorners(for: label)
let maskBezierPath = UIBezierPath(roundedRect: label.frame, byRoundingCorners: roundedCorners, cornerRadius: 4.0)
mask.append(maskBezierPath)
}
self.shapeLayer?.path = mask.cgPath
print("layoutSubviews(): \(self)")
print("layoutSubviews(): \(labels[0])")
print("layoutSubviews(): \(labels[1])")
print("layoutSubviews(): \(labels[2])")
}
// endregion
// region #Helper methods
private func setup() {
self.setupSubviews()
self.setupSubviewsAnchors()
}
private func setupSubviews() {
self.container.effect = UIBlurEffect(style: .light)
self.container.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.container)
let someSampleText = "This is\nsome sample\ntext for you"
for paragraph in someSampleText.components(separatedBy: "\n") {
let label = UILabel()
label.text = paragraph
label.translatesAutoresizingMaskIntoConstraints = false
self.labels.append(label)
self.container.contentView.addSubview(label)
}
}
private func setupSubviewsAnchors() {
NSLayoutConstraint.activate([
self.container.topAnchor.constraint(equalTo: self.topAnchor),
self.container.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.container.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.container.trailingAnchor.constraint(equalTo: self.trailingAnchor)
])
for (index, label) in self.labels.enumerated() {
let offset = 16.0 * CGFloat(index)
if index == 0 {
label.topAnchor.constraint(equalTo: self.container.contentView.topAnchor).isActive = true
} else {
let prev = self.labels[index - 1]
label.topAnchor.constraint(equalTo: prev.bottomAnchor).isActive = true
if index == self.labels.count - 1 {
label.bottomAnchor.constraint(equalTo: self.container.contentView.bottomAnchor).isActive = true
}
}
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: self.container.leadingAnchor, constant: offset),
label.trailingAnchor.constraint(lessThanOrEqualTo: self.container.trailingAnchor)])
}
}
private func roundedCorners(for label: Label) -> UIRectCorner {
switch label {
case self.labels.first:
return [.topLeft, .topRight, .bottomRight]
case self.labels.last:
return [.topRight, .bottomLeft, .bottomRight]
default:
return [.topRight, .bottomLeft]
}
}
// endregion
}
So, is there any UIView method that gets called after AutoLayout has computed and set the frames for the view and it's subviews?
labels
are populated into your view so I'm not sure what they consist of or how they're initially configured (frame wise). – Craig