如何優雅地減小視圖層級?

在實際業務中,咱們常常遇到一個業務控件,由幾個小控件組合完成。好比用戶頭像組件:有頭像圖片、等級圖片、紅點提示視圖等。爲了提升封裝性和重用性,通常都會自定義一個視圖控件來添加這些小控件。這樣有一個反作用就是增長了一層視圖層級,以下圖所示:git

視圖層級多了一層,在佈局計算時會更加耗時。視圖對象多了一個,內存消耗會更多。那有沒有辦法即保證了控件的封裝性,又能夠減小一層視圖的包裹呢?答案就是它:UILayoutGuide,用了它以後的效果以下圖:github

減小了一層視圖,可是顯示效果和封裝效果同樣。ide

重要的事情講三遍:
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
本方案僅提供一個有趣的思路,並不保證其性能!
佈局

LayoutContainer

LayoutContainerGithub地址性能

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

因此,建立繼承UILayoutGuideLayoutContainer類,當作子控件的佈局容器。子控件只須要相對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會被調用屢次,致使重複建立視圖!

Github地址

LayoutContainerGithub地址對象

相關文章
相關標籤/搜索