Skip to main content

What’s new in SwiftUI for iOS 18

About 6 minSwiftArticle(s)bloghackingwithswift.comswiftiosxcode

What’s new in SwiftUI for iOS 18 관련

Swift > Article(s)

Article(s)

What’s new in SwiftUI for iOS 18 – Hacking with Swift
We got new API for colors and gradients, more scrollview improvements, tab improvements, and more.

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…


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:

How to create custom text effects and animations | SwiftUI by Example

How to create custom text effects and animations
How to create zoom animations between views | SwiftUI by Example

How to create zoom animations between views
How to read user contacts with ContactAccessButton | SwiftUI by Example

How to read user contacts with ContactAccessButton
How to create new colors by blending two other SwiftUI colors | SwiftUI by Example

How to create new colors by blending two other SwiftUI colors
How to create a mesh gradient | SwiftUI by Example

How to create a mesh gradient
How to control the size of presented views | SwiftUI by Example

How to control the size of presented views
How to detect when the size or position of a view changes | SwiftUI by Example

How to detect when the size or position of a view changes
How to read the size and position of a scrollview | SwiftUI by Example

How to read the size and position of a scrollview
How to scroll to exact locations inside a scrollview | SwiftUI by Example

How to scroll to exact locations inside a scrollview
How to detect whether a scrollview is currently moving or is idle | SwiftUI by Example

How to detect whether a scrollview is currently moving or is idle
How to position and style subviews that come from a different view | SwiftUI by Example

How to position and style subviews that come from a different view
How to use @State inside SwiftUI previews using @Previewable | SwiftUI by Example

How to use @State inside SwiftUI previews using @Previewable

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.


이찬희 (MarkiiimarK)
Never Stop Learning.