At WWDC 2026, Apple introduced several small SwiftUI APIs that are easy to overlook.
None of them add a new visual component.
None of them introduce new capabilities that were previously impossible.
Yet together they represent one of the most meaningful refinements to SwiftUI's presentation model since sheet(item:) debuted.
SwiftUI has always encouraged developers to derive presentation from data.
If data exists, something appears.
If the data disappears, the presentation goes away.
For years, this philosophy was already visible in APIs such as:
.sheet(item:)
.popover(item:)
.navigationDestination(item:)However, two commonly used presentation APIs remained inconsistent.
alertconfirmationDialogUntil iOS 27, presenting either of these usually required maintaining two independent pieces of state.
@State private var selectedInvoice: Invoice?
@State private var showingAlert = falseOr perhaps:
@State private var invoiceToDelete: Invoice?
@State private var showingDeleteDialog = falseAt WWDC 2026, SwiftUI finally completes the picture.
iOS 27 introduces item-based overloads for alerts and confirmation dialogs.
confirmationDialog(_:item:titleVisibility:actions:)
confirmationDialog(_:item:titleVisibility:actions:message:)
alert(_:item:actions:message:)
alert(_:item:actions:)
alert(error:actions:)
alert(error:actions:message:)Together these APIs make it possible to model presentations entirely from optional state.
No extra Boolean.
No synchronization bugs.
No impossible states.
isPresented was always slightly awkwardConsider an invoicing application.
Users can delete invoices from a list.
Before iOS 27, the implementation often looked like this.
@State private var invoiceToDelete: Invoice?
@State private var showingDeleteConfirmation = falseWhen the user taps Delete:
invoiceToDelete = invoice
showingDeleteConfirmation = trueAnd the confirmation dialog:
.confirmationDialog(
"Delete Invoice",
isPresented: $showingDeleteConfirmation
) {
if let invoice = invoiceToDelete {
Button("Delete", role: .destructive) {
delete(invoice)
}
}
}There is an obvious issue here.
Both properties represent the same thing.
If an invoice exists, the dialog should appear.
If there is no invoice, the dialog should not exist.
Unfortunately, invalid states become possible.
invoiceToDelete = nil
showingDeleteConfirmation = trueOr:
invoiceToDelete = invoice
showingDeleteConfirmation = falseSwiftUI encourages developers to model state in ways that prevent impossible situations.
The old approach allows impossible situations.
The new APIs solve this problem completely.
SwiftUI now provides two overloads for item-based confirmation dialogs.
func confirmationDialog<A, T>(
_ title: Text,
item data: Binding<T?>,
titleVisibility: Visibility = .automatic,
@ContentBuilder actions: (T) -> A
) -> some View where A : Viewand
func confirmationDialog<A, M, T>(
_ title: Text,
item data: Binding<T?>,
titleVisibility: Visibility = .automatic,
@ContentBuilder actions: (T) -> A,
@ContentBuilder message: (T) -> M
) -> some View where A : View, M : ViewWhenever the bound item becomes non-nil, SwiftUI presents the confirmation dialog.
Once dismissed, SwiftUI automatically resets the binding back to nil.
struct Invoice: Identifiable {
let id: UUID
let number: String
}State becomes remarkably simple.
@State private var invoiceToDelete: Invoice?Triggering presentation:
Button("Delete") {
invoiceToDelete = invoice
}Dialog presentation:
.confirmationDialog(
"Delete Invoice",
item: $invoiceToDelete,
titleVisibility: .visible
) { invoice in
Button("Delete \"\(invoice.number)\"", role: .destructive) {
delete(invoice)
}
Button("Cancel", role: .cancel) { }
} message: { invoice in
Text("Invoice \(invoice.number) will be permanently removed.")
}Several improvements immediately stand out.
There is no Boolean state.
No optional unwrapping.
No manual cleanup.
No need to keep two states synchronized.
SwiftUI manages everything automatically.
iOS 27 also introduces two item-based alert overloads.
alert(_:item:actions:message:)This overload behaves similarly to sheet(item:).
SwiftUI presents an alert whenever the item becomes non-nil.
func alert<A, M, T>(
_ titleKey: LocalizedStringKey,
item data: Binding<T?>,
@ContentBuilder actions: (T) -> A,
@ContentBuilder message: (T) -> M
) -> some View where A : View, M : ViewExample: Payment Failure Alert
struct PaymentFailure: Identifiable {
let id = UUID()
let invoiceNumber: String
let reason: String
}State:
@State private var paymentFailure: PaymentFailure?Presentation:
paymentFailure = PaymentFailure(
invoiceNumber: invoice.number,
reason: "Network connection lost"
)Alert:
.alert(
"Payment Failed",
item: $paymentFailure
) { error in
Button("Retry") {
retryPayment()
}
Button("Cancel", role: .cancel) { }
} message: { error in
Text(
"""
Unable to process invoice \
\(error.invoiceNumber).
\(error.reason)
"""
)
}alert(_:item:actions:)Sometimes an alert doesn't require additional explanatory text.
func alert<A, T>(
_ title: Text,
item data: Binding<T?>,
@ContentBuilder actions: (T) -> A
) -> some View where A : ViewThis overload removes the message closure.
Example: Duplicate Invoice Detection
struct DuplicateInvoice: Identifiable {
let id = UUID()
let number: String
}State:
@State private var duplicateInvoice: DuplicateInvoice?Trigger alert:
duplicateInvoice = DuplicateInvoice(
number: "INV-2026-001"
)Presentation:
.alert(
"Invoice Already Exists",
item: $duplicateInvoice
) { invoice in
Button("Replace") {
replaceInvoice(invoice)
}
Button("Keep Existing", role: .cancel) { }
}This overload works well when the title itself already provides enough context.
SwiftUI also includes dedicated APIs for presenting errors directly.
Instead of wrapping errors inside custom Identifiable models, SwiftUI can now derive alert presentation from an optional error value.
alert(error:actions:)func alert<E, A>(
error: Binding<E?>,
@ContentBuilder actions: () -> A
) -> some View where E : Error, A : ViewExample: Upload Failure
enum UploadError: Error {
case fileTooLarge
case insufficientStorage
}State:
@State private var uploadError: UploadError?Presentation:
uploadError = .insufficientStorageAlert:
.alert(
error: $uploadError
) { error in
Button("OK") { }
}alert(error:actions:message:)func alert<E, A, M>(
error: Binding<E?>,
@ContentBuilder actions: (E) -> A,
@ContentBuilder message: (E) -> M
) -> some View where E : Error, A : View, M : ViewThis overload provides access to the error value inside a message closure.
Example: Cloud Sync Failure
enum SyncError: Error {
case networkUnavailable
case unauthorized
}State:
@State private var syncError: SyncError?Presentation:
syncError = .networkUnavailableAlert:
.alert(
error: $syncError
) { error in
Button("Retry") {
sync()
}
Button("Cancel", role: .cancel) { }
} message: { error in
switch error {
case .networkUnavailable:
Text("Check your internet connection and try again.")
case .unauthorized:
Text("Please sign in again.")
}
}| API | Best Used For |
|---|---|
confirmationDialog(item:actions:) |
Destructive operations with contextual information |
confirmationDialog(item:actions:message:) |
Destructive operations requiring additional explanation |
alert(item:actions:) |
Contextual alerts where the title is sufficient |
alert(item:actions:message:) |
Contextual alerts requiring detailed messages |
alert(error:actions:) |
Simple error presentation |
alert(error:actions:message:) |
Error handling with customized descriptions |
I think this is one of the most underrated SwiftUI improvements announced at WWDC 2026.
These APIs don't introduce new capabilities.
Developers could already build alerts and confirmation dialogs before.
Instead, they remove a surprisingly common state-management smell found in many SwiftUI applications.
I've reviewed numerous codebases where presentation state looked like this:
@State private var selectedItem: Item?
@State private var showingAlert = falseor
@Presents var destination: Destination.State?while the destination itself already contained enough information to determine whether presentation should happen.
With iOS 27, SwiftUI encourages a much simpler mental model:
Presentation is just another consequence of data.
If an item exists, present it.
If an error exists, show it.
If the item disappears, the presentation disappears as well.
That philosophy has been gradually spreading throughout SwiftUI since its introduction, and iOS 27 finally feels like the release where Apple completed the family.
If you have suggestions or opinion, feel free to connect with me on X and send me a DM. If this article helped you, Buy me a coffee.