iOS 17: Notable UIKit Additions
This post is brought to you by Emerge Tools, the best way to build on mobile.
I’m really starting to wonder why I write this annual post anymore 😅.
During the entirety of The Platforms State of the Union, I think I heard the term UIKit
or UIViewController
…twice? Maybe three, if you count the bit where the new #Preview{ }
syntax works for UIKit
controls now.
But alas - as long as the framework keeps chuggin’ with diffs each W.W.D.C., then I’ll keep writing this annual post. To that end, here are some notable UIKit additions for iOS 17. If you want to catch up on this series first, you can view the iOS 11, iOS 12, iOS 13, iOS 14, iOS 15 and iOS 16 versions of this article.
Unavailable Content
Ah yes, the empty data view. Here’s a snippet of code I currently use to implement it in UIKit, complete with Objective-C and swizzling:
#pragma mark - Swizzle
void swizzleInstanceUpdateMethods(id self) {
if (!implementationLookupTable) implementationLookupTable = [[NSMutableDictionary alloc] initWithCapacity:2];
if ([self isKindOfClass:[ASCollectionNode class]]) {
// Swizzle batchUpdates for collection node which is all we'll need
Method methodBatchUpdates = class_getInstanceMethod([self class], @selector(performBatchUpdates:completion:));
IMP performBatchUpdates_orig = method_setImplementation(methodBatchUpdates, (IMP)swizzledBatchUpdates);
[implementationLookupTable setValue:[NSValue valueWithPointer:performBatchUpdates_orig] forKey:[self instanceLookupKeyForSelector:@selector(performBatchUpdates:completion:)]];
} else if ([self isKindOfClass:[ASTableNode class]]) {
// Swizzle batchUpdates and reloadData for table node since we'll need both
Method methodBatchUpdates = class_getInstanceMethod([self class], @selector(performBatchUpdates:completion:));
IMP performBatchUpdates_orig = method_setImplementation(methodBatchUpdates, (IMP)swizzledBatchUpdates);
[implementationLookupTable setValue:[NSValue valueWithPointer:performBatchUpdates_orig] forKey:[self instanceLookupKeyForSelector:@selector(performBatchUpdates:completion:)]];
Method methodReloadDataWithCompletion = class_getInstanceMethod([self class], @selector(reloadDataWithCompletion:));
IMP performReloadDataWithCompletion_orig = method_setImplementation(methodReloadDataWithCompletion, (IMP)swizzledReloadDataWithCompletion);
[implementationLookupTable setValue:[NSValue valueWithPointer:performReloadDataWithCompletion_orig] forKey:[self instanceLookupKeyForSelector:@selector(reloadDataWithCompletion:)]];
}
}
// And a whole lot, LOT more...
…it’s…a little easier now. Enter UIContentUnavailableView
:
var config = UIContentUnavailableConfiguration.empty()
config.text = "Nothing Here"
config.image = .init(systemName: "plus")
let unavailable = UIContentUnavailableView(configuration: config)
Which yields:
The API is built on top of the content configurations that were introduced back in iOS 14. They have built in configurations for a few things: empty states, loading states and and searching.
Preview Syntax
As I mentioned in the opening, Swift’s slick new macros allow for Xcode Preview support in UIKit. This didn’t used to be straightforward, but it has always been possible. Behold at how laughably simple this is now (please excuse the odd syntax highlighting, my CSS isn’t quite yet ready for language changes):
#Preview("My Controller") {
MyController()
}
That’s it, now you’ll get a live Xcode preview just like you would for SwiftUI. Amazing:
Automatic Label Vibrancy
Labels will now, at least on visionOS, automatically support vibrancy. From an API standpoint, it uses UILabelVibrancy
:
let lbl = UILabel(frame. zero)
lbl.preferredVibrancy = .automatic
On visionOS, that’s always set to .automatic
, but it’s .none
everywhere else. Of note, you can only opt in to vibrancy, you can’t use this API to opt out of it. Plus, it’ll only effect situations where vibrancy would apply, which basically means when it’s over system materials.
Symbol Animations in Buttons
There is a whole lot of new API for animating SF Symbols. I haven’t dug into everything yet, but there are developer sessions later on in the week over it. I’ll be interested to learn more.
In the meantime, there is API I’m seeing for UIButton
to get symbol animations by setting one property:
let actionHandler = UIAction { ac in
}
actionHandler.title = "Tap Me"
actionHandler.image = .init(systemName: "plus.circle.fill")
let btn = UIButton(primaryAction: actionHandler)
btn.frame = view.bounds
// This contextually animates symbols
btn.isSymbolAnimationEnabled = true
…which yields a nice little bobble, bounce animation:
This is available for UIBarButtonItem
as well.
Symbol Animations in Images
To stay on those symbol animations for a minute, these are too fun to mess around with. There are several built-in symbol animations, and they all appear to use those juicy spring animations. For example, if you want an up arrow that looks like it drank 14 cups of coffee, here you go:
let symbolView = UIImageView(image: .init(systemName: "arrowshape.up.circle.fill"))
symbolView.frame = view.bounds
symbolView.contentMode = .scaleAspectFit
symbolView.addSymbolEffect(.bounce, options: .repeating, animated: true) { context in
if context.isFinished {
print("Animation done - but this won't, because it repeats.")
}
}
Behold bouncy boi:
Even better - combine it with the variable fill APIs added last year, and you can come up with some truly interesting effects:
Timer.publish(every: 0.35, on: .main, in: .default)
.autoconnect()
.sink { [weak self] _ in
switch self?.count {
case 0.0:
self?.count = 0.2
case 0.2:
self?.count = 0.6
case 0.6:
self?.count = 1.0
default:
self?.count = 0.0
}
guard let fillCount = self?.count else {
return
}
self?.symbolView.image = .init(systemName: "ellipsis.rectangle.fill",
variableValue: fillCount)
}.store(in: &subs)
I can tell you now, I will be utilizing an insufferable amount of these:
More Font Styles
We got huge and hugerer now:
let xl: UIFont = .preferredFont(forTextStyle: .extraLargeTitle)
let xl2: UIFont = .preferredFont(forTextStyle: .extraLargeTitle2)
Preview of both of those:
New Text Content Types
There are additions to boost autofill, specifically for credit cards and birthdays:
let bdayTextField = UITextField(frame: .zero)
bdayTextField.textContentType = .birthdateDay /* or .birthDate/Month/Year */
// Or credit cards
let creditCardTextField = UITextField(frame: .zero)
creditCardTextField.textContentType = .creditCardExpiration /* or .month/year/code plus several others */
UIKit Nuggets
Of course, I always write this before the developer sessions drop. So, a lot more is coming. Here are things I saw but didn’t cover because I don’t quite understand them fully, or I couldn’t get them to work in beta one:
- Massive trait collection capabilities: You can observe trait collection changes, make entirely custom ones, fire arbitrary logic when they change and that’s in addition to bringing them to SwiftUI’s environment.
- Text Sizing Rules: There is a new
sizingRule
property for labels, text fields and text views which appear to control sizing based off of a font’s ascenders and descenders when using preferred font styles. - Scene Activation: I’m seeing a lot of changes around scenes, and how they activate. I assume this is to support the new spaces stuff in visionOS.
- Menus: Related, but not specifically iOS - you’ll see that context menu interactions are available on tvOS now.
- visionOS: Of course, we can’t forget this one. It’s not every day we get a new device idiom! Enter
UIUserInterfaceIdiom.reality
. - Efficient Images: There is a new
UIImageReader
that seems interesting, allowing for tweaks like pixel per inch, thumbnail size and more. It appears to be meant for downsampling or efficient decoding. - Smart Text Entry: Support for the inline text prediction enhancements.
Final Thoughts
What else is there to say? The messaging has officially shifted. It started with “UIKit and SwiftUI are both great ways to build an app”, and went to “We’ve got great ways to put the UIKit stuff into SwiftUI, so you can use the SwiftUI-only APIs”, and last year it was straight up “SwiftUI is the best way to build an app.”
This year, UIKit really had the nail in the coffin feel to me, even though that’s hyperbolic. But it’s not where the puck is going, and Apple has made that abundantly clear. Does that bother me? No, not at all. It’s not going away, much like Objective-C is still here - and I’ll always celebrate how efficient, robust and capable the framework has been for my career (and still is).
But that was then, and now? It’s all about visionOS, interactive widgets - the list goes on. And I’m here for it, but I’ll still never forget the framework that got me started in this biz. Long live UIKit 🍻.
Until next time ✌️