The Per Rewrite Diaries: Day 23

This post is part of a series about rewriting my iOS app, Per. Per is a price per unit comparison app with a bunch of neat convenience figures, but it hasn’t been updated in years, so I’m rewriting it from scratch to eliminate a bunch of technical debt. Just because it’s not an open-source app doesn’t mean I can’t share what I learn as I go!

See the rest of the series here.

Custom Table View Cells

Right now, the ProductListContentViewController is using a very basic UITableViewCell to show basic details of each ProductItem entry. That’s fine, but it’s going to get messy when that cell gets a custom layout. Better to contain that in a separate view!

So, to begin with, I just wanted to refactor out that code into a new class. Creating one that conformed to UITableViewCell gave me some stubbed methods to add, and I added some properties to the class. First, a UILabel that I can later customize:

private let productDetailLabel: UILabel = {
    let label = UILabel()
    return label
}()

Then a product that, when set, updates the productDetailLabel:

var product: ProductItem {
    didSet {
        let productUnits = product.units?.symbol ?? "unit"
        productDetailLabel.text = "\(product.quantity) \(productUnits)s for \(product.price) costs \(product.pricePerUnit) per \(productUnits)"
    }
}

Right now, this means that I have to initialize the product property with some default value when it’s initialized:

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    self.product = ProductItem(price: 1.00, quantity: 1.99, units: nil)
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    addSubview(productDetailLabel)
    setupConstraints()
}

The setupContstraints() method does the same thing that it does in other UIViews — sets the view to not translate its autoresizing mask into constraints, and then activates the constraints I want. Pretty straightforward. Then it’s just a question of using this custom cell in ProductListContentViewController instead of the default style:

override func viewDidLoad() {
    // The other viewDidLoad stuff is done here
    // [...]
    // Then the custom cell is registered:
    productTableView.register(ProductListCellView.self, forCellReuseIdentifier: "productListCell")
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let productItem = productList.getItems()[indexPath[1]]
    let cell = productTableView.dequeueReusableCell(withIdentifier: "productListCell", for: indexPath) as! ProductListCellView
    cell.product = productItem
    return cell
}

That’s it! Something that I tend to forget when laying things out in code is to set translatesAutoresizingMaskIntoConstraints to false so that everything works as expected, but I’m getting the hang of doing that first in any setupConstraints() method now.

Tomorrow, I’ll customize this a bit further with multiple lines.

Angelo Stavrow

Montreal, Canada
Email me

Mobile/full-stack developer. Montrealer. Internet gadabout. Your biggest fan.