2

I'm struggling with UITableView. As you can see in this video in third section of table view third cell isn't display correctly. That happens when I dequeue my cell like that:

let cell = tableView.dequeueReusableCell(withIdentifier: MultipleSelectAnswerSurveyTableViewCellIdentifier, for: indexPath) as! MultipleSelectAnswerSurveyTableViewCell
cell.setup(answer: question.answers?[indexPath.row].value ?? "", isSelected: false, style: style, isLastInSection: indexPath.row == (question.answers?.count ?? 1) - 1)
return cell

Cell's setup() method:

func setup(answer: String, isSelected: Bool, style: Style, isLastInSection: Bool) {
    self.isLastInSection = isLastInSection
    selectionStyle = .none
    backgroundColor = style.survey.singleSelectAnswerTableViewCell.backgroundColor
    answerLabel.textColor = style.survey.singleSelectAnswerTableViewCell.answerLabelColor
    answerLabel.font = style.survey.singleSelectAnswerTableViewCell.answerLabelFont
    answerLabel.text = answer
    addSubview(answerLabel)
    addSubview(selectionIndicator)
    answerLabel.snp.makeConstraints { make in
        make.left.equalTo(8)
        make.centerY.equalTo(selectionIndicator.snp.centerY)
        make.top.equalTo(8)
        make.bottom.equalTo(-8)
        make.right.equalTo(selectionIndicator.snp.left).offset(-8)
    }
    selectionIndicator.snp.makeConstraints { make in
        make.right.equalTo(-8)
        make.top.greaterThanOrEqualTo(8)
        make.bottom.lessThanOrEqualTo(-8)
        make.width.height.equalTo(26)
    }
}

self.isLastInSection variable is used inside layoutSubviews():

override func layoutSubviews() {
    super.layoutSubviews()
    if isLastInSection {
        roundCorners(corners: [.bottomLeft, .bottomRight], radius: 16.0)
    }
    contentView.layoutIfNeeded()
}

And finally roundCorners():

extension UIView {
    func roundCorners(corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask
    }
}

When I dequeue cell with isLastInSection set to false cell is being displayed as expected (related video). So I think the problem is in life cycle of the cell and when the layoutSubview() is being called. I tried many solutions for similar problem found in different threads but none of them helped me. tableView(_:heightForRowAt:) causes the third cell to display correctly, but the first one has rounded bottom corners. Also all of them are fixed in height and that cannot happen. But what is really weird: when I print the isLastInSection during dequeueing cell which is unexpectedly rounded debugger returns me false:

(lldb) po indexPath.row == (question.answers?.count ?? 1) - 1 false

enter image description here

As you can see in Debug View Hierarchy view text exists so that's why I 've defined the problem as hiding part of content.

Debug View hierarchy

kkiermasz
  • 464
  • 2
  • 15
  • 2
    You dequeue cell and each time you add subviews etc. You don't check if it's already there which will happen in case of recycled cell. That probably breaks constraints or smth. Same problem with rounding - you set rounded corners, but you never revert this behavior if reused cell should not be rounded. Try without reusing cells (just create each time a new cell) and check if it helps. – Wojciech Kulik Mar 15 '19 at 23:21
  • @WojciechKulik Creating cell without reusing solved my problem. But table view probably will be laggy now. Of course I understand why cell doesn't back to normal frame without rounding. What I don't understand is why cell has rounded corners when `isLastInSection` parameter is set to false. Same thing is with cell's frame and subviews. I set up views every time it is being dequeued, but constraints etc are the same. – kkiermasz Mar 16 '19 at 08:06
  • I've moved adding subviews to init part of cell and problem still occurs, so it's caused because of rounding corners in `layoutSubviews()` – kkiermasz Mar 16 '19 at 08:23

1 Answers1

1

You dequeue cell and each time you add subviews you don't check if they are already there which will happen in case of recycled cell. That probably breaks constraints and causes incorrect sizing.

Same problem with rounding - you set rounded corners, but you never revert this behavior when reused cell should not be rounded.

Best way to solve this issue would be to add additional check and create subviews only once:

func setup(answer: String, isSelected: Bool, style: Style, isLastInSection: Bool) {
    if self.subviews.count == 0 {
        // adding subviews etc.
        // code that needs to be performed only once for whole cell's life
    }

    self.isLastInSection = isLastInSection
    // set up content that changes for each cell (like text)
    // for example a code depending on parameters of this method
}

alternatively you could keep some property like isInitialized and check that at the beginning.

Also your method layoutSubviews must support both cases:

override func layoutSubviews() {
    super.layoutSubviews()
    if isLastInSection {
        roundCorners(corners: [.bottomLeft, .bottomRight], radius: 16.0)
    } else {
        layer.mask = nil
    }
    contentView.layoutIfNeeded()
}
Wojciech Kulik
  • 7,823
  • 6
  • 41
  • 67
  • Yes! That's it! I have to remove mask from layer. I owe you one! Also I just want to add that I don't have to check `self.subviews.count == 0` because now all subviews are being added in init section :) – kkiermasz Mar 16 '19 at 10:32