在實際業務中,咱們常常遇到一個業務控件,由幾個小控件組合完成。好比用戶頭像組件:有頭像圖片、等級圖片、紅點提示視圖等。爲了提升封裝性和重用性,通常都會自定義一個視圖控件來添加這些小控件。這樣有一個反作用就是增長了一層視圖層級,以下圖所示:git
視圖層級多了一層,在佈局計算時會更加耗時。視圖對象多了一個,內存消耗會更多。那有沒有辦法即保證了控件的封裝性,又能夠減小一層視圖的包裹呢?答案就是它:UILayoutGuide
,用了它以後的效果以下圖:github
減小了一層視圖,可是顯示效果和封裝效果同樣。ide
重要的事情講三遍:
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
佈局
LayoutContainer
UILayoutGuide
是iOS9引入的,就是爲了解決須要有佔位視圖的場景。它不會出如今視圖層級裏面,也不會有視圖對象,只會在佈局引擎起做用。下面是官方註釋能夠細細品味:ui
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.spa
因此,建立繼承UILayoutGuide
的LayoutContainer
類,當作子控件的佈局容器。子控件只須要相對LayoutContainer
佈局,就能夠實現佈局獨立,達到其封裝性。code
LayoutContainer
使用示例LayoutContainer
子類化示例UserAvatarContainer
就是用戶頭像的封裝控件。內部的子控件,只須要相對self佈局便可。cdn
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會被調用屢次,致使重複建立視圖!