Angelo Stavrow [dot] Blog

per

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.

I'm a model, you know what I mean

View controllers are fun to work on and all, but at the end of the day, they're only meant to mediate between the user and some data model. In Per, we've got two models that I can think of right now. Today, we focus on one.

The Product

Start with a protocol, say Apple. The Product model in Per is fairly straightforward, but sure, let's do the protocol-oriented programming thing. Here's what our protocol looks like:

protocol Product: Comparable {
	var quantity: Double { get set }
	var units: Unit? { get set }
	var price: Double { get set }
	var pricePerUnit: Double { get }

Why is units an optional? Because we only care if the units are of type UnitMass or UnitVolume right now — otherwise we treat the product as dimensionless, i.e., plain ol' units.

The Product has to conform to Comparable so that we can, uh, compare multiple Products — and here, we're starting with a simplistic implementation in an extension:

extension Product {
    var pricePerUnit: Double {
        get {
            let formatter = NumberFormatter()
            formatter.numberStyle = .currency
            let formattedPricePerUnit = formatter.string(for: NSNumber(value: price / quantity))
            return Double(truncating: formatter.number(from: formattedPricePerUnit!)!)
        }
    }
    
    static func <(lhs: Self, rhs: Self) -> Bool {
        // Naïve implementation, doesn't account for unit type
        return lhs.pricePerUnit < rhs.pricePerUnit
    }
    
    static func ==(lhs: Self, rhs: Self) -> Bool {
        // Naïve implementation, doesn't account for unit type
        return lhs.pricePerUnit == rhs.pricePerUnit
    }
}

This doesn't account for the fact that if a client tries to compare something of UnitLength.meters against UnitType.pounds, it should fail. Instead, I'm simply looking at comparing the price per unit, which is a computed property in the Product extension. I don't love the way I'm rounding the value of pricePerUnit here using a NumberFormatter, so if you have any suggestions, do let me know!

Tomorrow, I'll work on an initializer for the actual struct that implements the protocol, and aim to better handle the unit comparison.

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

Tabula Rasa

Rewriting Per from the ground up is going to be a learning experience, and I'm going to take this opportunity to write about the process as I go. I'm aiming to make one (relatively) small change per day, with the goal of having a functional MVP by the end of February 2020 that's not using any custom design work — i.e., using built-in animations, colour palettes, fonts, and icons.

Working this way —without custom design work— means that I can focus on design patterns, unit testing, and so on, moving quickly without getting blocked on UI decisions. It also de-couples the development work (which I'm doing) from the design work (which someone else is doing), so that we can move through this iteratively, but without deep dependency on each other's progress.

I should pause here to define the functional MVP. By the end of the month, I want to replicate the functionality of Per in its current iteration, plus one more feature, which means it should:

  • Provide a quick, easy way to compare price per unit (existing feature)
  • Handle unit conversion automatically (existing feature)
  • Allow simple arithmetic when entering price or quantity (existing feature)
  • Expand the number of compared products from two to “unlimited” (new feature)

So, I started with a single-view iOS app, added the bundle identifier, and then started ripping out Main.storyboard from everywhere. Thankfully, my podcast co-host Frank Courville posted a great article on the topic last month to follow.

View controllers all the way down

I also started implementing a “layered view controller” concept presented in another article of Frank's — this uses coordinator view controllers, that manage container view controllers or wrap context view controllers that are composed of content view controllers. Read Frank's article, and be sure to download the sample app. You'll need to sign up for his newsletter, but I've been subscribed for a couple of years now and it's all been high quality articles on iOS development.

Per isn't an especially complex app, so this may seem like overkill. I'm going to be building this iteratively, though, and while this does mean I'll be creating several view controllers, this method will keep things small and loosely coupled.

Today was spent setting up this hierarchy.

Specifically, I created a simple top-level Product List coordinator view controller that sets a UINavigationController as its root view controller, which in turn embeds a Product List context view controller.

That context view controller wrap a Product List content view controller that implements UITableViewController. Why a table view controller? That feels like the fastest way to add a list of several products for comparison. I didn't yet set up any delegates or data sources for that table view, nore did I create a Product detail view controller or any way to navigate to it.

I'll kick off work on that tomorrow by creating some very basic models.

#per #perRewriteDiary #ios

Discuss...

From Intelligencer:

If you let products spoil, or you decide you don’t like them anymore halfway through the box, or if you forget what drawer your huge package of batteries is in, then you’re not getting as much value out of your bulk purchase as you had planned. Your effective investment return is likely to be negative; you would have been better off paying more per unit to buy less.

— Source: Buying in Bulk: When Is It a Mistake?

It's this philosophy of “cheaper isn't necessarily saving you money” that drove the development of Per, my little price-per-unit utility calculator. From the marketing copy:

Just because you’re getting more for your money, doesn’t mean that it’s a good buy. Maybe you’re not sure you’ve got the space to store eighty rolls of toilet paper. Or that you can get through sixteen heads of lettuce before they spoil.

Per tells you just how much more you’re getting for your money, so you can make those decisions confidently.

(I'm still planning updates for the app. Stay tuned for V2.)

#behaviour #per

Discuss...