Angelo Stavrow [dot] Blog

Missives and musings on a variety of topics.

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.

More Fun With Custom Input Views

Yesterday I started work on replacing the system decimal-pad keyboard with something more like a calculator (which lives in a class called CalculatorKeyboard).

What I'm learning is this: the easy part is adding the keys. The hard part is accounting for their behaviour.

For example: the operator keys (i.e., +, −, ×, ÷) and delete key shouldn't be enabled if there's no text in the text field. But I can't actually hook into the UITextField to see what the current text is programmatically; instead, I need to track that with each keypress.

Which seemed straightforward... until I realized that the user could tap the clear button in the textfield, and I'd have no way of knowing beyond constantly checking target.hasText or having something happen in the text field's textFieldShouldClear(_:) delegate method (I chose the latter option).

Or that the user could type something into the field, navigate to another field, and then come back to that first field. If the initializer for the calculator keyboard doesn't account for this by having an argument for whatever the current text of the text field may be, then its internal representation of what is being type will be out of sync with the textfield's text property.

I also want the operator keys to be disabled just after one is tapped, but then re-enabled if you add a number after that. That way, you can't enter multiple operators between operands in the equation.

So today's work largely focused on handling this kind of behaviour. I always feel a bit uncomfortable working on this stuff because I might miss an edge case somewhere, but using property observers to trigger updates helps a lot. It changes my mental model of the class to a state machine of sorts, which is a lot easier to sketch — and I've always found that if I can sketch out a decision tree or flowchart or whatever, I can write the relevant code fairly easily.

Tomorrow, I start work on implementing the actual calculation!

#per #perRewriteDiary #ios

Discuss...

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.

Custom Input Views

In the currently-shipping version of Per, I use an inputAccessoryView that adds a couple of buttons for things like navigating between fields and, more importantly, keys for performing simple arithmetic.

I started off my work today in re-implementing this and then realized that, hey, rather than doing this, why not instead replace the system's .decimalPad keyboard that I'm currently using with a calculator-type keyboard?

It could look something like this:

 --- --- --- ---
| 1 | 2 | 3 | + |
 --- --- --- ---
| 4 | 5 | 6 | - |
 --- --- --- ---
| 7 | 8 | 9 | ⨉ |
 --- --- --- ---
| . | 0 | ⌫ | ÷ |
 --- --- --- ---

In case the poor ASCII art doesn't make it very clear, the intention is having the decimal keypad, along with an extra column of arithmetic keys along the right.

One thing I got some comments on was that people didn't really take note of the calculator feature, likely because the keys in the input accessory view were too subtle. Doing it this way should be more prominent and hopefully improve feature adoption.

I started working on a CalculatorKeyboard class based on this Stack Overflow answer and it's working nicely well so far. It also gives me the opportunity to skin the keyboard to better match the rest of the app's design later on, though the picker view will probably look out of place if I go too far on this. Can you even skin a picker view? I haven't really looked into this.

With the system decimal keypad's functionality replaced, I'll start work on adding the calculator keys and their functionality tomorrow.

#per #perRewriteDiary #ios

Discuss...

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.

Fun With textDidChangeNotification

The first papercut I'm tackling since revisiting the list yesterday is a FIXME that affects the Add button in the product detail form view. We want the button to be disabled unless the form has “valid input”, which is to say, it's got at least a price and quantity set.

The UITextFieldDelegate lets you hook into various events related to a text view, and the one we're most interested here is when the text was changed. I kicked off this work by running a callback on textFieldDidEndEditing(_:) method that would call a method in the form view's delegate, updateVolatileFormData() — this struct provides a temporary datastore for the price, quantity, and (optionally) units as Strings from each text field.

Instead, on each change of the text in the form, I can call that same update method — and do a little bit of additional checking. If the form data is complete, enable the Add button; otherwise, disable it.

So how do we hook in to this event? At the end of my form view's setupView() method, I subscribe to textDidChangeNotification:

func setupView() {
    /* Set up the controls and other views here */

    NotificationCenter.default.addObserver(
        self,
        selector: #selector(textDidChange(_:)),
        name: UITextField.textDidChangeNotification,
        object: nil
    )
}

And then I create that selector:

@objc func textDidChange(_ notification: Notification) {
    if let textField = notification.object as! UITextField? {
        switch textField.tag {
        case 100:
            datasource?.quantity = textField.text ?? ""
        case 101:
            datasource?.units = textField.text ?? ""
        case 102:
            datasource?.price = textField.text ?? ""
        default:
            print("Unknown tag")
        }
        
        delegate?.updateVolatileFormData()
    }
}

Notifications include an object property that you can pass to subscribers, and in this case, that object payload is the UITextField that triggered the notification (which is why I force-cast notification.object as a UITextField? here — remember that I'm subscribing to UITextField.textDidChangeNotification, so I'm pretty sure that cast is guaranteed to succeed).

Now, here's a fun fact.

For reasons that I'm not clear on, these notifications are not fired when you change the content of a text field like so:

someTextField.text = "blah blah blah"

Instead, if you want to set the value of your text field programmatically, you'll have to do it this way:

someTextField.text = ""
someTextField.insertText("blah blah blah")

That will fire the text-changed notification. Because I'm using a picker view whose didSelectRow: delegate method sets the value of the units text field, I had to change it from the former to the latter approach.

I guess this makes sense because inserting/removing text counts as changing the text that's already there, whereas the straight assignment in the first approach doesn't because that String object may not even exist yet? I'm not entirely sure, but it's been a source of confusion in iOS for a while.

Anyways, with this work done, you can't crash the app if you hit the Add button before you've got appropriate input values in your form. The last bit of work to get this to feature parity with the shipping version of Per is adding an inputAccessoryView for navigating fields and enabling the simple calculator feature, so I'll kick off work on that tomorrow, then sort out all of the remaining papercuts before I move on to new v2.0 features (beyond being able to add multiple products, that is).

#per #perRewriteDiary #ios

Discuss...

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.

Revisiting the Papercut List

Two weeks ago, I paused on writing code and took stock of the “papercuts” that I wanted to deal with before moving forward. Now that my work on automatic unit conversion is wrapped up, it seems like a good time to do it again.

That said, there wasn't much to add to the list beyond stuff I'd already noted here:

  • The picker view code in ProductDetailContentViewController could be factored out into a separate class to make it a bit more maintainable. This isn't totally necessary, but it makes things a little cleaner and easier to maintain.
  • One thing that is necessary is to change the way I set units text field in the ProductDetailFormView when the picker's didSelectRow: value. I'm thinking of just adding a setUnitsTextFieldValue() method to the form view that can be called here.

This joins the outstanding issues:

  • The Add button should only be enabled when the form has sufficient information to create a product.
  • The displayError() method should be available to all view controllers.
  • The form view's UITextFieldDelegate code should be refactored into a separate class.

In the last two weeks, I've closed three papercut issues:

  • Created a custom table view cell for the product list.
  • Marked ProductList.add() as throws.
  • Refactored the UI layout code in the product detail content view controller.

So, I started with six papercuts, closed three, and am left with five. As Brent Simmons says, bug math is weird.

Again, these aren't necessarily bugs, but they are quality-of-life improvements in the sense that it'll make it easier to maintain and reason about the code.

There's also one UX issue to think about. The product list view has buttons in the navigation bar, and the product detail view has them under the form. I really dislike nav-bar buttons on anything but a 3.5” iPhone screen because of reachability issues, so I'd like to rethink how to combine this into, say, a floating button that changes functionality as you go from screen to screen. This isn't a goal for this function-focused initial rewrite, though.

In fact, beyond these papercuts there's only one feature left to implement to have feature parity with the currently shipping version of Per: adding a very simple calculator in an input accessory view.

Tomorrow I'll work on enabling the Add button only when there's enough form content to create a new ProductItem.

#per #perRewriteDiary #ios

Discuss...

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.

Optionally Conditionally Yours

Here's where we're at right now.

When the form to add a product to the list is first shown, Per inserts a segmented control that allows the user to choose whether the product in question is sold by weight, by volume, or by units. When adding a second or third product, this isn't shown, to enforce that all products are comparable on a price-per-unit basis.

This first product sets the unitType property (either UnitMass or UnitVolume) for the product list.

After that initial product is added, showing the form should obey the following rules:

  1. If the product list's unitType is nil (dimensionless units), keep the unit text field disabled.
  2. Otherwise, enable the unit text field and show the appropriate picker view (either with weight or volumne units, based on the list's unitType).

To do this, I made a couple of changes to the the product detail content view controller (which creates and presents the form view) delegate:

  1. Added a listUnitType: Unit? property that gets the product list's unitType;
  2. Added a setPickerTo(_ unit: Unit?) method that looks at the type of unit passed in, and then sets the picker view's data source appropriately.

The added method also allowed me to refactor the delegate method that was called by the segmented control that I discussed here.

Now, when the form view is created, I can add a check in the delegate property observer:

weak var delegate: ProductDetailContentViewControllerDelegate? {
    didSet {
        if self.delegate?.numberOfProductItems == 0 {
            insertUnitTypeSelectorControl()
            formHeight = getHeight(of: formStackView)
        } else {
            if let listUnitType = self.delegate?.listUnitType {
                unitType = listUnitType
                unitsTextField.text = listUnitType.symbol
                unitsTextField.isEnabled = true
            }
        }
    }
}

That checks for and conditionally sets an optional unitType property on the form when the delegate is set, if it's not the first product being added and the product list has a unitType. If the checks pass, it enables the units text field and sets it to some value.

Then in the textFieldDidBeginEditing(:) delegate method, I can check the unitType property when a user taps on the units text field and call delegate?.setPickerTo(unitType) to setup the picker view's units.

And with that, automatic unit conversion now works! Tomorrow, I'm going to go through all of this and make a list of what new papercuts have come up.

#per #perRewriteDiary #ios

Discuss...

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...

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.

Improving the Picker View

Yesterday I got a spike solution to show the UIPickerView showing when weight or volume units were selected in the form detail view. The form view doesn't actually set the ProductList's unitType yet, so today I made some progress toward that.

First, the UI needs to give the user the ability to select the units for the product they're entering. This means populating the picker view's data source with the appropriate units based on what the selection is for unit type (as set by a UISegmentedControl).

This means adding some kind of backing store for some subset of each type of units. I initially did this by adding two Dictionary objects with a String description of the unit as a key, and the symbol for that particular unit type (i.e., UnitMass.kilograms.symbol) as the value, figuring that both can be shown in the picker view, and the symbol alone shown in the form's unit text field.

Then, when the user makes a unit-type selection, a delegate method is called that:

  1. Creates an array where each element is the concatenation of the key and value for each dictionary entries;
  2. Sets that array as the data source for the picker view;
  3. Creates a second array of just the value for each dictionary entry; and
  4. Calls the picker's delegate's didSelectRow: method to set the unit text field's value.

Turns out

Here's a fun thing I forgot about! If you call map() on a Dictionary, the resulting array isn't guaranteed to be in the same order as the input collection.

If that's important, you can use a KeyValuePairs collection, which was renamed from DictionaryLiteral in Swift 5 (here's the proposal: SE-0214).

So here's what these not-really-a-dictionary dictionaries look like:

var pickerView: UIPickerView!
var pickerDataSource = [String]()        // For setting picker options in titleForRow:
var pickerTextFieldOutput = [String]()   // For setting units text field in didSelectRow:

let pickerWeightDataSource: KeyValuePairs = [
    "kilograms": UnitMass.kilograms.symbol,
    "grams": UnitMass.grams.symbol,
    "pounds": UnitMass.pounds.symbol,
    "ounces": UnitMass.ounces.symbol
]
    
 let pickerVolumeDataSource: KeyValuePairs = [
    "liters": UnitVolume.liters.symbol,
    "centiliters": UnitVolume.centiliters.symbol,
    "milliliters": UnitVolume.milliliters.symbol,
    "gallons": UnitVolume.gallons.symbol,
    "quarts": UnitVolume.quarts.symbol,
    "pints": UnitVolume.pints.symbol,
    "fluid ounces": UnitVolume.fluidOunces.symbol
]

Again, this is an ongoing spike solution, so it doesn't take localization into account — beyond the (American-)English descriptive names as keys,  Foundation actually provides for separate US and Imperial volume units, so that you can convert from e.g. UnitVolume.gallons and UnitVolume.imperialGallons — this will be sorted out later.

Here's the functional stuff for setting the picker view's data source and the unit text field's value:

// Swap data source contents based on `UISegmentedControl` selection
func setUnitType(_ sender: UISegmentedControl, target: UITextField) {
    var currentlySelectedRow = pickerView.selectedRow(inComponent: 0)

    switch(sender.selectedSegmentIndex) {
    case 0:
        pickerDataSource = pickerWeightDataSource.map({ key, value in "\(key) (\(value))" })
        pickerTextFieldOutput = pickerWeightDataSource.map({ key, value in "\(value)" })
        pickerView.reloadAllComponents()
    case 2:
        pickerDataSource = pickerVolumeDataSource.map({ key, value in "\(key) (\(value))" })
        pickerTextFieldOutput = pickerVolumeDataSource.map({ key, value in "\(value)" })
        pickerView.reloadAllComponents()
    default:
        pickerDataSource = [""]
        return
    }

    if pickerView.numberOfRows(inComponent: 0) <= currentlySelectedRow {
        currentlySelectedRow = pickerView.numberOfRows(inComponent: 0) - 1
    }
    pickerView.delegate?.pickerView?(self.pickerView, didSelectRow: currentlySelectedRow, inComponent: 0)
    target.becomeFirstResponder()
}

So that's working nicely. Changing selection between weight and volume units works gracefully, and the text field updates as soon as the picker view is shown or changed, so that it's never in a weird state (as can sometimes happen in the current shipping version of Per).

Tomorrow, I'll actually hook this sucker up to set the ProductList's unit type, which should —I think— be all I need to get automatic unit conversion working. Then, I can focus on refactoring this stuff into something a little less hack-y.

#per #perRewriteDiary #ios

Discuss...

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.

Spiking a Picker View

As I mentioned yesterday, I'm a little stuck trying to implement a UIPickerView to let you choose the units for a product. Maybe I was trying a bit too hard to be clever about this, so I'm going to try a spike solution today and see how that works out.

The ProductDetailContentViewController shows a ProductFormDetailView that contains the input fields for entering a new product. When the user taps the input field, I want to show a UIPickerView that presents some options based on the unit type of the list. That unit type is selected when you add the first product via a UISegmentedControl.

I'm going to start by making the ProductDetailContentViewController conform to UIPickerViewDelegate and UIPickerViewDatasource. That means I have to add a couple of methods, so I let Xcode add the stubs:

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    <#code#>
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    <#code#>
}

I also declare a pickerView and, in the view controller's viewDidLoad() method, have its delegate and data source set to self. For now, the data source will be an array of strings:

var pickerView: UIPickerView!
let pickerDataSource = ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"]

override func viewDidLoad() {
    super.viewDidLoad()

    /* --- Some setup for other views --- */

    pickerView = UIPickerView()
    pickerView.delegate = self
    pickerView.dataSource = self
}

Okay, with that, I can fill out those protocol stubs:

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return pickerDataSource.count
}

There's an important protocol method missing here, though — the one that shows something in each row of the picker view:

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return pickerDataSource[row] as String
}

That should be enough to get Option 1, Option 2, Option 3, etc., to show in the picker view. Now we just need to add that picker view to the view hierarchy.

Here's where things get a little bit complicated. We only want the picker view to show when the following criteria are met:

  1. The unitType of the ProductList is not dimensionless (i.e., is of UnitMass or UnitVolume)
  2. The user taps on the units field in the ProductDetailFormView

Right now the units field is disabled, so I start by adding logic to the UISegmentedControl's action such that it gets enabled if the user chooses something other than “units”. I also add an showPickerView() delegate method on the view controller that its subviews can call, but all it does for now is print a success message to the console.

Now, I can add logic to the textFieldDidBeginEditing() event listener to show the picker view when someone taps on the units field:

func textFieldDidBeginEditing(_ textField: UITextField) {
    if textField.tag == 101 {
        delegate?.showPickerView()
    }
}

That calls the delegate method successfully, so I add the necessary code to show the picker view:

func showPickerView() {
    view.addSubview(pickerView)
    pickerView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        pickerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
        pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
        pickerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
    ])
}

A test in Simulator doesn't show the picker view at first... because it's behind the keyboard. That makes sense.

In the shipping version of Per, I set the keyboard to be the picker view — this avoids any jarring appearing/disappearing of the keyboard as you go from text field to text field. So in fact, that deeply simplifies things: I can pass the calling UITextField into the showPickerView() method and just set its inputView to the picker view:

func showPickerView(_ sender: UITextField) {
    sender.inputView = pickerView
}

And that works like a charm. When the units field is enabled and tapped, I see a picker view. When another text field is tapped, I see the decimal keyboard.

I'll add one final protocol method, so that the text field can be set to the selection in the picker view:

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    productDetailFormView.unitsTextField.text = pickerDataSource[row]
}

This works, but is a bad solution: I'm reaching into the subview to directly manipulate one of its controls. The view controller should know nothing about its subviews, because if something changes in the subview, then that could break everything. I added a FIXME here to update this, and it'll probably involve a property observer on something like VolatileFormData to update text fields.

For now, this can wait — I want to continue building off a working solution. Right now the segmented control only shows for the first product you choose, so tomorrow I'll work on making this actually set the ProductList's unitType property so that subsequent products use these units and show the picker view.

#per #perRewriteDiary #ios

Discuss...

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.

Bleh

I was really hoping to wrap up the conversion feature today, but I'm struggling to get a UIPickerView working the way I'd like.

Essentially, I want to be able to set the rows of the picker view based on the UISegmentedControl's selected segment, but for whatever reason I just can't get it to work the way I'd like.

I'm also feeling very distracted today, so I've been having a hard time figuring out just why this isn't working. That's... okay. I mean, it's a little frustrating, but it's okay. I think part of this is coming from trying to force something so that I can write about it, instead of allowing myself the time to go deep and understand what's going on.

This is the last day of the month, but I'm going to continue journaling on a daily basis at least until this functional rewrite is done.

#per #perRewriteDiary #ios

Discuss...

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.

Two days to go

Okay, so tomorrow's the end of the month. I'd like to get to the point where automatic unit conversion is working, but I don't think I'll get there in two hours. One way or the other, I didn't get to the point where the app functionally recreates the features of the currently shipping version of Per, but that's okay! I made a huge amount of progress and I'm really happy about that.

Today, I implemented the UI for picking the unit type when you add your first item to the product list. When the view controller sets the view's delegate, I have a didSet property observer that checks the delegate's numberOfProductItems property and, if there aren't any, inserts a UISegmentedControl where users can choose whether they want weight, volume, or dimensionless units. And it works really well!

Tomorrow, I'll set it up such that choosing a non-dimensionless option in the segmented control lets the user choose a weight or volume unit. Given that tomorrow is Saturday, I may stretch it past the usual hour to get the unit-conversion feature sorted out. There's a lot less complexity this time around for several reasons, so I don't think it'll take too long.

#per #perRewriteDiary #ios

Discuss...

Enter your email to subscribe to updates.