Sam Soffes

Automatic UI Updates with Value Types in Swift

Posted on

Value types are one of my favorite things in Swift. At first, I was resistant. It’s a much different way of thinking. Let’s look at a simple example that really shows the power.

I was recently working on a little control for entering in numbers on Apple Watch. Here's the code:

public struct NumberPad: Printable {

    // MARK: - Properties

    public var value: Double

    public let decimalPlaces: UInt

    public var description: String {
        let formatter = NSNumberFormatter()
        formatter.numberStyle = .CurrencyStyle
        formatter.maximumFractionDigits = Int(decimalPlaces)
        formatter.minimumFractionDigits = currencyFormatter.maximumFractionDigits
        return formatter.stringFromNumber(value)!
    }

    public var isEmpty: Bool {
        return value == 0.0
    }


    // MARK: - Initializers

    public init(value: Double = 0.0, decimalPlaces: UInt = 2) {
        self.value = value
        self.decimalPlaces = decimalPlaces
    }


    // MARK: - Manipulation

    public mutating func insert(place: Int) {
        let offset = pow(10.0, Double(decimalPlaces))
        var int = Int(value * offset)
        int *= 10
        int += place
        value = Double(int) / offset
    }

    public mutating func deleteBackwards() {
        let offset = pow(10.0, Double(decimalPlaces))
        var int = Int(value * offset)
        int /= 10
        value = Double(int) / offset
    }
}

It's really straight forward. There's a little bit of math to insert or delete numbers. It's not bad though. Here's how you use it:

var pad = NumberPad()
pad.insert(7) // $0.07
pad.insert(5) // $0.75
pad.insert(0) // $7.50
pad.deleteBackwards() // $0.75

Easy enough. In my view controller, I use a button's action to call insert on my NumberPad that's an instance variable. Here's an action in the view controller:

func one(sender: AnyObject?) {
    numberPad.insert(1)
}

Now we need to connect the number pad's label to the number pad's formatted value. Traditionally I would have reached for KVO or the delegate pattern.

Here's the awesome part. Swift's powerful support for value types makes this so easy. Let's look at the code in the view controller where we declare the NumberPad instance variable.

private var numberPad = NumberPad(value: 0, decimalPlaces: 0) {
    didSet {
        amountLabel.setText(numberPad.description)
    }
}

That's it. Whenever the number pad changes, the label changes too! Because it's a struct, which is a value type, didSet gets called whenever it changes. So simple.

If you look back at the implementation for insert and deleteBackwards in NumberPad above, you can see they both have the mutating keyword. I love how clear it is that calling those will cause didSet to get called.

Say we had the following:

let pad = NumberPad()
pad.insert(1) // Error!

The second line is an error. Calling insert mutates the struct which isn't allowed since it was declared with let. If we change that to var, then it's totally fine. This is great if you need to have something mess with a struct and then send it to the background for processing. You could send an imutable copy (or mutable if you wanted) to the background for processing.

Hopefully this shows some of the power of value types in Swift. I highly recommend watching Andy and Justin’s talks from Functional Swift 2014. Those have been the most helpful things to me in my functional Swift learning.

I'd love to hear what you think about value types in Swift. I'm @soffes. Say hi.