LAST CHANCE: Save 50% on all my Swift books and bundles! >>

What’s new in SwiftUI for iOS 18

We got new API for colors and gradients, more scrollview improvements, tab improvements, and more.

Paul Hudson       @twostraws

This is another good year for SwiftUI, with another batch of scrollview improvements, some welcome macOS features, remarkable control over text rendering, and more – the team at Apple have a lot to be proud of, and many developers will breathe a sigh of relief as API such as fine-grained subview control is now public for all of us to use.

But there's also one major architectural change you need to be aware of, so let's start with that…

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

View is now on the main actor

For a long time, the View protocol looked a bit like this:

protocol View {
    @MainActor var body: some View
}

That meant code in your view's body ran on the main actor, but code elsewhere in your view did not.

This allowed our views to do work across tasks naturally, but caused problems when using @Observable classes that ran on the main actor. For example, code like this simply wouldn't compile:

@Observable @MainActor
class ViewModel {
    var name = "Anonymous"
}

struct ContentView: View {
    @State private var viewModel = ViewModel()

    var body: some View {
        Text("Hello, \(viewModel.name)!")
    }
}

That would throw up "Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context", which is a rather complex way of saying "your class says it must be on the main actor, but you're creating it away from the main actor."

When you rebuild your code with Xcode 16 that error goes away completely, and with no work from us – it's just gone. However, it's important to know why. You see, the View protocol now looks more like this:

@MainActor protocol View {
    var body: some View
}

The difference is small, but makes a huge difference: the @MainActor attribute moved from body up to the protocol itself, which means the body property along with all other properties and methods we make are run on the main actor.

You can see the impact with this sample code:

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .onAppear(perform: doSampleWork)
    }

    func doSampleWork() {
        Task {
            for i in 1...10_000 {
                print("Work 1-\(i)")
            }
        }

        Task {
            for i in 1...10_000 {
                print("Work 2-\(i)")
            }
        }
    }
}

Previously that would run both tasks at the same time, so you'd see "Work 1" and "Work 2" outputs being printed intermingled. However, now that View runs on the main actor, my whole view runs on the main actor, and therefore the doSampleWork() work method also runs on the main actor.

This means the onus is on you to make sure you push work off the main actor as necessary, otherwise you'll see a pretty dramatic decrease in performance.

So, the overall change is a welcome one: fewer errors for the most common work. However, it will create a little churn in the short term as you spin work off to other actors manually.

On to the new APIs…

We got another batch of major improvements this year, delivering more scrollview power, incredible new animation effects, and better control over how we place subviews:

Note: This list is currently incomplete; some of the new APIs didn't quite ship for seed 1, and some others aren't quite functioning well enough for me to talk about. Hopefully seed 2 or 3 will see improvements here.

Smaller improvements

Alongside those major features, we also received some smaller but still important adjustments:

That @Entry change alone is a real delight – it makes things like environment and preference key significantly easier.

What's still missing?

We're now five years into SwiftUI, so you might expect the platform has reached maturity. However, there are a handful of omissions that continue to cause problems, and we can only hope these get addressed soon.

First, we still don't have any kind of WebKit or Safari integration. While a full WebView might perhaps be a lot of work, even some kind of SafariView to match SFSafariViewController would be something. I've filed feedback, I've talked to Apple's engineers in labs, and at this point I don't know what else to do. UIKit had UIWebView in its very first release – what do we need to get something similar in SwiftUI?

Second, working with the keychain remains incredibly hard. This API has always been problematic, but by ignoring it SwiftUI makes the problem worse – it's trivial to use @AppStorage, but doing so sacrifices essential user security. Sadly, we're in a state where the wrong choice is by far the easiest to reach for.

Third, we desperately need more control over remote images. SwiftUI for iOS 15 gave us the rather surprising AsyncImageView – still the only API that silently swallows errors, from what I can tell – but years on we haven't acquired any ability to adjust caching, retries, and more. Some configuration API similar to defaultAppStorage() would make a huge difference.

And finally, TextEditor still has no rich text support. I can imagine this being an extremely complex task, not least because it's clear the SwiftUI team want their text to retain meaningful metadata rather than just being blobs of attributes. However, this missing support limits where SwiftUI can be used, and I know many apps would benefit from adding more functionality here.

Those are just four ideas, and I know other folks have their own priorities. Please do continue to file feedback with Apple – I know it can feel like a bit of a black hole sometimes, but your feedback reports are read and discussed internally, and every time someone duplicates a request it's effectively one more vote for that feature.

SwiftUI is by far the best way to create apps for Apple's platforms, and this release continues to stretch its lead. Once we reach feature parity with UIKit – yes, WKWebView and SFSafariViewController, but also DataScannerViewController, list section index titles, and pretty much everything that still needs @UIApplicationDelegateAdaptor – then really there's nothing holding it back.

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.