iOS開發tips-UITableView、UICollectionView行高/尺寸自適應


UITableView

我們都知道UITableView從iOS 8開始實現行高的自適應相對比較簡單,首先必須設置estimatedRowHeight給出預估高度,設置rowHeightUITableViewAutomaticDimension(注意:如果不修改rowHeight默認就是UITableViewAutomaticDimension),對於這兩個參數除了直接修改tableview對應的屬性之外仍然支持使用對應的代理方法設置。最后只要在UITableViewCell中設置contentView的約束即可。由於UITableViewCell的寬度等同於UITableView因此約束的設置事實上只是為了自動計算高度。通常的做法就是設置contentView的top和bottom約束,而后其內部子視圖可以提供intrinsicContentSize(例如UIButtonUILabel默認就已經提供)或者已經有明確的height約束。這樣一來就可以做到子控件確定了自身高度,而contentView子控件又設置了和contentView相關的bottom約束來反向計算出UITableViewCell的實際高度。
下面仍然以前面UITableView文章的自定義Cell舉例,相比之前大量的運算而言Self-Sizing Cells可以說簡化了很多。除了設置estimatedRowHeight外最重要的就是添加相關Autolayout約束。由於頭像高度已經固定,內容高度可以通過固有高度自動計算,而二者的間隔和top、bottom約束已經固定,從而Self-Sizing Cells可以自動計算出Cell的高度。
高度計算約束關系:

Cell布局代碼:

	import UIKit
	import SnapKit
	
	class StatusTableViewCell: UITableViewCell {
	
	    // MARK: - 公共屬性
	    var status:Status! {
	        didSet {
	            self.avatarImageView.image = UIImage(named: status.profileImageUrl)
	            self.userNameLabel.text = status.userName
	            self.mtypeImageView.image = UIImage(named: status.mbtype)
	            self.createdAtLabel.text = status.createdAt
	            self.sourceLabel.text = status.source
	            self.contentLabel.text = status.text
	        }
	    }
	    
	    // MARK: - 生命周期及方法覆蓋
	    override func awakeFromNib() {
	        super.awakeFromNib()
	    }
	    
	    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
	        super.init(style: style, reuseIdentifier: reuseIdentifier)
	        self.setup()
	    }
	    
	    required init?(coder aDecoder: NSCoder) {
	        super.init(coder: aDecoder)
	    }
	
	    override func setSelected(_ selected: Bool, animated: Bool) {
	
	    }
	    
	    // MARK: - 私有方法
	    private func setup() {
	        self.contentView.addSubview(self.avatarImageView)
	        self.contentView.addSubview(self.userNameLabel)
	        self.contentView.addSubview(self.mtypeImageView)
	        self.contentView.addSubview(self.createdAtLabel)
	        self.contentView.addSubview(self.sourceLabel)
	        self.contentView.addSubview(self.contentLabel)
	        
	        self.avatarImageView.snp.makeConstraints { (make) in
	            make.top.left.equalTo(10.0)
	            make.size.equalTo(CGSize(width: 40.0, height: 40.0))
	        }
	        
	        self.userNameLabel.snp.makeConstraints { (make) in
	            make.top.equalTo(self.avatarImageView.snp.top)
	            make.left.equalTo(self.avatarImageView.snp.right).offset(8.0)
	        }
	        
	        self.mtypeImageView.snp.makeConstraints { (make) in
	            make.top.equalTo(self.userNameLabel.snp.top)
	            make.left.equalTo(self.userNameLabel.snp.right).offset(8.0)
	            make.size.equalTo(CGSize(width: 14.0, height: 14.0))
	        }
	        
	        self.createdAtLabel.snp.makeConstraints { (make) in
	            make.left.equalTo(self.userNameLabel.snp.left)
	            make.bottom.equalTo(self.avatarImageView.snp.bottom)
	        }
	        
	        self.sourceLabel.snp.makeConstraints { (make) in
	            make.left.equalTo(self.createdAtLabel.snp.right).offset(10.0)
	            make.bottom.equalTo(self.createdAtLabel.snp.bottom)
	            make.right.lessThanOrEqualTo(-8.0)
	        }
	        
	        self.contentLabel.snp.makeConstraints { (make) in
	            make.top.equalTo(self.avatarImageView.snp.bottom).offset(8.0)
	            make.left.equalTo(self.avatarImageView.snp.left)
	            make.right.equalTo(-8.0)
	            make.bottom.equalTo(-10.0) // 注意此處必須設置,否則contentView無法自適應高度
	        }
	        
	    }
	    
	    // MARK: - 私有屬性
	    private lazy var avatarImageView:UIImageView = {
	        let temp = UIImageView()
	        return temp
	    }()
	    
	    private lazy var mtypeImageView:UIImageView = {
	        let temp = UIImageView()
	        return temp
	    }()
	    
	    private lazy var userNameLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 50.0/255.0, green: 50.0/255.0, blue: 50.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 14.0)
	        return temp
	    }()
	    
	    private lazy var createdAtLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 120.0/255.0, green: 120.0/255.0, blue: 120.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 12.0)
	        return temp
	    }()
	    
	    private lazy var sourceLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 120.0/255.0, green: 120.0/255.0, blue: 120.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 12.0)
	        return temp
	    }()
	    
	    private lazy var contentLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 50.0/255.0, green: 50.0/255.0, blue: 50.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 14.0)
	        temp.numberOfLines = 0
	        return temp
	    }()
	    
	}

最終效果:

無論是UITableView還是后面的UICollectionview,Self-Sizing Cells的概念均是從iOS 8開始提出的。如果是iOS 8之前的版本則需要通過systemLayoutSizeFitting()進行計算或者通過frame直接設置。

UICollectionView

了解了UITableView的Cell行高自適應之后,要理解UICollectionviewCell的Size自適應並不難,因為UICollectionViewCell相比較於UITableViewCell除了要通過AutoLayout確定contentView的高度之外還要確定其寬度,其寬度確定原則和UITableViewCell的高度確定是類似的,只是要通過UICollectionviewCell的contentView子控件自身確定其寬度,然后設置子控件和contentView相關的right約束即可。當然對於UICollectionViewCell自適應尺寸同樣必須設置UICollectionViewFlowLayout的estimatedItemSize屬性(如果使用UICollectionViewFlowLayout)。
下面的的demo演示了類淘寶商品展示的UICollectionview布局,除了通過AutoLayout確定高度外,通過商品圖片的left、right約束和width的設置可以反向推斷出contentView的寬度,通過Self-Sizing Cells就可以最終確定UICollectionviewCell的size。
Cell布局代碼:

	import UIKit
	
	class ProductCollectionViewCell: UICollectionViewCell {
	    
	    // MARK: - 公共屬性
	    var product:Product! {
	        didSet {
	            self.productImageView.image = UIImage(named: product.image)
	            self.contentLabel.text = product.text
	            self.priceLabel.text = "¥\(product.price)"
	            self.salesLabel.text = "\(product.sale)人購買"
	        }
	    }
	    
	    // MARK: - 生命周期及方法覆蓋
	    override init(frame: CGRect) {
	        super.init(frame: frame)
	        self.setup()
	    }
	    
	    required init?(coder aDecoder: NSCoder) {
	        super.init(coder: aDecoder)
	    }
	    
	    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
	        return super.preferredLayoutAttributesFitting(layoutAttributes)
	    }
	    // MARK: - 私有方法
	    private func setup() {
	        self.backgroundColor = UIColor.white
	        self.contentView.addSubview(self.productImageView)
	        self.contentView.addSubview(self.contentLabel)
	        self.contentView.addSubview(self.priceLabel)
	        self.contentView.addSubview(self.salesLabel)
	        
	        let screenWidth = UIScreen.main.bounds.width
	        self.productImageView.snp.makeConstraints { (make) in
	            make.top.left.right.equalTo(0.0)
	            make.height.equalTo(screenWidth*0.5).priority(999)
	            make.width.equalTo((screenWidth-18)*0.5).priority(999) // 此設置可以確定cell寬度,注意盡管降低了默認的優先級僅僅是為了計算中間步驟不至於約束沖突,最終顯示時此約束仍然會生效
	        }
	        
	        self.contentLabel.snp.makeConstraints { (make) in
	            make.top.equalTo(self.productImageView.snp.bottom).offset(4.0)
	            make.left.equalTo(8.0)
	            make.right.equalTo(-8.0)
	            make.height.equalTo(28.0)
	        }
	        
	        self.priceLabel.snp.makeConstraints { (make) in
	            make.top.equalTo(self.contentLabel.snp.bottom).offset(8.0)
	            make.left.equalTo(self.contentLabel.snp.left)
	            make.bottom.equalTo(-8.0) //此設置可以確定cell高度
	        }
	        
	        self.salesLabel.snp.makeConstraints { (make) in
	            make.centerY.equalTo(self.priceLabel.snp.centerY)
	            make.right.equalTo(-8.0)
	        }
	    }
	    
	    // MARK: - 私有屬性
	    private lazy var productImageView:UIImageView = {
	        let temp = UIImageView()
	        temp.contentMode = .scaleAspectFit
	        temp.clipsToBounds = true
	        return temp
	    }()
	    
	    private lazy var contentLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 50.0/255.0, green: 50.0/255.0, blue: 50.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 12.0)
	        temp.numberOfLines = 2
	        return temp
	    }()
	    
	    private lazy var priceLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor.orange
	        temp.font = UIFont.systemFont(ofSize: 14.0)
	        return temp
	    }()
	    
	    private lazy var salesLabel:UILabel = {
	        let temp = UILabel()
	        temp.textColor = UIColor(red: 150.0/255.0, green: 150.0/255.0, blue: 150.0/255.0, alpha: 1.0)
	        temp.font = UIFont.systemFont(ofSize: 12.0)
	        return temp
	    }()
	    
	}

最終效果:

除此之外從iOS 8開始,UICollectionViewCell提供了preferredLayoutAttributesFitting()方法用於提供一些cell屬性修改,當然通過此方法可以重新修改cell的size(包括Self-Sizing Cells自動計算后的size),對於手動計算高度的情況這也方法也提供了一種不用外部設置cell size的而能提供讓UICollectionView確定cell尺寸的方式,但是這並不在Self-Sizing Cells討論之列,因此本文不再深入討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM