Saphal Patro
Saphal Patro

Reputation: 1

UITableCellView autosizing to fill whole screen

I initialised my UITableViewCell as with a initLayout function as follows:

        contentView.addSubview(horizontalStack)
        horizontalStack.addArrangedSubview(imageToView)
        imageToView.contentMode = .scaleAspectFit
        verticalStack.addArrangedSubview(petName)
        verticalStack.addArrangedSubview(petDescription)
        horizontalStack.addArrangedSubview(verticalStack)

but this gives me the following results. The image is way too big and the lines are all messed up, with the title way up above at the top:

image

I want the view to look something like this. Note the symmetric nature of all the elements

image

What I have tried so far:

  1. The UITableViewCell is a Horizontal Stack View with 2 elements, namely:

Can someone please help me with this?

Upvotes: 0

Views: 61

Answers (1)

DonMag
DonMag

Reputation: 77423

Stack views do a great job of arranging subviews - but you have to give them enough information so they know how you want them arranged.

First, I'm assuming you added constraints for the horizontalStack to begin with (otherwise we wouldn't see anything in your screen caps).

Without providing any other constraints, stack views use the intrinsicContentSize of the subviews to handle the arrangement. If you haven't given a UIImageView any constraints, its intrinsic size will be the size of the image.

Assuming you want the image to be square (1:1 ratio), give it a heightAnchor = widthAnchor constraint.

imageToView.heightAnchor.constraint(equalTo: imageToView.widthAnchor)

Then, decide how wide you want it to be. A constant point width - such as 80?

imageToView.widthAnchor.constraint(equalToConstant: 80.0)

Or a relative width, such as 1/2 the width of the labels?

imageToView.widthAnchor.constraint(equalTo: verticalStack.widthAnchor, multiplier: 0.5)

You'll also want to decide how you want the alignment in your horizontalStack.

Here are a couple examples. The first section has horizontalStack.alignment = .center and the second section uses horizontalStack.alignment = .top (images on right have background colors and a dashed-border around the stack view to make it easy to see the frames):

enter image description hereenter image description here

enter image description hereenter image description here

Here is the code I used - no @IBoutlet connections or prototype cells, so just add a table view controller and assign the class to PatroTableViewController:

class PatroCell: UITableViewCell {
    
    static let identifier: String = "patroCell"
    
    let horizontalStack: UIStackView = {
        let v = UIStackView()
        v.spacing = 8
        v.alignment = .center
        return v
    }()
    
    let verticalStack: UIStackView = {
        let v = UIStackView()
        v.axis = .vertical
        v.spacing = 8
        return v
    }()
    
    let imageToView: UIImageView = {
        let v = UIImageView()
        return v
    }()
    let petName: UILabel = {
        let v = UILabel()
        v.font = UIFont.systemFont(ofSize: 16.0)
        return v
    }()
    let petDescription: UILabel = {
        let v = UILabel()
        v.font = UIFont.systemFont(ofSize: 15.0)
        v.textColor = .lightGray
        v.numberOfLines = 0
        return v
    }()

    // so we can see the frame of the horizontal stack view
    let stackOutlineView: DashedOutlineView = {
        let v = DashedOutlineView()
        return v
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func commonInit() -> Void {
    
        stackOutlineView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(stackOutlineView)
        
        horizontalStack.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(horizontalStack)
        horizontalStack.addArrangedSubview(imageToView)
        imageToView.contentMode = .scaleAspectFit
        verticalStack.addArrangedSubview(petName)
        verticalStack.addArrangedSubview(petDescription)
        horizontalStack.addArrangedSubview(verticalStack)
        
        let g = contentView.layoutMarginsGuide
        
        // this will avoid auto-layout warnings
        let hsBottom = horizontalStack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
        hsBottom.priority = UILayoutPriority(rawValue: 999)
        
        NSLayoutConstraint.activate([
            
            // constrain horizontal stack to all 4 sides - use margins
            horizontalStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            horizontalStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            horizontalStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            
            // the bottom anchor with priority 999
            hsBottom,

            // image view should be square - 1:1 ratio
            imageToView.heightAnchor.constraint(equalTo: imageToView.widthAnchor),
            
            // image view should be 80 x 80?
            //imageToView.widthAnchor.constraint(equalToConstant: 80.0),
            
            // or, maybe, image view width should be 1/2 the width of the labels?
            imageToView.widthAnchor.constraint(equalTo: verticalStack.widthAnchor, multiplier: 0.5),
            
            // constrain the outline view
            stackOutlineView.topAnchor.constraint(equalTo: horizontalStack.topAnchor, constant: 0.0),
            stackOutlineView.leadingAnchor.constraint(equalTo: horizontalStack.leadingAnchor, constant: 0.0),
            stackOutlineView.trailingAnchor.constraint(equalTo: horizontalStack.trailingAnchor, constant: 0.0),
            stackOutlineView.bottomAnchor.constraint(equalTo: horizontalStack.bottomAnchor, constant: 0.0),
            
        ])
        
        stackOutlineView.isHidden = true
        
        // change to "if true" to see the frames
        if false {
            // so we can see the UI element frames
            imageToView.backgroundColor = .red
            petName.backgroundColor = .green
            petDescription.backgroundColor = .yellow
            petDescription.textColor = .black
            stackOutlineView.isHidden = false
        }
        
    }
    
}

class PatroTableViewController: UITableViewController {
    
    let descriptions: [String] = [
        "One-line description",
        "This is the description of the pet in this cell. It is enough text that it will cause word-wrapping.",
        "This description will be much longer... It will wrap onto many lines so we can see how the cell layout will look when the description text makes the label taller than the image view on the left. Note the differences between \".alignment = .center\" vs \".alignment = .top\""
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.register(PatroCell.self, forCellReuseIdentifier: PatroCell.identifier)
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return descriptions.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: PatroCell.identifier, for: indexPath) as! PatroCell
        
        cell.petName.text = "Dog"
        cell.petDescription.text = descriptions[indexPath.row]
        cell.imageToView.image = UIImage(named: "dog")

        if indexPath.section == 0 {
            // set horizontal stack alignment to center
            cell.horizontalStack.alignment = .center
        } else {
            // set horizontal stack alignment to top
            cell.horizontalStack.alignment = .top
        }
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return "Alignment: Center"
        }
        return "Alignment: Top"
    }
}

class DashedOutlineView: UIView {
    
    var shapeLayer: CAShapeLayer!
    
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    func commonInit() -> Void {
        shapeLayer = self.layer as? CAShapeLayer
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0).cgColor
        shapeLayer.lineWidth = 1.0
        shapeLayer.lineDashPattern = [8,8]
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        shapeLayer.path = UIBezierPath(rect: bounds).cgPath
    }
}

Upvotes: 1

Related Questions