mahan
mahan

Reputation: 14985

iOS - Take all the available space in a horizontal UIStackView

I have UITableViewCell that contains a horizontal UIStackView. The UIStackView contains four views in the following order.

UIImageView   UILabel    UILabel UIImageView

There are 16 points spacing after the arrangedSubViews. I want that the second UILabel takes all the available space. If there is not enough space, it text should wrap.

I have used the following codes. It works almost. The problem is that even though there is enough space between the UILabel's as you see in the screenshot attached, the second UILabel breaks. But I want it to break only if there is not enough space.

enter image description here


class TableViewCell: UITableViewCell {
    
    static let identifier = "TableViewCell"
    
    private let leadingImageView: UIImageView = {
        let view = UIImageView(image: UIImage(systemName: "calendar"))
        view.setConstraints(heightConstant: 25, widthConstant: 25)
        view.tintColor = .text
        return view
    }()
    
    private let leadingLabel: UILabel = {
        let label = UILabel()
        label.textColor = .label
        label.text = "Start Date"
        label.numberOfLines = 0
        label.sizeToFit()
        return label
    }()
    
    let trailingLabel: UILabel = {
        let label = UILabel()
        label.text = "Friday, 17 July 2020"
        label.textColor = .label
        label.numberOfLines = 0
        label.textAlignment = .right
        return label
    }()
    
    let trailingImageView: UIImageView  = {
        let configuration = UIImage.SymbolConfiguration(pointSize: 12, weight: .light)
        let image = UIImage(systemName: "arrowtriangle.down", withConfiguration: configuration)
        let view = UIImageView(image: image)
        return view
    }()
    
    private let superStackView: UIStackView = {
        let view = UIStackView()
        view.distribution = .fillProportionally
        view.alignment = .center
        view.spacing = 16
        return view
    }()
    
    private  let containerView: UIView = {
        let view = UIView()
        view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        view.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return view
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setUpSubviews()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
extension TableViewCell {
    private  func setUpSubviews(){
        containerView.addSubview(trailingImageView)
        trailingImageView.alignCenter(centerXAnchor: containerView.centerXAnchor,
                                      centerYAnchor: containerView.centerYAnchor)
        
        superStackView.addArrangedSubview(leadingImageView)
        superStackView.addArrangedSubview(leadingLabel)
        superStackView.addArrangedSubview(trailingLabel)
        superStackView.addArrangedSubview(containerView)
        
        let constant = CGFloat(16)
    
        self.addSubview(superStackView)
        self.contentView.addSubview(superStackView)
        superStackView.setConstraints(topAnchor: contentView.topAnchor, leadingAnchor: contentView.leadingAnchor,
                              bottomAnchor: contentView.bottomAnchor, trailingAnchor: contentView.trailingAnchor,
                              topConstant: constant, leadingConstant: constant, bottomConstant: constant, trailingConstant: constant)
        
        
        self.contentView.updateConstraints()
        
    }
}

How can I fix this issue so that the cell looks like the cells in the following image?

enter image description here

Upvotes: 0

Views: 2300

Answers (2)

DonMag
DonMag

Reputation: 77647

You're close...

First, setting a stack view's Distribution to Fill Proportionally is the most misunderstood distribution option, and you don't want to use it here.

Second, it helps greatly when designing to use contrasting backgrounds to make it easy to see what your frames are doing.

Here is your code, modified to get to what appears to be your goal. I don't have your "constraint helpers" so I changed it to standard constraint format. I also added a few comments for some clarification:

class TableViewCell: UITableViewCell {
    
    static let identifier = "TableViewCell"
    
    private let leadingImageView: UIImageView = {
        let view = UIImageView(image: UIImage(systemName: "calendar"))
        view.translatesAutoresizingMaskIntoConstraints = false
        
        //view.setConstraints(heightConstant: 25, widthConstant: 25)
        view.widthAnchor.constraint(equalToConstant: 25).isActive = true
        view.heightAnchor.constraint(equalToConstant: 25).isActive = true

        view.tintColor = .blue // .text
        return view
    }()
    
    private let leadingLabel: UILabel = {
        let label = UILabel()
        label.textColor = .label
        label.text = "Start Date"
        // only single line for "leading label"
        label.numberOfLines = 1
        // content hugging
        label.setContentHuggingPriority(.required, for: .horizontal)
        label.backgroundColor = .cyan
        return label
    }()
    
    let trailingLabel: UILabel = {
        let label = UILabel()
        label.text = "Friday, 17 July 2020"
        label.textColor = .label
        label.numberOfLines = 0
        label.textAlignment = .right
        label.backgroundColor = .green
        return label
    }()
    
    let trailingImageView: UIImageView  = {
        let configuration = UIImage.SymbolConfiguration(pointSize: 12, weight: .light)
        let image = UIImage(systemName: "arrowtriangle.down", withConfiguration: configuration)
        let view = UIImageView(image: image)
        return view
    }()
    
    private let superStackView: UIStackView = {
        let view = UIStackView()
        view.translatesAutoresizingMaskIntoConstraints = false
        // do NOT use .fillProportionally
        view.distribution = .fill
        view.alignment = .center
        view.spacing = 16
        return view
    }()
    
    private  let containerView: UIView = {
        let view = UIView()
        // not needed
        //view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        //view.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return view
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setUpSubviews()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
extension TableViewCell {
    private  func setUpSubviews(){
        containerView.addSubview(trailingImageView)
        //trailingImageView.alignCenter(centerXAnchor: containerView.centerXAnchor,
        //                            centerYAnchor: containerView.centerYAnchor)
        
        superStackView.addArrangedSubview(leadingImageView)
        superStackView.addArrangedSubview(leadingLabel)
        superStackView.addArrangedSubview(trailingLabel)
        superStackView.addArrangedSubview(containerView)
        
        let constant = CGFloat(16)
        
        self.addSubview(superStackView)
        self.contentView.addSubview(superStackView)
        //superStackView.setConstraints(topAnchor: contentView.topAnchor, leadingAnchor: contentView.leadingAnchor,
        //                            bottomAnchor: contentView.bottomAnchor, trailingAnchor: contentView.trailingAnchor,
        //                            topConstant: constant, leadingConstant: constant, bottomConstant: constant, trailingConstant: constant)
        
        NSLayoutConstraint.activate([
            
            trailingImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
            trailingImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
            
            superStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: constant),
            superStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: constant),
            superStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -constant),
            superStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -constant),

        ])

        // not needed
        //self.contentView.updateConstraints()
        
    }
}

And, an example view controller:

class MahanTableViewController: UITableViewController {
    
    var myData: [String] = [
        "Saturday, 18 July 2020",
        "Sunday, 19 July 2020",
        "Wednesday, 22 July 2020",
        "Saturday, 26 September 2020",
        "Sunday, 27 September 2020",
        "Wednesday, 30 September 2020",
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
        
    }
    
    // MARK: - Table view data source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
        
        cell.trailingLabel.text = myData[indexPath.row]
        
        return cell
    }
    
}

Producing this output:

enter image description here

Upvotes: 2

Jawad Ali
Jawad Ali

Reputation: 14417

Try to Return 1

let trailingLabel: UILabel = {
        let label = UILabel()
        label.text = "Friday, 17 July 2020"
        label.textColor = .label
        label.numberOfLines = 1
        label.textAlignment = .right
        return label
    }()

Upvotes: 0

Related Questions