Swift中声明UIStackView属性时为何需要使用lazy var?

以下是自定义UITableViewCell的实现代码:
class SummaryLabelTVC: UITableViewCell { static let identifier = "SummaryLabelTVC" var summaryIcon = UIImageView().then { $0.image = UIImage(systemName: "calender") $0.tintColor = UIColor.white } var label = UILabel().then { $0.font = .systemFont(ofSize: 13) $0.textColor() } lazy var stackView = UIStackView().then { $0.axis = .horizontal $0.alignment = .leading $0.addArrangedSubview(summaryIcon) $0.addArrangedSubview(label) } private func configureUI() { addSubview(stackView) addSubview(label) } }
上述实现中summaryIcon、label均用普通var声明,仅stackView用lazy var声明,将stackView的lazy关键字移除改为普通var后,就出现截图中的异常效果。
核心原因
两类属性声明的核心差异是初始化时机不同:
- 普通
var存储属性:在类实例两段式初始化的第一阶段完成赋值,赋值时self尚未完成构造,此时不能在属性的默认初始化逻辑中引用同实例的其他属性、调用和self相关的方法,否则会出现初始化顺序混乱、拿到未初始化的无效值。
代码里的summaryIcon和label初始化逻辑只修改自身属性,没有引用其他实例属性,所以用普通var完全没问题。 lazy var存储属性:第一次被代码访问时才会执行初始化逻辑,初始化时类实例已经完成全部构造,self状态完整可用,可以正常引用实例上的其他属性、调用实例方法。
代码里的stackView初始化时需要调用addArrangedSubview把同实例的summaryIcon、label加到自身上,必须依赖完整可用的self,所以直接用普通var会因为初始化阶段self未完成构造,无法正确获取两个子视图的有效实例,最终出现布局异常。
额外代码问题
代码里还有两个和lazy关键字无关、但会直接导致布局错乱的问题:
- UITableViewCell的所有自定义子视图必须添加到
contentView上,不能直接调用addSubview加到cell根视图,否则会出现滑动时布局错位、手势交互异常的问题。 configureUI方法里在添加stackView之后,又单独执行了addSubview(label):UIKit规定同一个视图同时只能有一个父视图,被addSubview到新父视图时会自动从原父视图移除。这行代码会直接把label从stackView的视图层级里移出,单独挂在cell根视图上,导致stackView里只剩icon,布局完全错乱,这行多余代码必须删除。
另外代码还有两处语法/拼写错误:
- 系统日历图标名称拼写错误,正确是
calendar,写的calender会导致图片加载失败 label初始化里的$0.textColor()是错误的方法调用,textColor是属性,需要直接赋值,比如$0.textColor = .label
修正参考代码
class SummaryLabelTVC: UITableViewCell { static let identifier = "SummaryLabelTVC" var summaryIcon = UIImageView().then { $0.image = UIImage(systemName: "calendar") $0.tintColor = .white } var label = UILabel().then { $0.font = .systemFont(ofSize: 13) $0.textColor = .label } lazy var stackView = UIStackView().then { $0.axis = .horizontal $0.alignment = .leading $0.spacing = 8 $0.addArrangedSubview(summaryIcon) $0.addArrangedSubview(label) } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) configureUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func configureUI() { contentView.addSubview(stackView) // 按需设置布局约束,示例为SnapKit写法 stackView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)) } } }
内容的提问来源于stack exchange,提问作者sosojin
相关产品推荐
相关产品推荐

