[SC]()

iOS. Apple. Indies. Plus Things.

Marking Swift Properties Available by iOS Version

// Written by Jordan Morgan // Aug 21st, 2024 // Read it in about 1 minutes // RE: The Indie Dev Diaries

This post is brought to you by Sentry, mobile monitoring considered "not bad" by 4 million developers.

While working with the nascent PencilKit APIs introduced in iOS 18 to create custom tools, I ran into a situation that (gasp!) used to be trivial in Objective-C, but requires a little bit of dancin’ in Swift.

That is, marking properties available by a specific iOS version:

@property (nonatomic, copy) PKCustomToolItem *customItem API_AVAILABLE(ios(18.0));

There is, unfortunately, no corollary to this in Swift. You can mark extensions, classes and structs in a similar manner…

@available(iOS 18.0, *)
struct GetQuoteControlWidget: ControlWidget {
    // code
}

…or even handle control flow:

if #available(iOS 18.0, *) {
    setupCustomTools()
}

But, trying to do something similar with a property in Swift won’t compile:

@available(iOS 18.0, *)
private var customToolWrapper: PKToolPickerCustomItem = .init(configuration: .init(identifier: someID, name: someName))

The above would result in a compiler error, Stored properties cannot be marked potentially unavailable with '@available'.

Drats. But!

It turns out, with a little house call from our old friend NSObject (in my case — any base class of whatever you’re trying to use should do), you can do something similar when you utilize a computed property:

private var _customTool: NSObject? = nil
@available(iOS 18.0, *)
private var customToolWrapper: PKToolPickerCustomItem {
	if _customTool == nil {
		let tool = PKToolPickerCustomItem(configuration: .init(identifier: someID, name: someName))
		_customTool = tool
	}
	return _customTool as! PKToolPickerCustomItem
}

And voilà! You’re free to continue on with your day:

if #available(iOS 18.0, *) {
	customToolWrapper.allowsColorSelection = true 
}

There is an old saying on effective teaching, “First delight — then instruct.” I’ve always resonated with that, and have long considered Swift to embody such a notion. It eased you in, and then let you go a bit deeper.

These days, it’s feeling more like I need a rocket manual as opposed to determined curiosity to control the language (looking at you, Swift 6)1. Here’s hoping this specific situation becomes easier in the future (to wit — could a property wrapper solve this?) but regardless, this approach should work all the same.

Until next time ✌️

  1. Look, I get concurrency is an insanely hard problem to solve. But these errors are all over the place, and it feels like a huge swing in another direction as opposed to progressive disclosure in terms of learning how it should work. But the folks working on it are bright, talented people — so I have no doubt it’ll eventually “click”, even if the road getting there isn’t without its bumps. 

···

Spot an issue, anything to add?

Reach Out.