user121095
user121095

Reputation: 813

Few labels inside StackView truncated initially but appears perfectly upon scrolling

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!

Upvotes: 1

Views: 2463

Answers (2)

user121095
user121095

Reputation: 813

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

Upvotes: 3

DonMag
DonMag

Reputation: 77690

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

Upvotes: 1

Related Questions