Search code examples
iosswiftuitableviewautolayout

How can I line up different UITableViewCell images and labels?


All I'm doing in my cellForRowAt is setting UITableViewCell's (no subclass) imageView?.image and textLabel?.text values (and fonts and colors). Setting an image via SF Symbols and text according to my model. To go into more detail, there are only three possible cell kinds: a workspace, the "Add Workspace" button, and the archive.

In the following screenshot, I've demonstrated with green lines how these views don't quite line up in a logical fashion. Text is misaligned between all three types of cells, and (annoyingly) the SF Symbols image for that boxy icon with an "add" (+) indicator is slightly wider than the standard image.

Can anyone help me with an easy fix for this that I'm simply just missing? I've already tried setting the imageViews' aspect ratios to 1:1 with constraints. That didn't affect anything.

Text and images are not quite aligned in this UITableView.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell

    if indexPath.section == 0 {
        cell = tableView.dequeueReusableCell(withIdentifier: "workspace", for: indexPath)
        if indexPath.row == addWorkspaceRow {
            cell.imageView?.image = .addWorkspace
            cell.imageView?.tintColor = cell.tintColor
            cell.textLabel?.text = "Add Workspace"
            cell.textLabel?.textColor = cell.tintColor
            cell.accessoryType = .none
        } else {
            let workspace = model.workspaces[indexPath.row]
            cell.imageView?.image = .workspace
            cell.imageView?.tintColor = .label
            cell.textLabel?.text = workspace.displayName
            cell.textLabel?.textColor = .label
            cell.accessoryType = .disclosureIndicator
        }
    } else {
        cell = tableView.dequeueReusableCell(withIdentifier: "archive", for: indexPath)
        cell.imageView?.image = .archive
        cell.imageView?.tintColor = .archive
        cell.textLabel?.text = "Archive"
        cell.textLabel?.textColor = .label
        cell.accessoryType = .disclosureIndicator
    }

    cell.textLabel?.font = .preferredFont(forTextStyle: .body)

    return cell
}

Solution

  • I meet the same things, and want to solve it without using custom cell. But it is hard to config the constraint without subclass it.

    Eventually, I turn to use custom cell, it is simple to use, and in case you may need to add more stuff in your tableView cell, custom cell is the better place to go.

    enter image description here

    My code, yeah I'm making a music player now;)

    class LibraryTableCell: UITableViewCell {
        var cellImageView = UIImageView()
        var cellLabel = UILabel()
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: "libraryTableCell")
            
            cellImageView.translatesAutoresizingMaskIntoConstraints = false
            cellImageView.contentMode = .scaleAspectFit
            cellImageView.tintColor = .systemPink
            contentView.addSubview(cellImageView)
            
            cellLabel.translatesAutoresizingMaskIntoConstraints = false
            cellLabel.font = UIFont.systemFont(ofSize: 20)
            contentView.addSubview(cellLabel)
            
            NSLayoutConstraint.activate([
                cellImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                cellImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
                cellImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
                cellImageView.widthAnchor.constraint(equalToConstant: 44),
                
                cellLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                cellLabel.leadingAnchor.constraint(equalTo: cellImageView.trailingAnchor, constant: 10),
                
            ])
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    class LibraryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
        var tableView = UITableView()
        let cellTitles = ["Playlists", "Artists", "Albums", "Songs", "Genres"]
        let imageNames = ["music.note.list", "music.mic", "square.stack", "music.note", "guitars"]
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = .white
            title = "Library"
            
            navigationController?.navigationBar.prefersLargeTitles = true
            
            tableView.delegate = self
            tableView.dataSource = self
            tableView.register(LibraryTableCell.self, forCellReuseIdentifier: "libraryTableCell")
            tableView.translatesAutoresizingMaskIntoConstraints = false
            tableView.rowHeight = 48
            view.addSubview(tableView)
            
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20),
                tableView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.6)
            ])
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return cellTitles.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            guard let cell = tableView.dequeueReusableCell(withIdentifier: "libraryTableCell", for: indexPath) as? LibraryTableCell else {
                fatalError("Unable to dequeue libraryTableCell")
            }
            
            cell.accessoryType = .disclosureIndicator
            
            let imageName = imageNames[indexPath.row]
            cell.cellImageView.image = UIImage(systemName: imageName)
            
            let title = cellTitles[indexPath.row]
            cell.cellLabel.text = title
            
            return cell
        }
        
        func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            cell.separatorInset = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 8)
        }
    }