The iOS 18 generation of SwiftUI adds view-collection access, a new tab API, hero transitions,
programmatic scroll position, and two boilerplate-killing macros. One breaking change leads: the
App, Scene, and View protocols are now @MainActor isolated, which can break existing
code.
View Collections
Group(subviewsOf:) gives a container access to the subviews a @ViewBuilder closure produced,
through the new Subview and SubviewsCollection proxy types. This is the hook for building
custom containers like List.
Group(subviewsOf: content) { subviews in
HStack {
if !subviews.isEmpty { subviews[0] }
if subviews.count > 1 { subviews[1] }
}
if subviews.count > 2 {
VStack { subviews[2...] }
}
}
The Tab Type
Tab replaces tabItem, takes a selection value directly, and TabSection groups tabs. With
.tabViewStyle(.sidebarAdaptable) the tab bar can flow into a sidebar.
TabView {
Tab("home", systemImage: "home", value: Destination.home) {
HomeView()
}
TabSection("Other") {
Tab("settings", systemImage: "settings", value: Destination.settings) {
SettingsView()
}
}
.tabViewStyle(.sidebarAdaptable)
}
Hero Animations
Pair matchedTransitionSource on the NavigationLink label with navigationTransition(.zoom)
on the destination, sharing an id and a @Namespace.
@Namespace var hero
NavigationLink {
DetailView()
.navigationTransition(.zoom(sourceID: "myId", in: hero))
} label: {
ThumbnailView()
}
.matchedTransitionSource(id: "myId", in: hero)
Scroll Position
The ScrollPosition type with the scrollPosition modifier reads the exact offset and scrolls
programmatically by setting the state.
@State private var position: ScrollPosition = .init(point: .zero)
ScrollView { /* content */ }
.scrollPosition($position)
Button("jump to top") {
position = ScrollPosition(point: .zero)
}
Entry and Previewable Macros
@Entry collapses the EnvironmentKey plus computed-property dance into one line, and it also
works for focused and container values. @Previewable puts @State directly in a preview
without a wrapper view.
extension EnvironmentValues {
@Entry var itemsPerPage: Int = 10
}
#Preview("toggle") {
@Previewable @State var toggled = true
return Toggle("Loud Noises", isOn: $toggled)
}
Also new: window pushing, text selection observation, search focus monitoring, custom text
rendering, and MeshGradient.