[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 Emerge Tools, the best way to build on mobile.

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.