Maintainable configuration with struct!
Yes, you read it right. Although many times, we uses enums and switch but there is a much more maintainable and readable configuration pattern by using struct. I first read it here on Jesse Squires blog.
Very often, we use enums to filter different configuration in our code. I often use it in a custom UITableViewCell class.
Example, in a mobile commerce app, we have a LineItemTableViewCell that is displaying the status of the line item.
struct LineItem {
let productName: String
let price: Double
let status: String
}
enum LineItemTableViewCellStyle {
case cart
case deliveryStatus
case orderHistory
}
class LineItemTableViewCell: UITableViewCell {
@IBOutlet weak var productNameLabel: UILabel!
@IBOutlet weak var priceLabel: UILabel!
@IBOutlet weak var orderStatusLabel: UILabel!
func configure(lineItem: LineItem) {
productNameLabel.text = lineItem.productName
priceLabel.text = "\(lineItem.price)"
orderStatusLabel.text = lineItem.status
}
func applyStyle(_ style: LineItemTableViewCellStyle) {
switch style {
case .cart:
orderStatusLabel.textColor = UIColor.black
orderStatusLabel.isHidden = true
isUserInteractionEnabled = true
accessoryType = .none
case .deliveryStatus:
orderStatusLabel.textColor = UIColor.red
orderStatusLabel.isHidden = false
isUserInteractionEnabled = true
accessoryType = .disclosureIndicator
case .orderHistory:
orderStatusLabel.textColor = UIColor.blue
orderStatusLabel.isHidden = false
isUserInteractionEnabled = true
accessoryType = .disclosureIndicator
}
}
}
The LineItemTableViewCell is being use by different tableView. Like CartViewController, DeliveryStatusViewController and OrderHistoryViewController. Each will call the cell.applyStyle(style:) for different style.
Let say we decide to allow user to review their order. We will add a new ReviewViewController using LineItemTableViewCell as well. But we also have to add a new .reviewOrder into our enum and switch.
func applyStyle(_ style: LineItemTableViewCellStyle) {
switch style {
case .cart:
orderStatusLabel.textColor = UIColor.black
orderStatusLabel.isHidden = true
isUserInteractionEnabled = true
accessoryType = .none
case .deliveryStatus:
orderStatusLabel.textColor = UIColor.red
orderStatusLabel.isHidden = false
isUserInteractionEnabled = true
accessoryType = .checkmark
case .orderHistory:
orderStatusLabel.textColor = UIColor.blue
orderStatusLabel.isHidden = false
isUserInteractionEnabled = true
accessoryType = .disclosureIndicator
case .reviewOrder:
orderStatusLabel.textColor = UIColor.blue
orderStatusLabel.isHidden = false
isUserInteractionEnabled = true
accessoryType = .disclosureIndicator
}
}
This add more lines to the applyStyle() and become more difficult to read. We are just toggling the properties of the UILabel and cell. Sometimes I wonder if I could have a default style and just change those that need to be change. Thanks to Jesse, I learn a new and better way of doing it.
So instead of using enum and switch we can actually use struct to do it. The new applyStyle() will look like this:
func applyStyle(_ style: LineItemCellStyle) {
orderStatusLabel.textColor = style.labelColor
orderStatusLabel.isHidden = style.hideLabel
isUserInteractionEnabled = style.allowInteraction
accessoryType = style.accessoryType
}
The LineItemCellStyle is a struct that preset all the common pattern.
struct LineItemCellStyle {
let labelColor: UIColor
let hideLabel: Bool
let allowInteraction: Bool
let accessoryType: UITableViewCellAccessoryType
init(labelColor: UIColor = UIColor.black, hideLabel: Bool = false, allowInteraction: Bool = true,
accessoryType: UITableViewCellAccessoryType = .none) {
self.labelColor = labelColor
self.hideLabel = hideLabel
self.allowInteraction = allowInteraction
self.accessoryType = accessoryType
}
static var cartStyle: LineItemCellStyle {
return LineItemCellStyle(hideLabel: true)
}
static var deliveryStyle: LineItemCellStyle {
return LineItemCellStyle(labelColor: UIColor.red, allowInteraction: false, accessoryType: .checkmark)
}
static var historyStyle: LineItemCellStyle {
return LineItemCellStyle(labelColor: UIColor.blue, accessoryType: .disclosureIndicator)
}
static var reviewStyle: LineItemCellStyle {
return LineItemCellStyle(accessoryType: .disclosureIndicator)
}
}
The advantage of this approach is a cleaner LineItemTableViewCell. We no longer have a massive switch statement but instead a custom struct to manage the different cell style. Default properties can also be preset in the init instead of settings it again and again.
If you notice that your custom UITableViewCell or any other switch and enum configuration is getting massively huge, consider this approach. I really like this and already refactor most of my project with this. It is a lot easier to write test for it as well!