在實際業務中,咱們常常遇到一個業務控件,由幾個小控件組合完成。好比用戶頭像組件:有頭像圖片、等級圖片、紅點提示視圖等。爲了提升封裝性和重用性,通常都會自定義一個視圖控件來添加這些小控件。這樣有一個反作用就是增長了一層視圖層級,以下圖所示:git
視圖層級多了一層,在佈局計算時會更加耗時。視圖對象多了一個,內存消耗會更多。那有沒有辦法即保證了控件的封裝性,又能夠減小一層視圖的包裹呢?答案就是它:UILayoutGuide
,用了它以後的效果以下圖:github
減小了一層視圖,可是顯示效果和封裝效果同樣。markdown
重要的事情講三遍:
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
ide
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
佈局,就能夠實現佈局獨立,達到其封裝性。ui
LayoutContainer
使用示例LayoutContainer
子類化示例UserAvatarContainer
就是用戶頭像的封裝控件。內部的子控件,只須要相對self佈局便可。spa
class UserAvatarContainer: LayoutContainer { lazy var avatarImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "lufei.jpg") imageView.contentMode = UIView.ContentMode.scaleAspectFill return imageView }() lazy var vipImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "VIP") return imageView }() override func initializeViews() { super.initializeViews() avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarImageView.layer.masksToBounds = true //須要用owningView當作父視圖 owningView?.addSubview(avatarImageView) avatarImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true avatarImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true avatarImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true avatarImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true vipImageView.translatesAutoresizingMaskIntoConstraints = false owningView?.addSubview(vipImageView) vipImageView.widthAnchor.constraint(equalToConstant: 30).isActive = true vipImageView.heightAnchor.constraint(equalToConstant: 30).isActive = true vipImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true vipImageView.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 = true avatarContainer.heightAnchor.constraint(equalToConstant: 100).isActive = true avatarContainer.widthAnchor.constraint(equalToConstant: 100).isActive = true avatarContainer.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true } } 複製代碼
LayoutContainer
的子視圖須要在initializeViews
方法裏面進行初始化LayoutContainer
的子視圖的佈局在layoutSubviews
進行調整LayoutContainer
僅能被addLayoutGuide一次,不容許被removeLayoutGuide。否則initializeViews會被調用屢次,致使重複建立視圖!