Embracing the AppKit Ways
Earlier today, I was posting some ad-hoc thoughts about how I prefer SwiftUI layout over programmatic AppKit on Mastodon. I expected this to become a blog post later today.
But instead, I find myself thinking along the lines of: “am I holding this wrong?”
I’m programmatically setting up a list of radio buttons. A radio button group is only loosely associated – they need to share the target
and action
, and that’s that.
To identify radio button is which, you may use their tag
. That helps to figure out which one was selected when the action
is triggered.
An alternative way to identify which radio button in the group was selected, you can also ===
compare for object identity of the action sender. This requires storing each radio button as a property, though.
I was working without storing radio buttons for a couple of days. Their tag
was (and is!) sufficient to handle the action properly. Also, I don’t enjoy the imperative setup of NSButton
to become a radio button, so I abstracted that away – and now I don’t want to use this differently to capture the reference in a property instead.
How, then, do you toggle a radio button of a group programmatically if you don’t have a direct reference?
Turning a specific radio button .on
doesn’t suffice – you also need to turn all the others .off
. For me, that meant the radio button group would need to be available as an array anyway, so that I could iterate over the group easily and toggle the one with the appropriate tag
. Meh.
Double-checking the Apple Forums if I was maybe overlooking something, I noticed that people iterated the subviews of a group container. Nobody questioned that approach.
Why was I not doing this?
What would that look like?
let selectedTag: Int = ...
let buttonsTargetingThisAction = self.subviews
.compactMap { $0 as? NSButton } }
.filter { $0.action == #function } // ❶
for button in buttonsTargetingThisAction {
button.isChecked = (button.tag == selectedTag) // ❷
}
It’s not that bad, actually.
Note that ❶ I’m using #function
to represent “this action” here. That only works if this code is part of the @objc
action the button is targeting. And ❷ isChecked
isn’t built-in but is my own NSButton
extension.
After complaining about how I prefer to now work with SwiftUI where I can, I also have to take one step back and realize that I need to embrace AppKit and its Objective-C API heritage properly whenever I want to use it. There’s no point in making my life hard by programmatically creating the AppKit UI, then complain that it’s harder.
I could’ve used Xibs instead, which doesn’t sound that bad to me anymore, and @IBAction
s to quickly store references to all radio buttons per group. Since I didn’t, I should lean into traditional ways to work with AppKit views.