SwiftUI - iOS 27

What's New in SwiftUI for iOS 27

iOS 27 is the SwiftUI release where phone layouts need to behave less like fixed-size screens. Build for resizable width, rank your toolbar actions, and move list-only interactions into custom scroll layouts when the design needs it.

June 15, 2026 6 minute read iOS 27
WWDC26 SwiftUI iPhone toolbar demo screen
The SwiftUI session shows the same phone UI adapting its toolbar as available width changes.

iOS focus

Resizable iPhone UI

Size classes and flexible layouts beat idiom checks as iPhone apps appear in more resizable contexts.

Toolbar ranking

Keep the actions people need most visible when horizontal space is tight.

Custom rows

Swipe actions and reordering no longer force a List-first design.

Resizable iPhone Layouts

Apple's SwiftUI session explicitly calls out iOS 27 iPhone app resizing and Xcode 27 resize handles in Live Previews. The code impact is straightforward: stop assuming a single compact phone canvas. Prefer layout rules driven by size, space, and content rather than device idiom.

This is still SwiftUI work even when the app looks unchanged at normal iPhone size. Views that branch on horizontalSizeClass, available width, and content density will survive iPhone Mirroring, iPhone-on-iPad, and preview resizing better than views that assume every iPhone screen is one fixed column.

Requires iOS 27 Requires Xcode 27
Fallback for OS 26

Keep the same flexible layout code. Older iOS versions may not expose the same resizing behavior, but the layout still improves Split View, Dynamic Type, and iPhone-on-iPad cases.

struct LibraryLayout: View {
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass

    var body: some View {
        let columns = horizontalSizeClass == .compact ? 2 : 4

        ScrollView {
            LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columns)) {
                ForEach(items) { item in
                    LibraryTile(item)
                }
            }
        }
    }
}

Rank the Top Bar

iOS is where toolbar overflow hurts first. The OS 27 APIs let you keep undo, redo, and share where users can reach them while moving secondary commands into overflow. Use minimization only when the content area benefits from it, such as feeds, editors, galleries, and document canvases.

Treat the toolbar as a priority list, not a bag of buttons. The system can collapse low-priority items as the view narrows, but only if you mark the commands that must stay visible and move optional commands into an explicit overflow menu.

Requires iOS 27 SDK
NavigationStack {
    FeedView()
        .toolbar {
            ToolbarItemGroup {
                FilterButton()
                SortButton()
            }
            .visibilityPriority(.high)

            ToolbarOverflowMenu {
                ManageDownloadsButton()
                ResetLayoutButton()
            }
        }
        .toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
}

Rows Without List Lock-In

On iPhone, custom feeds often need row gestures without List styling. In iOS 27, two separate changes matter: swipe actions can be coordinated by a custom scroll container, and reordering can move to stacks or grids.

Swipe Actions in Custom Scroll Views

Use this when the UI is still row-like, but the visual design is not a system List: inbox rows, store carts, media queues, saved items, and compact task feeds.

The row keeps the familiar .swipeActions modifier. The new part is the container modifier on the scroll view, which coordinates swipe state across all rows in that custom scroller.

Requires iOS 27 SDK
Fallback for OS 26

Use List for rows that need swipe actions on older iOS releases. For a custom visual design, provide visible trailing buttons or a context menu instead of hidden swipe gestures.

ScrollView {
    LazyVStack(spacing: 8) {
        ForEach(messages) { message in
            MessageRow(message)
                .swipeActions {
                    Button("Archive") { archive(message) }
                }
        }
    }
}
.swipeActionsContainer()

Reorderable Stacks and Grids

Reordering is the better fit when the user is arranging content, not dismissing a row: photo picks, board columns, launcher tiles, dense media cards, and inspector panels.

The generated children opt into dragging with .reorderable(). The parent container receives the difference and applies it to the source array, so model mutation stays in one place.

Requires iOS 27 SDK
Fallback for OS 26

Keep List plus onMove where a list presentation is acceptable. For a custom grid, use explicit move buttons or show the edit affordance only on iOS 27.

LazyVGrid(columns: columns) {
    ForEach(cards) { card in
        CardTile(card)
    }
    .reorderable()
}
.reorderContainer(for: Card.ID.self) { difference in
    cards.apply(difference: difference)
}

Image Loading

iOS apps with feeds, stores, and image-heavy lists benefit from the AsyncImage change immediately: it now respects HTTP caching by default. Build with Xcode 27 when you need per-request cache policy or a custom URL cache.

Previously, scrolling back to an image could trigger another download path unless the app built its own cache around AsyncImage. With the OS 27 behavior, server cache headers are enough for the common case. Use the request-based initializer only when a feed needs a specific cache policy.

Requires iOS 27
AsyncImage(request: URLRequest(
    url: product.artworkURL,
    cachePolicy: .returnCacheDataElseLoad
))

Item-Based Confirmation

Alerts and confirmation dialogs now follow the same item-binding pattern SwiftUI developers already use for sheets. On iPhone, that keeps command-driven UI small: store the selected model value, present the dialog, and let SwiftUI clear the presentation when the item goes away.

Requires iOS 27 SDK
@State private var messageToDelete: Message?

var body: some View {
    MessageList(messages) { message in
        messageToDelete = message
    }
    .confirmationDialog("Delete message?", item: $messageToDelete) { message in
        Button("Delete", role: .destructive) {
            delete(message)
        }
    }
}

Lazy @State Initialization

The lazy @State initialization change is a separate win on iPhone because short-lived parent view updates used to waste work when a child view stored an observable class in State.

This is a toolchain behavior change, not a new iOS-only visual API. If a child view declares an observable object in @State, SwiftUI can now avoid constructing that object until the state storage is actually used for the view lifetime.

Back-deployed
struct FeedFilterView: View {
    @State private var model = FeedFilterModel()

    var body: some View {
        FilterControls(selection: $model.selection)
    }
}

Sources