swiftxcodeuitableviewautolayoutstackview

Create custom UITableViewCell with using stack view and auto layout


I have 2 questions about constraints and auto layout:

  1. How programmatically create a cell-like in the picture below? I didn't understand how to programmatically add auto-layout and constraints for my cell. enter image description here
  2. How to assign default Apple layout margins to the cell? (for example, left inset in default cell equal 20 pt for big screens and 16 pt for small).

My current code:

class cellWithTitleAndDetail: UITableViewCell {
    
    // MARK: - Properties
    let title = UILabel()
    let detail = UILabel()
    let stackView = UIStackView()
    

    // MARK: - Override init
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        title.translatesAutoresizingMaskIntoConstraints = false
        detail.translatesAutoresizingMaskIntoConstraints = false
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .fillProportionally
        
        // Set color
        title.textColor = .white
        detail.textColor = .white
        
        // Highlight StackView
        stackView.addBackground(color: .blue)
        
        stackView.addArrangedSubview(title)
        stackView.addArrangedSubview(detail)
        
        stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
        stackView.isLayoutMarginsRelativeArrangement = true

        self.contentView.addSubview(stackView)
                
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

}

Result: enter image description here

Update:

Below added my new code and code from DonMag's answer.

New question: "LayoutMarginsGuide" works perfectly on iPhones with screen width that equal to 375 pt(image 375-1). But on big size screens separator appears earlier than the cell(image 414-2). How I can fix this?

enter image description here

New code:

// MARK: - Override init
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // Off translatesAutoresizingMaskIntoConstraints
        title.translatesAutoresizingMaskIntoConstraints = false
        detail.translatesAutoresizingMaskIntoConstraints = false
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        // Setup stackView
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .fill
        
        // Hugging
        title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
        detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
        
        // Resistance
        title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
        detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
        
        // Set textAlignment
        title.textAlignment = .left
        detail.textAlignment = .right
        
        // Set numberOfLines
        title.numberOfLines = 0
        
        // Highlight stackView and set colors
        stackView.addBackground(color: .blue)
        title.textColor = .white
        detail.textColor = .white
        
        // Add title and detail
        stackView.addArrangedSubview(title)
        stackView.addArrangedSubview(detail)
 
        // Add to subview
        self.contentView.addSubview(stackView)
        
        // Get layoutMarginsGuide
        let layoutMarginsGuide = contentView.layoutMarginsGuide
        
        // Set constraints
        NSLayoutConstraint.activate([
            
            // constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
            stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),

        ])
                
    }

Solution

  • You can use the Content View's layoutMarginsGuide:

        // only if you want different margins than the content view's margins
        //stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
        //stackView.isLayoutMarginsRelativeArrangement = true
        
        self.contentView.addSubview(stackView)
        
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            // constrain all 4 sides of the stack view to the
            //  content view's layoutMarginsGuide
            stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
    
        ])
        
    

    As a side note, it's not clear what you really want (and this would be a separate question if this doesn't provide the layout you want)...


    Edit

    Using your updated code - the only change I made is giving the labels a background color since you didn't show your stackView.addBackground(color: .blue) code:

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        // Off translatesAutoresizingMaskIntoConstraints
        title.translatesAutoresizingMaskIntoConstraints = false
        detail.translatesAutoresizingMaskIntoConstraints = false
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        // Setup stackView
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .fill
        
        // Hugging
        title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
        detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
        
        // Resistance
        title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
        detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
        
        // Set textAlignment
        title.textAlignment = .left
        detail.textAlignment = .right
        
        // Set numberOfLines
        title.numberOfLines = 0
        
        // Highlight stackView and set colors
        //stackView.addBackground(color: .blue)
        title.backgroundColor = .blue
        detail.backgroundColor = .red
    
        title.textColor = .white
        detail.textColor = .white
        
        // Add title and detail
        stackView.addArrangedSubview(title)
        stackView.addArrangedSubview(detail)
    
        // Add to subview
        self.contentView.addSubview(stackView)
        
        // Get layoutMarginsGuide
        let layoutMarginsGuide = contentView.layoutMarginsGuide
        
        // Set constraints
        NSLayoutConstraint.activate([
            
            // constrain all 4 sides of the stack view to the content view's layoutMarginsGuide
            stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0.0),
            stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 0.0),
            stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 0.0),
            stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 0.0),
    
        ])
                
    }
    

    This is what I get:

    enter image description hereenter image description here


    Edit 2

    Table view cell separators can change based on device, iOS version, table view style, etc.

    For the most reliable consistency, set your own.

    Here's an example...

    Here's the full code:

    class MyTestTableViewController: UITableViewController {
        
        let testTitles: [String] = [
            "Yesterday all my troubles seemed so far away, Now it looks as though they're here to stay.",
            "She packed my bags last night pre-flight, Zero hour nine AM.",
            "When you're weary, feeling small, When tears are in your eyes, I will dry them all."
        ]
        let testDetails: [String] = [
            "The Beatles",
            "Elton John",
            "Simon & Garfunkel",
        ]
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            tableView.register(MyCellWithTitleAndDetail.self, forCellReuseIdentifier: "myCell")
            
            // our custom separatorInset
            //  left matches cell's stackView leading anchor
            tableView.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0)
        }
        
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 3
        }
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 3
        }
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyCellWithTitleAndDetail
                
            // set cell separatorInset equal to tableView separatorInset
            cell.separatorInset = tableView.separatorInset
                
            cell.title.text = testTitles[indexPath.row]
            cell.detail.text = testDetails[indexPath.row]
            
            return cell
    
        }
    
    }
    
    class MyCellWithTitleAndDetail: UITableViewCell {
        
        // MARK: - Properties
        let title = UILabel()
        let detail = UILabel()
        let stackView = UIStackView()
        
        
        // MARK: - Override init
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            // Off translatesAutoresizingMaskIntoConstraints
            title.translatesAutoresizingMaskIntoConstraints = false
            detail.translatesAutoresizingMaskIntoConstraints = false
            stackView.translatesAutoresizingMaskIntoConstraints = false
            
            // Setup stackView
            stackView.axis = .horizontal
            stackView.alignment = .center
            
            stackView.distribution = .fillEqually
    
            // if we want the labels to be 50% of the width,
            //  set stackView.distribution = .fillEqually
            //  then we don't need any Content Hugging or Compression Resistance priority changes
            
    //      // Hugging
    //      title.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
    //      detail.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
    //
    //      // Resistance
    //      title.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
    //      detail.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
    
            // Set textAlignment
            title.textAlignment = .left
            detail.textAlignment = .right
            
            // Set numberOfLines
            title.numberOfLines = 0
            
            // Highlight stackView and set colors
            
            title.backgroundColor = .blue
            detail.backgroundColor = .red
            //stackView.addBackground(color: .blue)
            
            title.textColor = .white
            detail.textColor = .white
            
            // Add title and detail
            stackView.addArrangedSubview(title)
            stackView.addArrangedSubview(detail)
            
            // Add to subview
            self.contentView.addSubview(stackView)
            
            // Set constraints
            NSLayoutConstraint.activate([
                
                // constrain all 4 sides of the stack view to the content view
                //  with your own "margins"
                stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
                stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15.0),
                stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15.0),
                stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0),
                
            ])
            
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
    }
    

    and the results:

    enter image description here

    enter image description here