Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Examples/Sources/WindowingExample/WindowingApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct AlertDemo: View {

Button("Present error") {
Task {
await presentAlert("Failed to succeed")
await presentAlert("Failed to succeed") {}
}
}
}
Expand Down Expand Up @@ -162,6 +162,7 @@ struct SheetDemo: View {
struct WindowingApp: App {
@State var title = "My window"
@State var resizable = false
@State var isStandaloneAlertShown = false

var body: some Scene {
WindowGroup(title) {
Expand Down Expand Up @@ -189,6 +190,9 @@ struct WindowingApp: App {
#endif

AlertDemo()
Button("Show standalone alert") {
isStandaloneAlertShown = true
}

Divider()

Expand All @@ -210,6 +214,9 @@ struct WindowingApp: App {
}
}
}

AlertScene("Standalone alert", isPresented: $isStandaloneAlertShown) {}

#if !os(iOS) && !os(tvOS)
WindowGroup("Secondary window") {
#hotReloadable {
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftCrossUI/Builders/AlertActionsBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/// A builder for `[AlertAction]`.
@resultBuilder
public struct AlertActionsBuilder {
/// If no actions are provided, return a default "OK" action.
public static func buildBlock() -> [AlertAction] {
[AlertAction(label: "OK", action: {})]
}

public static func buildPartialBlock(first: Button) -> [AlertAction] {
[
AlertAction(
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftCrossUI/Environment/Actions/AlertAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
/// breaking ``Button``'s API would have much more wide-reaching impacts than
/// breaking this single-purpose API.
public struct AlertAction: Sendable {
public static let ok = AlertAction(label: "Ok", action: {})

public var label: String
public var action: @MainActor @Sendable () -> Void
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct PresentAlertAction {
@discardableResult
public func callAsFunction(
_ title: String,
@AlertActionsBuilder actions: () -> [AlertAction] = { [.ok] }
@AlertActionsBuilder actions: () -> [AlertAction]
) async -> Int {
let actions = actions()

Expand Down
79 changes: 79 additions & 0 deletions Sources/SwiftCrossUI/Scenes/AlertScene.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/// A scene that shows a standalone alert.
///
/// The exact behavior of the alert is backend-dependent, but it typically
/// shows up as an application modal or standalone window.
public struct AlertScene: Scene {
public typealias Node = AlertSceneNode

var title: String
var isPresented: Binding<Bool>
var actions: [AlertAction]

public let commands = Commands.empty

/// Creates an alert scene.
///
/// The exact behavior of the alert is backend-dependent, but it typically
/// shows up as an application modal or standalone window.
///
/// - Parameters:
/// - title: The alert's title.
/// - isPresented: A binding to a `Bool` that controls whether the alert
/// is presented.
/// - actions: The alert's actions.
public init(
_ title: String,
isPresented: Binding<Bool>,
@AlertActionsBuilder actions: () -> [AlertAction]
) {
self.title = title
self.isPresented = isPresented
self.actions = actions()
}
}

/// The scene graph node for ``AlertScene``.
public final class AlertSceneNode: SceneGraphNode {
public typealias NodeScene = AlertScene

private var scene: AlertScene
private var alert: Any?

public init<Backend: AppBackend>(
from scene: AlertScene,
backend: Backend,
environment: EnvironmentValues
) {
self.scene = scene
}

public func update<Backend: AppBackend>(
_ newScene: AlertScene?,
backend: Backend,
environment: EnvironmentValues
) {
if let newScene {
self.scene = newScene
}

if scene.isPresented.wrappedValue, alert == nil {
let alert = backend.createAlert()
backend.updateAlert(
alert,
title: scene.title,
actionLabels: scene.actions.map(\.label),
environment: environment
)
backend.showAlert(alert, window: nil) { responseId in
self.alert = nil
self.scene.isPresented.wrappedValue = false
self.scene.actions[responseId].action()
}

self.alert = alert
} else if !scene.isPresented.wrappedValue, let alert {
backend.dismissAlert(alert as! Backend.Alert, window: nil)
self.alert = nil
}
}
}
Loading