The Per Rewrite Diaries: Day 32

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.

Setting Units

Yesterday, I set each entry in my unit KeyValuePairs to look something like this:

"kilograms": UnitMass.kilograms.symbol

And that way I could directly get the String representation of the unit's symbol (in this example, “kg”) to drop into the picker view and text field.

Instead, I could make the entries look like this:

"kilograms": UnitMass.kilograms

When I need to get the string symbol for the unit, I can do that — no need to do that work ahead of time.

Now, with the addition of an optional tuple selectedUnit: (String, Unit), I can handle setting the units for the first product (and thus the unit type for the entire ProductList) in the picker view's didSelectRow: delegate method:

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    // FIXME: We're going through both unit data sources when we only need to go through one.
    _ = pickerWeightDataSource.contains { key, value in
        if (value.symbol == pickerTextFieldOutput[row]) {
            selectedUnit = (key, value)
            return true
        }
        return false
    }
    
    _ = pickerVolumeDataSource.contains { key, value in
        if (value.symbol == pickerTextFieldOutput[row]) {
            selectedUnit = (key, value)
            return true
        }
        return false
    }
    
    // FIXME: We're reaching into the subview to directly manipulate a textfield. THIS IS BAD!
    productDetailFormView.unitsTextField.text = pickerTextFieldOutput[row]
}

There are now a couple of FIXMEs in that one delegate method. I don't love that I'm going through both the picker's weight and volume data sources to see which symbol we've got; the method should be smarter than this.

So I can fix that this way, and —because Swift lets you define a function within a function— can clean up the duplicated code at the same time:

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    func checkSymbolAndSetUnit(key: String, value: Unit) -> Bool {
        if (value.symbol == pickerTextFieldOutput[row]) {
            selectedUnit = (key, value)
            return true
        }
        return false
    }
    
    if (pickerTextFieldOutput[0] == pickerWeightDataSource[0].value.symbol) {
        _ = pickerWeightDataSource.contains { key, value in
            checkSymbolAndSetUnit(key: key, value: value)
        }
    } else {
        _ = pickerVolumeDataSource.contains { key, value in
            checkSymbolAndSetUnit(key: key, value: value)
        }
    }
    
    // FIXME: We're reaching into the subview to directly manipulate a textfield. THIS IS BAD!
    productDetailFormView.unitsTextField.text = pickerTextFieldOutput[row]
}

That feels much cleaner. Since pickerTextFieldOutput is an array of all the unit symbol, I can test to see if the first element matches the symbol of the first element in my pickerWeightDataSource (a KeyValuePairs collection). If it is, find the right UnitMass and set that as the selectedUnit. If it's not, go through the pickerVolumeDataSource instead.

That sorts things out for setting the units on the first product and the unit type for the ProductList, but then I can't choose the units for subsequent products that I add to the list. I'll work on that tomorrow!

#per #perRewriteDiary #ios

Discuss...