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.

Just Stick It In A Stack View

I've discovered that —at least the way I'm calling things— no matter what I do, I just can't resize a UIPickerView after it's been instantiated, and instantiating it with the frame I want just gets ignored. I need to dig further into this because I'm clearly doing something wrong, but as I'm only working on this an hour a day, that's slow going.

So, in the spirit of making progress and not getting sidetracked with details that ultimately aren't that important, whenever I have to show my picker view, I call this method:

func showPickerView(frame: CGRect, sender: UITextField) {
      let stackView = UIStackView(frame: frame)
      stackView.axis = .vertical
      
      let safeInsetView = UIView(frame: CGRect(x: 0, y: 0, width: frame.width, height: view.safeAreaInsets.bottom))
      
      stackView.addArrangedSubview(pickerView)
      stackView.addArrangedSubview(safeInsetView)
      sender.inputView = stackView
  }

Fuck it. It works, so long as I set my calculator keyboard's frame height to 216 (the height of a picker view) plus the bottom safe inset area.

I hate magic numbers in my code, so I'll continue investigating this, but for now... it's fine.

I've decided that tomorrow will be my last day posting these. Fifty days of uninterrupted blogging... not bad. But I'm also pretty much at feature parity with the shipping version now, so I can start adding features instead.

So tomorrow, a wrap-up, and then back to your regularly scheduled programming.

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

Safe Area Insets

On Tuesday, I shared this image comparing the height of the two main input views (the calculator keyboard and the picker) in Per between an iPhone 8 and an iPhone 11:

"Screenshots from an iPhone 8 and iPhone 11 showing relative differences in input view height"

I could explicitly set the height of both input views to something like 240 instead of giving it a .flexibleHeight auto-resizing mask, but that didn't feel like the right approach. I asked about this in the Core Intuition Slack and Daniel Jalkut suggested I poke around in Xcode's view debugger to see what's going on there.

Here's what I've been able to surmise.

I layout a custom input view like CalculatorKeyboard as follows (it's really a set of nested stack views, but I'm leaving that out — details of the approach are from this Stack Overflow answer):

func setupView() {
    autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addButtons()
}

func addButtons() {
    let stackView = createStackView(axis: .horizontal)
    stackView.frame = bounds
    stackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addSubView(stackView)

    // —code to layout all of the buttons goes here—
}

This basically just sets the frame of the custom keyboard to that of the system keyboard's bounds. Note that it doesn't do anything to add a safe area inset at the bottom of the keyboard. The view debugger tells me this is 375×291 on an iPhone 11 Pro, and 375×216 on an iPhone 8, a difference of 75 points.

I'm way less specific about setting the frame of the picker. In fact, I just set:

textField.inputView = pickerView

and call it a day. The view debugger tells me its size is 375×216 on both iPhone 11 Pro and iPhone 8 — the same size.

I'm getting closer here. I think part of this has to do with the intrinsic content size of the custom keyboard vs the picker, and also that I should probably be doing more in the parent view controller than just hoping things work out with autolayout in the views.

I'll dig deeper on this 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.

Take Care Of Yourself

I'm taking a break from this work today because it feels like everything is falling apart right now and I need some time to sort out my own needs. That's way more important than not breaking the streak.

#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 Steps Forward, One Step Back

Papercuts are the little scratches you get as you plow headlong through the prickly underbrush of implementing features.

One such papercut that I sorted out this morning was a weird UIToolbar iOS 13 bug requiring me to set a size on the input accessory view's frame lest I get that annoying “unable simultaneously satisfy constraints” warning. Previously, you could instantiate the toolbar with a simple

let inputAccessoryView = UIToolbar()

but now you'll need to do something like:

let inputAccessoryView = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 100)).

I'm running into a weird bug, however, where the input view changes size on some devices between my custom calculator keyboard and the picker view:

"Screenshots from an iPhone 8 and iPhone 11 showing relative differences in input view height"

Notice how those input views are the same size on an iPhone 8 (on the left), for example, but very different on an iPhone 11 (on the right).

I'll dig into this some more 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.

Onwards and Upwards

Just a short update today: as planned, I added the input accessory view to the detail form today. This only adds a simple previous/next toolbar button to skip between text fields in the form. It was nice to use SF Symbols for the left and right carets!

That gets us to feature parity — but now we've got the papercuts to deal with, with the hope that I'll have this installed on my phone for testing by the end of the week.

Tomorrow I take care of planning what to tackle first!

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

One Step Closer

A couple of little calculator-related papercuts were sorted out today.

  1. The math operator keys (“+”, “–”, “⨉”, “÷”) now enable themselves when there's a left-hand-side operand for the equation, and disable themselves when both operands are present (or nothing is in the text field at all).
  2. Tapping into the next text field will solve for any equation entered into the current text field (or, if you've got something like “12+” entered, it'll clear the “+” symbol from the text field).
  3. Deleting through an equation no longer crashes the app when you delete the operator symbol.

The only thing I want to finish up before installing going back to clean up any other papercuts is to add an input accessory view —the toolbar that sits just on top of the iOS keyboard, like a hat— that includes a previous and next button to skip between fields, which I'll add 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.

I 💖 Property Observers

Slowly unravelling work on Per's calculator feature has been interesting. I had worked on getting the UX/UI of the calculator working before getting the actual arithmetic sorted out, such that certain keys would be enabled or disabled based on state, and then tried to add the math functionality on top of that, which made for a very messy pile of code.

I went through and removed anything that enables or disables keys, and cleared out the big, ugly updateResult() method that handled the actual (attempt at) arithmetic. I'm pushing a lot of the work into a property observer, and that's probably not going to be the final solution for this class, but it's really handy for simplifying how a thing should react to a change.

At any rate, here's how it's working now.

Every time a key in the keyboard is tapped, a character is appended to a currentString property — either a digit, a decimal, or an arithmetic operator. The main use for this string is to track what's in the relevant UITextField.

I'm using the didSet: observer on that property to parse that string every time it's updated, such that I can set a pair of Doubles for the left- and right-hand-side operands. This is done by checking a flag that tells me if an operator button was tapped. If that's false, we're setting the LHS operand; if it's true, we're setting the RHS operand.

When you tap an operator button, the class also makes a note of what that symbol was. Then, when you tap the equals button, it calls a much-simplified updateResult() method that looks at the value of the LHS and RHS operands, and performs the arithmetic based on the last operator button tapped (so, if you had tapped the + key, it'll add the operands) before resetting a few properties. The solution to that calculation is set as the new value of currentString (which sets this as a new LHS operand), and is also returned as a string (which the caller uses to update the target text field).

Oh, if you're creating a custom input view for your app and need a way to clear the text field, here's a handy recursive function you can use:

func clearTextField(_ sender: UIButton) {
    guard let isNotEmpty = target?.hasText else { return }
    if (isNotEmpty) {
        target?.deleteBackward()
        clearTextField(sender)
    }
}

Tomorrow, I'm going to add back automatic enabling and disabling of various keys based on the state of the keyboard.

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

Re-visiting The Calculator

On Wednesday, I laid out how the calculator feature would work. Then, yesterday, I added an equals button to the calculator keyboard, which changed how this all works. Here's how it should go:

  1. The user taps on a text field:
    • if it's empty, proceed to step 2.
    • if it's already got a first number (LHS operand) in it, proceed to step 3.
  2. Enter the LHS operand via the keypad; it shows up in the text field.
  3. Tap an operator symbol on the keypad; it shows up in the text field.
  4. If the user then:
    • switches to a different field, remove the operator symbol from the text field and go back to step 1.
    • enters a second number (RHS operand), proceed to step 5.
  5. As the user enters the RHS operand, it shows up in the text field.
  6. If the user then:
    • switches to a different field, perform the arithmetic and replace the text field contents with the result.
    • taps the equals button on the keypad, perform the arihtmetic and replace the text field contents with the result as a new LHS operand, then append the new operator symbol to the text field.

This is proving a little bit more complicated to untangle than I'd expected. The first equation entered calculates correctly, but then I've got too many flags and temporary variables to keep track of, and inevitably things get mixed up.

Tomorrow, I'm going to try to sketch out a better approach for this.

#per #perRewriteDiary #ios

Discuss...

Friday, March 6th marked three years of working remotely out of my home office. In prior years, I've answered a series of questions, but I've been struggling to write this year's post.

Read more...

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.

Untangling Stack Views

One thing I always have struggled with is visually reasoning about nested arrays — which, if you kinda squint and look at from the right angle, is very similar to what you've got with nested stack views.

The actual code to set up a stack view? Fairly straightforward, really. Apple did a nice job with this API. But untangling my own set up of the various horizontal and vertical stack views to create the keyboard was a bit... gross. I should have probably started by thinking about the top-level stack view in terms of columns rather than rows — blindly copy-pasting a solution from Stack Overflow often gets you to a solution while creating other problems, I guess.

At any rate, that's cleaned up now. I ended up exploring three options, and I'm not in love with any of them:

"Three options of calculator keyboard layouts"

For now I'm going with the one on the far right, with the equals button in its own column.

(Yes, I'm testing with the iPhone 8 Simulator right now. I always tend to test with an older device's screen size, though I don't have a good reason for why — but I will have to take care to ensure the layout makes sense with the home bar indicator thing on the iPhone X/Xs/11.)

Tomorrow, I get back to the actual calculator functionality!

#per #perRewriteDiary #ios

Discuss...

Enter your email to subscribe to updates.