Chapter 12: Bronze Challenge

Here is my solution. In trying to comply with MVC I thought it best to implement the code in ItemCell.swift. Similar to viewDidLoad() this subclass has layoutSubviews(). I simply overwrote this function and added a call to update the valueLabel text color. Unfortunately, layoutSubviews is called for each subview, hence here its called 3 times (for each UILabel). If anyone has a better solution please share it.

ItemCell.swift

import UIKit

class ItemCell: UITableViewCell {
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var serialNumberLabel: UILabel!
    @IBOutlet var valueLabel: UILabel!
    
    func updateValueLabelTextColor() {
        if let text = valueLabel.text {
            let startIndex = text.index(text.startIndex, offsetBy: 1)
            if let value = Double(text.substring(from: startIndex)) {

                if value < 50 {
                    valueLabel.textColor = UIColor.green
                } else {
                    valueLabel.textColor = UIColor.red
                }
            }
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        updateValueLabelTextColor()
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        nameLabel.adjustsFontForContentSizeCategory = true
        serialNumberLabel.adjustsFontForContentSizeCategory = true
        valueLabel.adjustsFontForContentSizeCategory = true
    }
}

This is a very good solution, thanks!

Hi, I like it, very simple. I had a problem though because I had a cell at the end with no value so I added an if statement:

func updateValueLabelTextColor() {
    if let text = valueLabel.text {
        if text.unicodeScalars.count > 0 {
            let startIndex = text.index(text.startIndex, offsetBy: 1)
            if let value = Double(text.substring(from: startIndex)) {
                    
                if value < 50 {
                    valueLabel.textColor = UIColor.green
                } else {
                    valueLabel.textColor = UIColor.red
                }
            }
        }
    }
}

Hope this helps someone else!

Hello, everyone. I think there is a better solution. Problem with first solution is code dependency. If ItemViewController will change, for example by replacing $ sign with euro sign, you will have to change ItemCell too. It can leads to many errors.

I added method colorize(_:withColor:) to ItemCell.
Here is my ItemCell.swift file:

class ItemCell: UITableViewCell {
@IBOutlet var nameLabel: UILabel!
@IBOutlet var serialNumberLabel: UILabel!
@IBOutlet var valueLabel: UILabel!

enum CellLabelType: Int {
    case Name
    case SerialNumber
    case Value
}

override func awakeFromNib() {
    super.awakeFromNib()
    
    nameLabel.adjustsFontForContentSizeCategory = true
    serialNumberLabel.adjustsFontForContentSizeCategory = true
    valueLabel.adjustsFontForContentSizeCategory = true
}

func colorize(_ labelType: CellLabelType, withColor color: UIColor) {
    switch labelType {
    case .Name:
        nameLabel.textColor = color
    case .SerialNumber:
        serialNumberLabel.textColor = color
    case .Value:
        valueLabel.textColor = color
    }
}

}

In ItemsViewController i use UITableViewDelegate method tableView(_:willDisplay:forRowAt:):

 override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
                        forRowAt indexPath: IndexPath) {
    let rowNumber = indexPath.row
    let item = itemStore.allItems[rowNumber]
    let itemCell = cell as! ItemCell
    if item.valueInDollars < 50 {
        itemCell.colorize(.Value, withColor: UIColor.green)
    } else {
        itemCell.colorize(.Value, withColor: UIColor.red)
    }
    
}

Hope this helps someone.

Here’s my solution, which only adds a few lines of code to enable this functionality. If someone could please comment on whether this follows the MVC design pattern or not, the feedback is greatly appreciated.

In the ItemCell.swift class, I added a computed property of type Bool, which uses a ternary operator to set the textColor property of the valueLabel based on whether the item is less than or equal to 50:

class ItemCell: UITableViewCell {
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var serialNumberLabel: UILabel!
    @IBOutlet var valueLabel: UILabel!
    
    var isUnderFifty: Bool = false {
        didSet {
            valueLabel.textColor = isUnderFifty ? UIColor.green : UIColor.red
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        nameLabel.adjustsFontForContentSizeCategory = true
        serialNumberLabel.adjustsFontForContentSizeCategory = true
        valueLabel.adjustsFontForContentSizeCategory = true
        
    }
    
}

ItemsViewController.swift only job is to determine whether the item’s value is less than or equal to 50 and set ItemViewCell’s ‘isUnderFifty’ property based on that operation:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    // Get a new or recycled cell
    let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
    
    // Set the text on the cell with the description of the item that is at the nth index of items, where n = row this cell will appear in on the table view
    
    if indexPath.row == itemStore.allItems.count {
        cell.textLabel?.text = "No more items!"
        cell.detailTextLabel?.text = ""
        cell.textLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
    } else {
        let item = itemStore.allItems[indexPath.row]
        
        cell.isUnderFifty = (item.valueInDollars <= 50)

        cell.nameLabel.text = item.name
        cell.serialNumberLabel.text = item.serialNumber
        cell.valueLabel.text = "$\(item.valueInDollars)"
        
    }

    return cell
}

Thanks joshuaflores, nice solution. That is what was asked for in the book. There are two things that could be improved. According to Apple’s Documentation layoutSubviews() should be avoided. Instead either setNeedsLayout() or layoutIfNeeded() should be used (https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews). It works also. For cases the preceding is not $ but a different currency (more characters) one can iterate through the string and break when a number appears:

if let text = valueLabel.text {
            for i in 0..<text.characters.count {
                let startIndex = text.index(text.startIndex, offsetBy: i)
                if let value = Double(text.substring(from: startIndex)) {
                    if value < 50 {
                        valueLabel.textColor = UIColor.green
                    } else {
                        valueLabel.textColor = UIColor.red
                    }
                    break
                } else {
                    valueLabel.textColor = UIColor.black
                }
            }
        }