自定义UIView中updateConstraints获取frame尺寸及布局问题咨询
关于自定义UIView布局的两个问题解析
我来帮你梳理清楚这两个布局问题,先从自动布局的最佳实践说起:
1. 自动布局:updateConstraints与layoutSubviews的正确用法
你现在在layoutSubviews里调用updateConstraints的做法其实不太合适,而且用self.frame.height来设置约束常量是有风险的:
- 不要手动在
layoutSubviews里调用updateConstraints:系统会在视图尺寸变化、约束需要更新的时机自动触发updateConstraints,手动调用可能导致约束反复计算,既影响性能还可能引发布局循环。 updateConstraints阶段不要依赖frame值:这个阶段视图的最终布局还没确定,self.frame.height拿到的可能是旧尺寸或者临时值,会导致约束计算错误。
正确的做法是用约束比例来替代固定的frame值,比如你想让logoImage高度占父视图的一半,直接用高度锚点的比例约束就行。另外,约束只需要设置一次,避免重复添加:
class CustomView: UIView { private let logoImage = UIImageView() private let label = UILabel() private var didSetupConstraints = false // 标记是否已经设置过约束 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { super.init(coder: coder) setupSubviews() } private func setupSubviews() { // 关闭子视图的自动转换约束(必须做,否则Auto Layout会失效) logoImage.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false addSubview(logoImage) addSubview(label) // 这里可以提前设置子视图属性,比如logoImage.image = UIImage(named: "logo") } override func updateConstraints() { super.updateConstraints() guard !didSetupConstraints else { return } // 避免重复添加约束 NSLayoutConstraint.activate([ // logoImage占父视图上半部分 logoImage.topAnchor.constraint(equalTo: topAnchor), logoImage.leftAnchor.constraint(equalTo: leftAnchor), logoImage.rightAnchor.constraint(equalTo: rightAnchor), logoImage.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5), // label在logoImage下方,左右撑满 label.topAnchor.constraint(equalTo: logoImage.bottomAnchor, constant: 12), label.leftAnchor.constraint(equalTo: leftAnchor), label.rightAnchor.constraint(equalTo: rightAnchor) ]) didSetupConstraints = true } }
2. 手动设置frame无效果的原因
如果尝试在layoutSubviews里直接用label.frame = CGRect(...)却看不到视图,大概率是因为你同时开启了Auto Layout:
- 一旦给子视图添加了约束,系统的Auto Layout引擎会自动计算并覆盖子视图的frame值,你手动设置的frame会被直接忽略。
- 如果想纯手动布局,需要满足两个条件:
- 不要给子视图添加任何约束;
- 确保子视图的
translatesAutoresizingMaskIntoConstraints保持默认的true(不要设为false)。
纯手动布局的示例代码:
class CustomView: UIView { private let logoImage = UIImageView() private let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { super.init(coder: coder) setupSubviews() } private func setupSubviews() { // 不要设置translatesAutoresizingMaskIntoConstraints为false,保持默认值 addSubview(logoImage) addSubview(label) } override func layoutSubviews() { super.layoutSubviews() // 用bounds而不是frame,bounds是视图自身的坐标系统,更可靠 let logoHeight = bounds.height / 2 logoImage.frame = CGRect(x: 0, y: 0, width: bounds.width, height: logoHeight) label.frame = CGRect(x: 0, y: logoImage.frame.maxY + 12, width: bounds.width, height: bounds.height - logoHeight - 12) } }
总结
- 用Auto Layout的话,优先用约束表达尺寸关系,不要依赖frame值,也不要手动触发
updateConstraints; - 纯手动布局的话,必须彻底关闭Auto Layout(不添加任何约束),在
layoutSubviews里用bounds计算子视图尺寸。
内容的提问来源于stack exchange,提问作者Emre Önder
相关产品推荐
相关产品推荐

