1

I was trying a new StackView inside TableView's cell approach for certain requirement

Some Background - I have a TableView and it's cell has a stackView inside it. That stackView can have multiple types of views inside it but for the sake of simplicity i am working with labels for now. I have created a custom XIB(LabelInfoView) which has 3 labels in horizontal. It shows data in sort of key-value pair combination. The labels can be multiline

enter image description here

Now I am adding 6 of these inside the stackView which is present inside the tableView cell

I have set content hugging and content compression resistance priorities

For label named Label

enter image description here

For label named Separator

enter image description here

For label named Info

enter image description here

Problem - The first time the table is loaded, some(not all) labels are getting truncated at random orders and even for random cells(say first cell's third label view is getting truncated and second cell's first and second and like that, totally random. No fixed orders even between multiple run of applications)

enter image description here

(Notice 5th labelView inside first tableView's cell)

Now when since i am dequeuing the cells, while scrolling, when my first cell go of screen and then when comes back on the screen, prepareForReuse() will be called

  cellsStackView.removeAllArrangedSubviews()
  setupCard()

I am first removing all the subviews inside the stackView and add them again . This time , after the method is called, the stackView is loaded correctly.

enter image description here

I have tried a lot of things like tweaking the priorities and stuff but nothing works so far!

user121095
  • 517
  • 4
  • 15
  • What are your values for stackview alignment and distribution properties? – bisma Oct 18 '19 at 14:04
  • @bisma Alignment - `Fill` , Distribution - `Equal Spacing` – user121095 Oct 18 '19 at 14:05
  • I would suggest avoid using stack view inside the UITableView because it make scrolling slow as well as it stuck. As per your design your first label width is fixed and second also so use constraint and it handle by width constraint Regarding this issue stack view run time assigned it own constraints to view so i common issue to with stack and table view cell – Pravin Tate Oct 18 '19 at 15:10
  • @PravinTate Other than the stackView approach , I have nested tableView approach which is even worse than this one – user121095 Oct 18 '19 at 19:20
  • @user121095 - I have to wonder if it is related to how you are loading your xib view? Or maybe related to other constraints you ay be adding (for the "Label" and round buttons / images)? – DonMag Oct 18 '19 at 19:51

2 Answers2

3

Since I was setting numberOfLines to 0 for both the leftLabel and the rightLabel, the auto-layout engine was not able to figure out that at which point the label should go multi-line i.e. the label needed a width constraint . So, I added the width of the leftLabel to <= 50% of superview's width . And then it finally worked

user121095
  • 517
  • 4
  • 15
1

Here is an example using code-only instead of a custom XIB.

No IBOutlet or Prototype cells, so just assign WorkTableViewController as the custom class for a UITableViewController:

class MyThreeLabelView: UIView {

    let leftLabel: UILabel = {
        let v = UILabel()
        return v
    }()

    let sepLabel: UILabel = {
        let v = UILabel()
        return v
    }()

    let rightLabel: UILabel = {
        let v = UILabel()
        return v
    }()

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

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

    private func commonInit() {

        backgroundColor = .white

        [leftLabel, sepLabel, rightLabel].forEach {
            $0.font = UIFont.systemFont(ofSize: 16.0)
            $0.translatesAutoresizingMaskIntoConstraints = false
            addSubview($0)
        }

        // bold-italic font for left label
        leftLabel.font = leftLabel.font.boldItalic

        // we want left and separator labels to NOT compress or expand
        leftLabel.setContentHuggingPriority(.required, for: .horizontal)
        leftLabel.setContentHuggingPriority(.required, for: .vertical)
        leftLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

        sepLabel.setContentHuggingPriority(.required, for: .horizontal)
        sepLabel.setContentHuggingPriority(.required, for: .vertical)
        sepLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

        // right label should hug vertically
        rightLabel.setContentHuggingPriority(.required, for: .vertical)

        // right label can be mutliple lines
        rightLabel.numberOfLines = 0

        NSLayoutConstraint.activate([

            // constrain all 3 labels 10-pts from top, at least 10-pts from bottom
            leftLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10.0),
            leftLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),

            sepLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
            sepLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),

            rightLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
            rightLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10),

            // constrain left label 10-pts from leading edge
            leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10.0),

            // constrain separator label 10-pts from left label
            sepLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor, constant: 10.0),

            // constrain right label 10-pts from separator label
            rightLabel.leadingAnchor.constraint(equalTo: sepLabel.trailingAnchor, constant: 10.0),

            // constrain right label 10-pts from trailing edge
            rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),

        ])

    }

}

class StackCell: UITableViewCell {

    let stackView: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        v.alignment = .fill
        v.distribution = .fill
        v.spacing = 0
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }

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

    override func prepareForReuse() {
        super.prepareForReuse()

        // remove all arrangedSubviews from stack view
        stackView.arrangedSubviews.forEach {
            $0.removeFromSuperview()
        }
    }

    func commonInit() -> Void {

        contentView.backgroundColor = .lightGray

        // add the stack view
        contentView.addSubview(stackView)

        // constrain 12-pts on all 4 sides
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
            stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0),
            stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12.0),
            stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12.0),
        ])

    }

    func addLabels(_ labels: [String]) -> Void {
        labels.forEach {
            s in

            // instance of MyThreeLabelView
            let v = MyThreeLabelView()
            // for this example, left and separator labels don't change
            v.leftLabel.text = "Assigned To"
            v.sepLabel.text = "-"
            // assign right label text
            v.rightLabel.text = s
            // add MyThreeLabelView to the stack view
            stackView.addArrangedSubview(v)
        }
    }

}

class WorkTableViewController: UITableViewController {

    let reuseID = "StackCell"

    var theData: [[String]] = [[String]]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // make 8 sets of 6-rows of labels
        for i in 1...8 {
            let tmp: [String] = [
                "1) Row \(i)",
                "2) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
                "3) Short text.",
                "4) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
                "5) Short text.",
                "6) Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
            ]
            theData.append(tmp)
        }

        tableView.register(StackCell.self, forCellReuseIdentifier: reuseID)

    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! StackCell

        cell.addLabels(theData[indexPath.row])

        return cell
    }

}

// UIFont extension for bold / italic / boldItalic
// found here: https://stackoverflow.com/a/21777132/6257435
extension UIFont {
    var bold: UIFont {
        return with(.traitBold)
    } // bold

    var italic: UIFont {
        return with(.traitItalic)
    } // italic

    var boldItalic: UIFont {
        return with([.traitBold, .traitItalic])
    } // boldItalic

    func with(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont {
        guard let descriptor = self.fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits(traits).union(self.fontDescriptor.symbolicTraits)) else {
            return self
        }
        return UIFont(descriptor: descriptor, size: 0)
    }

    func without(_ traits: UIFontDescriptor.SymbolicTraits...) -> UIFont {
        guard let descriptor = self.fontDescriptor.withSymbolicTraits(self.fontDescriptor.symbolicTraits.subtracting(UIFontDescriptor.SymbolicTraits(traits))) else {
            return self
        }
        return UIFont(descriptor: descriptor, size: 0)
    }

} // extension

Result:

enter image description here

DonMag
  • 44,662
  • 5
  • 32
  • 56
  • Okay so the problem was i had set `numberOfLines` for all the _three labels_ to 0. But when i changed the `numberOfLines` for `leftLabel` and `sepLabel` to 1 and kept 0 for `rightLabel` , it worked. What can be the problem do you have any idea ? – user121095 Oct 21 '19 at 04:59