Skip to content

Instantly share code, notes, and snippets.

@schwa
Created October 16, 2023 18:05
Show Gist options
  • Save schwa/e3215765825cd4d131bae9ee9019cbdd to your computer and use it in GitHub Desktop.
Save schwa/e3215765825cd4d131bae9ee9019cbdd to your computer and use it in GitHub Desktop.
SwiftUI helpers to make dealing with ImmsersiveSpaces less annoying.
/// Create an ``ImmersiveSpaceState`` in your view, wrap it with `@State` and modify your view content with ``immersiveSpaceHelper()``.
/// Example:
/// ```swift
/// struct MyView: View {
/// @State
/// var immersiveSpaceState = ImmersiveSpaceState(id: "my-immersive-space-id")
///
/// var body: some View {
/// Toggle("Show/Hide Immersive Space", isOn: $immersiveSpaceState.show)
/// .immersiveSpaceHelper($immersiveSpaceState)
/// }
/// }
/// ```
public struct ImmersiveSpaceState {
public fileprivate(set) var id: String
/// The actual (read-only) visible state of the immersive space.
public fileprivate(set) var shown: Bool = false
/// The desired visible state of the immersive space. Set/reset this to open/dismiss the immersive state.
public var show: Bool = false
/// Call to (try to) open the immersive space.
public fileprivate(set) var open: () async -> Bool = { fatalError() }
/// Call to dismiss the immersive space.
public fileprivate(set) var dismiss: () async -> Void = { fatalError() }
public init(id: String) {
self.id = id
}
}
public struct ImmersiveSpaceStateModifier: ViewModifier {
@Binding
var immersiveSpaceState: ImmersiveSpaceState
@Environment(\.openImmersiveSpace)
var openImmersiveSpace
@Environment(\.dismissImmersiveSpace)
var dismissImmersiveSpace
public func body(content: Content) -> some View {
content
.onAppear {
immersiveSpaceState.open = {
switch await openImmersiveSpace(id: immersiveSpaceState.id) {
case .opened:
immersiveSpaceState.shown = true
immersiveSpaceState.show = true
case .error:
logger?.error("Failed to open immersive space (we don't know what the error is because, API)")
immersiveSpaceState.shown = false
immersiveSpaceState.show = false
case .userCancelled:
logger?.error("User cancelled immersive space open.")
immersiveSpaceState.shown = false
immersiveSpaceState.show = false
@unknown default:
fatalError("Immsersive space entered unknown state.")
}
return immersiveSpaceState.shown
}
immersiveSpaceState.dismiss = {
await dismissImmersiveSpace()
immersiveSpaceState.shown = false
}
}
.onChange(of: immersiveSpaceState.show) { oldValue, newValue in
guard oldValue != newValue else {
return
}
switch (newValue, immersiveSpaceState.shown) {
case (true, false):
Task {
await immersiveSpaceState.open()
}
case (false, true):
Task {
await immersiveSpaceState.dismiss()
}
default:
logger?.log("Request to change state to same state ignored.")
}
}
}
}
public extension View {
func immersiveSpaceHelper(_ state: Binding<ImmersiveSpaceState>) -> some View {
modifier(ImmersiveSpaceStateModifier(immersiveSpaceState: state))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment