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
}
}
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
}
}
}
}
}
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:):
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
}
}
}