Problem with table cell autolayout not spacing the way I want

Viewed 186

I'm trying to put a table cell that I designed into my table view which is supposed to show an artists songs, album pictures, as well as a play button but I'm not able to get the layout correct the way that I want it.

Screenshot of current app layout:

enter image description here

I want all the play buttons flush to the right of the table view but it seems like it disregards the constraints I put in the table view and goes out of bounds. I also want the extra space that the label has (shown in picture linked below) so that it spaces throughout the whole cell so that the play button can be to the right of the table view cell.

Current table view cell:

enter image description here

Here is the picture of my view controller layout and the constraints I put on them. Let me know if there is anything I can do and change. Thanks.

View controller layout:

enter image description here

4 Answers

Try to do it programmatically, for this example I set numberOfRowsInSection to return 20, set objects in your UITableViewCell class:

let containerViw: UIView = {
    let myView = UIView()
    myView.translatesAutoresizingMaskIntoConstraints = false
    myView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    myView.widthAnchor.constraint(equalToConstant: 100).isActive = true
    return myView
}()

lazy var buttonPlay: UIButton = {
    let button = UIButton(type: .system)
    let image = UIImage(systemName: "play.circle")
    button.setBackgroundImage(image, for: .normal)
    button.addTarget(self, action: #selector(handlePlay), for: .touchUpInside)
    button.translatesAutoresizingMaskIntoConstraints = false
   
    return button
}()

let dummyLabel: UILabel = {
    let label = UILabel()
    label.text = "Dummy Label"
    label.font = .systemFont(ofSize: 16, weight: .regular)
    label.textColor = .black
    
    return label
}()

let finestraBackground: UIImageView = {
    
    let imageView = UIImageView()
    imageView.backgroundColor = .red
    imageView.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysOriginal)
    imageView.translatesAutoresizingMaskIntoConstraints  = false
    imageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
    imageView.layer.cornerRadius = 50
    imageView.layer.masksToBounds = true
    
    return imageView
}()

now in UITableViewCell.CellStyle set stackView and add Constraints:

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    backgroundColor = .white
    
    containerViw.addSubview(buttonPlay)
    buttonPlay.heightAnchor.constraint(equalToConstant: 50).isActive = true
    buttonPlay.widthAnchor.constraint(equalToConstant: 50).isActive = true
    buttonPlay.centerYAnchor.constraint(equalTo: containerViw.centerYAnchor).isActive = true
    buttonPlay.centerXAnchor.constraint(equalTo: containerViw.centerXAnchor).isActive = true
    
    let stackView = UIStackView(arrangedSubviews: [finestraBackground, dummyLabel, containerViw])
    stackView.distribution = .fillProportionally
    stackView.spacing = 10
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(stackView)
    stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
    stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
    stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
    stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

last step set the function for play:

@objc fileprivate func handlePlay() {
    print("Play...")
}

and this is the result:

enter image description here

full code:

class TableViewCellCustom: UITableViewCell {

let containerViw: UIView = {
    let myView = UIView()
    myView.translatesAutoresizingMaskIntoConstraints = false
    myView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    myView.widthAnchor.constraint(equalToConstant: 100).isActive = true
    return myView
}()

lazy var buttonPlay: UIButton = {
    let button = UIButton(type: .system)
    let image = UIImage(systemName: "play.circle")
    button.setBackgroundImage(image, for: .normal)
    button.addTarget(self, action: #selector(handlePlay), for: .touchUpInside)
    button.translatesAutoresizingMaskIntoConstraints = false
   
    return button
}()

let dummyLabel: UILabel = {
    let label = UILabel()
    label.text = "Dummy Label"
    label.font = .systemFont(ofSize: 16, weight: .regular)
    label.textColor = .black
    
    return label
}()

let finestraBackground: UIImageView = {
    
    let imageView = UIImageView()
    imageView.backgroundColor = .red
    imageView.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysOriginal)
    imageView.translatesAutoresizingMaskIntoConstraints  = false
    imageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
    imageView.layer.cornerRadius = 50
    imageView.layer.masksToBounds = true
    
    return imageView
}()

@objc fileprivate func handlePlay() {
    print("Play...")
}

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    backgroundColor = .white
    
    containerViw.addSubview(buttonPlay)
    buttonPlay.heightAnchor.constraint(equalToConstant: 50).isActive = true
    buttonPlay.widthAnchor.constraint(equalToConstant: 50).isActive = true
    buttonPlay.centerYAnchor.constraint(equalTo: containerViw.centerYAnchor).isActive = true
    buttonPlay.centerXAnchor.constraint(equalTo: containerViw.centerXAnchor).isActive = true
    
    let stackView = UIStackView(arrangedSubviews: [finestraBackground, dummyLabel, containerViw])
    stackView.distribution = .fillProportionally
    stackView.spacing = 10
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(stackView)
    stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
    stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
    stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
    stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

UPDATE, create tableView programmatically example with a top view, without storyboard

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

let cellId = "cellId"
let tableView = UITableView()

let containerView = UIView()
let dummyImageView = UIImageView()
let artistLabel = UILabel()

override func viewDidLoad() {
    super.viewDidLoad()
    
    artistLabel.text = "Artist Name\nArtist Song Name"
    artistLabel.font = .systemFont(ofSize: 16, weight: .semibold)
    artistLabel.numberOfLines = 0
    artistLabel.textColor = .white
    
    dummyImageView.image = UIImage(named: "yourImage")
    dummyImageView.contentMode = .scaleAspectFill
    dummyImageView.clipsToBounds = true
    dummyImageView.translatesAutoresizingMaskIntoConstraints = false
    dummyImageView.widthAnchor.constraint(equalToConstant: 150).isActive = true
    dummyImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
    dummyImageView.layer.cornerRadius = 75
    
    containerView.backgroundColor = .darkGray
    containerView.translatesAutoresizingMaskIntoConstraints = false
    
    
    tableView.delegate = self
    tableView.dataSource = self
    tableView.translatesAutoresizingMaskIntoConstraints = false
    
    tableView.register(TableViewCellCustom.self, forCellReuseIdentifier: cellId)
    
    view.addSubview(containerView)
    containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
    
    let stackView = UIStackView(arrangedSubviews: [dummyImageView, artistLabel])
    stackView.axis = .horizontal
    stackView.spacing = 10
    stackView.distribution = .fillProportionally
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    containerView.addSubview(stackView)
    stackView.heightAnchor.constraint(equalToConstant: 150).isActive = true
    stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20).isActive = true
    stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10).isActive = true
    stackView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
    
    view.addSubview(tableView)
    tableView.topAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 120
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TableViewCellCustom
    
    return cell
}
}

in scene delegate activate navigation controller like this:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    
    window = UIWindow(windowScene: windowScene)
    //        let controller = UINavigationController(rootViewController: ViewController())//if you want navigation controller uncomment that
    let controller = ViewController() // if you want navigation Controller COMMENT That
    window?.makeKeyAndVisible()
    window?.rootViewController = controller
}

Or you can use didSelecrRowAt method: just add this two lines in your TableViewController:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Play...", indexPath.row)
}

It can be achieved without code.

Just set Content Compression Resistance Priority to 1000 for your "Play Button" and Content Hugging Priority to be higher than your "Song Name" label like 251 for button and 250 for label.

You have given the constraints incorrectly as I can see, You can change the constraints of the play button with trailing to the superview and giving it carter vertically to superview. And Remove leading from the button if any this will make it on the right side of the cell view.

And giving the image View as top and bottom and leading constraints to superView and make the height constant for the superView or the cellView.

Then making the label leading constraint to ImageView and center vertically with the image view/superView. And give the label trailing to the button a fixed contact like only 10. And you are good to go.

I think it’s because you have putted PlayButton inside your StackView. StackView Automatically putts your components side by side. Try putting your Play Button out of StackView and add Constraints for both stackView and PlayButton.

Related