The iOS 16 and macOS 13 release reshaped SwiftUI: value-based navigation, the Layout protocol, Swift Charts, resizable sheets, and ViewThatFits. This is the map; each feature has its own deep-dive doc.
NavigationStack Replaces NavigationView
NavigationView is deprecated. NavigationStack binds the stack to an array of values, so navigation becomes data you can push, pop, and deep-link programmatically.
NavigationStack(path: $path) {
List {
ForEach(users) { user in
NavigationLink(user.name, value: user)
}
}
.navigationTitle("Users")
.navigationDestination(for: User.self) { user in
UserView(user: user)
}
}
.onAppear {
path.append(contentsOf: [.init(name: "Ada"), .init(name: "Grace")])
}
NavigationLink now carries a value, and navigationDestination(for:) maps that value type to its destination view.
The Layout Protocol
Conform to Layout and implement sizeThatFits plus placeSubviews to build a custom container. Measure children with subview.sizeThatFits(.unspecified), then place each with place(at:anchor:proposal:). The article's example sizes every child to the widest one, an equal-width HStack.
struct EqualWidthHStack: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let maxSize = maxSize(subviews: subviews)
let totalSpacing = spacings(subviews: subviews).reduce(0.0, +)
return CGSize(
width: maxSize.width * CGFloat(subviews.count) + totalSpacing,
height: maxSize.height
)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let maxSize = maxSize(subviews: subviews)
var x = bounds.minX + maxSize.width / 2
for index in subviews.indices {
subviews[index].place(
at: CGPoint(x: x, y: bounds.midY),
anchor: .center,
proposal: ProposedViewSize(width: maxSize.width, height: maxSize.height)
)
x += maxSize.width + spacings(subviews: subviews)[index]
}
}
}
Swift Charts
The Charts framework draws declarative charts with accessibility and localization built in. Mix mark types inside one Chart.
Chart(items) { item in
LineMark(x: .value("Index", item.index), y: .value("Value", item.value))
BarMark(
x: .value("Index", item.index),
yStart: .value("Start", 0),
yEnd: .value("End", item.value)
)
.foregroundStyle(by: .value("Value", item.value))
}
Resizable Sheets and ViewThatFits
presentationDetents turns a sheet into a draggable bottom sheet. The default is large; adding .medium lets the user drag between sizes.
.sheet(isPresented: $showSettings) {
Text("Settings")
.presentationDetents([.medium, .large])
}
ViewThatFits renders the first of its children that fits the available space, for example an HStack that degrades to a VStack. Also new: the Transferable protocol for share and drag-and-drop, the Grid view, and window and menu bar extra scenes.