在实际业务中,我们经常遇到一个业务控件,由几个小控件组合完成。比如用户头像组件:有头像图片、等级图片、红点提示视图等。为了提高封装性和重用性,一般都会自定义一个视图控件来添加这些小控件。这样有一个副作用就是增加了一层视图层级,如下图所示:

视图层级多了一层,在布局计算时会更加耗时。视图对象多了一个,内存消耗会更多。那有没有办法即保证了控件的封装性,又可以减少一层视图的包裹呢?答案就是它:UILayoutGuide,用了它之后的效果如下图:

减少了一层视图,但是显示效果和封装效果一样。
重要的事情讲三遍:
本方案仅提供一个有趣的思路,并不保证其性能!
本方案仅提供一个有趣的思路,并不保证其性能!
本方案仅提供一个有趣的思路,并不保证其性能!
LayoutContainer
UILayoutGuide是iOS9引入的,就是为了解决需要有占位视图的场景。它不会出现在视图层级里面,也不会有视图对象,只会在布局引擎起作用。下面是官方注释可以细细品味:
UILayoutGuides will not show up in the view hierarchy, but may be used as items in an NSLayoutConstraint and represent a rectangle in the layout engine.
所以,创建继承UILayoutGuide的LayoutContainer类,当做子控件的布局容器。子控件只需要相对LayoutContainer布局,就可以实现布局独立,达到其封装性。
LayoutContainer使用示例
LayoutContainer子类化示例
UserAvatarContainer就是用户头像的封装控件。内部的子控件,只需要相对self布局即可。
class UserAvatarContainer: LayoutContainer {lazy var avatarImageView: UIImageView = {let imageView = UIImageView()imageView.image = UIImage(named: "lufei.jpg")imageView.contentMode = UIView.ContentMode.scaleAspectFillreturn imageView}()lazy var vipImageView: UIImageView = {let imageView = UIImageView()imageView.image = UIImage(named: "VIP")return imageView}()override func initializeViews() {super.initializeViews()avatarImageView.translatesAutoresizingMaskIntoConstraints = falseavatarImageView.layer.masksToBounds = true//需要用owningView当做父视图owningView?.addSubview(avatarImageView)avatarImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = trueavatarImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = trueavatarImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = trueavatarImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = truevipImageView.translatesAutoresizingMaskIntoConstraints = falseowningView?.addSubview(vipImageView)vipImageView.widthAnchor.constraint(equalToConstant: 30).isActive = truevipImageView.heightAnchor.constraint(equalToConstant: 30).isActive = truevipImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = truevipImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true}override func layoutSubviews() {super.layoutSubviews()//如果是通过约束布局,这里可以根据layoutFrame(不能使用frame、bounds、center)进行布局调整avatarImageView.layer.cornerRadius = layoutFrame.size.height/2}}
添加LayoutContainer
class ListCell: UITableViewCell {lazy var avatarContainer: UserAvatarContainer = {UserAvatarContainer()}()override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {super.init(style: style, reuseIdentifier: reuseIdentifier)contentView.addLayoutGuide(avatarContainer)avatarContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12).isActive = trueavatarContainer.heightAnchor.constraint(equalToConstant: 100).isActive = trueavatarContainer.widthAnchor.constraint(equalToConstant: 100).isActive = trueavatarContainer.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true}}
注意事项
LayoutContainer的子视图需要在initializeViews方法里面进行初始化LayoutContainer的子视图的布局在layoutSubviews进行调整LayoutContainer仅能被addLayoutGuide一次,不允许被removeLayoutGuide。不然initializeViews会被调用多次,导致重复创建视图!
iOS学习栈(将持续更新)上
结交人脉
最后推荐个我的iOS交流群:789143298 ‘有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
收录:原文地址

