How to override intrinsictContentSize for flexible height and fixed width view?

I am trying to display custom views in a tableView and let iOS calculate the height of each row using UITableViewAutomaticDimension. Unfortunately, my cell does not have the proper size (poor height).

My custom view is defined as the following (I do not use AutoLayout for this part for many reasons, and I do not want to use it):

class MyView : UIView {
    var label: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

    func setupView() {
        label = UILabel()
        label.numberOfLines = 0
        addSubview(label)
    }

    func setText(text: String) {
        label.text = text
        setNeedsLayout()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        label.frame = bounds
        invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: CGSize {
        guard let text = label.text else { return .zero }
        let width = bounds.width
        let height = text.heightWithConstrainedWidth(width: width, font: label.font)
        return CGSize(width: width, height: height)
    }
}

Now I wrapped this user view in UITableViewCellto use it in UITableView. Here I use AutoLayout:

class MyCell : UITableViewCell {
    var myView: MyView!

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupCell()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupCell()
    }

    func setupCell() {
        myView = MyView()
        contentView.addSubview(myView)
        myView.snp.makeConstraints { make in
            make.edges.equalTo(contentView)
        }
    }
}

Now, using the following viewController, I show a cell with large text to see if its height is automatically calculated:

class ViewController: UIViewController {
    var tableView: UITableView!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        tableView = UITableView()
        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalTo(view)
        }
        tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 30
        tableView.dataSource = self
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
        cell.myView.setText(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur")
        return cell
    }
}

, 21. , , intrinsicContentSize MyView, 143. : enter image description here

, ( ): enter image description here

, intrinsictContentSize of MyView , , , UIView, . , , , invalidateIntrinsicContentSize() MyView layoutSubviews().

, ?

+4
3

, intrinsicContentSize , . , UIView:

, . , , .

MyView bounds , intrinsicContentSize, layoutSubviews(). intrinsicContentSize, , .

1: sizeThatFits(_:)

-, MyView :

//  MyView.swift

func setupView() {
    label = UILabel()
    label.numberOfLines = 0
    label.autoresizingMask = [.flexibleWidth] // New
    addSubview(label)
}

override func layoutSubviews() {
    super.layoutSubviews()
    label.sizeToFit() // New
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
    return label.sizeThatFits(size) // New
}

MyView label, (- .flexibleWidth) ( numberOfLines 0). MyView , sizeThatFits(_:).

-, , UITableView . , UITableView systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority) , sizeThatFits(_:) . (. WWDC 2014 Session 226.)

, , - . UILayoutPriorityRequired. , ( , ) .

myView:

//  MyCell.swift

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
    return myView.sizeThatFits(targetSize)
}

.

2: intrinsicContentSize

MyView , . :

//  MyView.swift

override func layoutSubviews() {
    super.layoutSubviews()
    label.frame = bounds
}

- MyView. ( ), - :

//  MyView.swift

override var intrinsicContentSize: CGSize {
    return CGSize(width: UIViewNoIntrinsicMetric, height: label.intrinsicContentSize.height)
}

, . , , UILabel , , .

preferredMaxLayoutWidth . UILabel :

// , -intrinsicContentSize

, - , preferredMaxLayoutWidth ​​ . intrinsicContentSize , , , :

//  MyCell.swift

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
    myView.label.preferredMaxLayoutWidth = targetSize.width
    myView.invalidateIntrinsicContentSize()

    return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
}

MyView , MyView , preferredMaxLayoutWidth MyView .

, MyView intrinsicContentSize .

+2

, .

, intrinsicContentSize, , . layoutSubviews. , invalidateIntrinsicContentSize! , .

layoutSubviews. , , , . , , . , , :

override var intrinsicContentSize: CGSize {
    guard
        let text = label.text,
        let parent = superview
    else { return .zero }
    let width = parent.bounds.width
    let height = text.heightWithConstrainedWidth(width: width, font: label.font)
    return CGSize(width: width, height: height)
}

, MyView . , // MyView Content View, Content View MyView. , MyView.

+1

viewDidAppear:

DispatchQueue.main.async { 
    self.tableView.reloadData()
}

, .

0
source

Source: https://habr.com/ru/post/1663462/


All Articles