UITableView and UICollectionView Line Height/Size Adaptation

Keywords: iOS Attribute

UITableView

We all know that UITableView is relatively simple to implement line height adaptation from iOS 8. First, we must set estimated RowHeight to give the estimated height and rowHeight to UITableView AutomaticDimension (note: UITableView AutomaticDimension is the default if rowHeight is not modified). For these two parameters, besides directly modifying the corresponding attributes of tableview, we still need to set rowHeight as UITableView AutomaticDimension. Support the use of the corresponding proxy method settings. Finally, just set the contentView constraint in UITableViewCell. Because the width of the UITableViewCell is the same as that of the UITableView, the constraints are actually set to automatically calculate the height. The usual approach is to set top and bottom constraints for contentView, and then its internal subviews can provide intrinsic Content Size (such as UIButton UILabel already provided by default) or have explicit height constraints. In this way, the sub-control can determine its own height, and the contentView sub-control sets the bottom constraints related to contentView to calculate the actual height of UITableViewCell in reverse.
The following is still the previous one. UITableView For example, the self-Sizing Cells in this article can be said to simplify a lot compared with a lot of previous operations. In addition to setting estimated RowHeight, the most important thing is to add relevant Autolayout constraints. Since the head height has been fixed, the content height can be automatically calculated by the inherent height, and the distance between the two and the top and bottom constraints have been fixed, so Self-Sizing Cells can automatically calculate the Cell height.
Height Computation Constraints:

Cell layout code:

    import UIKit
    import SnapKit
    
    class StatusTableViewCell: UITableViewCell {
    
        // MARK: - Common attributes
        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: - Life Cycle and Method Coverage
        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 methods
        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) // Note that you must set it here, otherwise contentView cannot adjust height
            }
            
        }
        
        // MARK: - Private attributes
        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
        }()
        
    }

Ultimately:

Whether UITableView or UICollection View later, the concept of Self-Sizing Cells was proposed from iOS 8. If it's a previous version of iOS 8, it needs to be computed by system LayoutSizeFitting () or set directly by frame.

UICollectionView

After understanding the row height adaptation of UITableView, it is not difficult to understand the Size adaptation of UICollectionViewCell, because UICollectionViewCell has to determine its width in comparison with UITableViewCell. The principle of determining the width of UITableViewCell is similar to that of determining the height of UITableViewCell, but only through UICollectionViewCell. The contentView child control determines its width by itself, and then sets the right constraints related to the child control and contentView. Of course, the estimated ItemSize property of UICollectionViewFlowLayout must also be set for UICollectionViewCell adaptive Size (if UICollectionViewFlowLayout is used).
The demo below demonstrates the UICollection view layout of Taobao-like merchandise display. In addition to the height determined by AutoLayout, the width of contentView can be inferred from the left, right constraints and width settings of merchandise pictures, and the size of UICollection viewCell can be finally determined by Self-Sizing Cells.
Cell layout code:

    import UIKit
    
    class ProductCollectionViewCell: UICollectionViewCell {
        
        // MARK: - Common attributes
        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)People buying"
            }
        }
        
        // MARK: - Life Cycle and Method Coverage
        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 methods
        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) // This setting determines the cell width. Note that although the default priority is lowered only to calculate intermediate steps without constraint conflicts, the constraint will still take effect when it is finally displayed.
            }
            
            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) //This setting determines cell height
            }
            
            self.salesLabel.snp.makeConstraints { (make) in
                make.centerY.equalTo(self.priceLabel.snp.centerY)
                make.right.equalTo(-8.0)
            }
        }
        
        // MARK: - Private attributes
        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
        }()
        
    }

Ultimately:

Beginning with iOS 8, UICollectionView Cell provides preferred Layout Attributes Fitting () method to provide some cell attribute modifications. Of course, this method can modify the size of cell (including Self-Sizing Cells automatically calculated size) and also provide a UI for manual height calculation without setting cell size externally. CollectionView determines the cell size in a way that is not discussed in SelfSizing Cells, so this article will not go into depth.


Posted by rbenfield on Tue, 26 Mar 2019 02:06:29 -0700