[SC]()

iOS. Apple. Indies. Plus Things.

Solving Small Problems with Small Tuples

// Written by Jordan Morgan // Mar 20th, 2022 // Read it in about 2 minutes // RE: SwiftUI

One thing that has always miffed me in my decade plus of writing Objective-C is the amount of friction it felt like I encountered when I had to throw together a one-off type. Software always changes, but sometimes there are situations where it feels like you’ve got an incredibly deliberate pack of data to return to a caller, and it’ll never be seen or heard from by anybody else.

Please indulge me with a NSRunLoop down memory lane for just a second.

I had a spot recently, where in an individual control, I had three buttons. The last two buttons had a few things injected into them, based on the first button’s type. In pseudo code, something like this:

if buttonOne.design == .someTypeA {
    buttonTwo.design == .someTypeB
    buttonThree.design == .someTypeC
} // and so on

The point isn’t to dissect that code, or recommend a switch or anything - it’s simply to show I’ve got two compartmentalized things that only this control needs to ever know about internally. And that is, I have three buttons and depending on their index and what that first button has set, the last two will have this thing set.

So in Objective-C, where did that leave me? An NSDictionary with magic values, accessed by index? A subclass of NSObject? The thought of making those things inline or reaching for File -> Add New File always felt so heavy for such a tiny little piece of code that nobody else would care about.

What I really wanted to do was cozy up with struct types, before Swift made them come back in style:

typedef struct {
    SJCButtonDesignType middleDesign;
    SJCButtonDesignType trailingDesign;
} SJCTrailingButtonTypes;

And then maybe so something like this:

- (SJCTrailingButtonTypes)trailingButtonConfigurations {
    // Code to figure out what the second and third button should show
    SJCTrailingButtonTypes buttonTypes;
    buttonTypes.middleDesign = SJCButtonDesignWillDo;
    buttonTypes.trailingDesign = SJCButtonDesignDone;

    return buttonTypes;
}

Then, I’d apply those where I called that method from. Good enough, even though it looks a little funky.

Tuple Time

All of this is a longer winded way of me saying I was happy to embrace Swift’s flexibility and its type system, especially for one-off scenarios like these. And, there are times where I just love me a good ol’ Tuple type, just for this. Here’s how I do it in Swift:

private func statusForTrailingButtons() -> (middleButton: WorkItem.Status, lastButton: WorkItem.Status) {
    // Code to figure out what the second and third button should show
    return (.WillDo, .Done)
}

And then, I can get the benefit of type safety, readability and that lightweight-idness (that’s a word, right?) that I’m after:

let trailingData = statusForTrailingButtons()

// In a SwiftUI View down the road...
MyAwesomeButton(type: .ToDo)
MyAwesomeButton(type: trailingData.middleButton)
MyAwesomeButton(type: trailingData.lastButton)

I like that a little better than this:

private func statusForTrailingButtons() -> ([WorkItem.Status]) {
    return [.WillDo, .Done]
}

// Then later on
let trailingData = statusForTrailingButtons()

// In a SwiftUI View down the road...
MyAwesomeButton(type: .ToDo)
MyAwesomeButton(type: trailingData.first ?? .Unset)
MyAwesomeButton(type: trailingData.last ?? .Unset)

And this, for some reason that I can’t really put my finger on, feels a little too heavy for such a small thing:

struct TrailingButtonConfigurations {
    let middleButton: ButtonDesign
    let trailingButton: ButtonDesign
}

Here, this one just doesn’t look as nice to me:

private func trailingButtonDesignFor(index: Int) -> ButtonDesign {
    // Code
}

// In a SwiftUI View down the road...
MyAwesomeButton(type: .ToDo)
MyAwesomeButton(type: trailingButtonDesignFor(index: 1))
MyAwesomeButton(type: trailingButtonDesignFor(index: 2))

Albeit, all of this could just be moved into a constructor - but then you’d have two more properties to carry around, but at least at the call site you’d make things prettier:

init(primaryButtonDesign: ButtonDesign) {
    middleButtonType = /* Code to figure that out */
    trailingButtonType = /* Code to figure that out */
}

// In a SwiftUI View down the road...
MyAwesomeButton(type: .ToDo)
MyAwesomeButton(type: middleButtonType)
MyAwesomeButton(type: trailingButtonType)

So, yeah. I love Tuples for tiny, small and centralized tasks. Or hey, maybe there was a better way to do all of this in the bigger picture. Probably, but I’ve always been much better at creating products than writing code. And the code I do write? I want it to be predictable and expressive.

Until next time ✌️

···

Spot an issue, anything to add?

Reach Out.