Last active
August 12, 2024 02:45
-
-
Save drewolbrich/c896a424f08789b42f441e6d8de0a7f8 to your computer and use it in GitHub Desktop.
A workaround for pushWindow not being available on visionOS 1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// PushWindow_visionOS2.swift | |
// | |
// Created by Drew Olbrich on 7/1/24. | |
// Copyright © 2024 Lunar Skydiving LLC. All rights reserved. | |
// | |
// MIT License | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
import SwiftUI | |
/// This file defines a view modifier and an environment action that can be used to | |
/// work around the issue that the `pushWindow` environment action is only defined | |
/// in visionOS 2.0 and later. | |
/// | |
/// The specific problem addressed is that the following code will not compile if | |
/// the deployment version selected in Xcode is visionOS 1.0: | |
/// ``` | |
/// @Environment(\.pushWindow) var pushWindow | |
/// ``` | |
/// Because this variable is defined using an environment macro, there's no | |
/// straightforward way to use `#available` or `@available` to implement a | |
/// workaround that will compile on both visionOS 1 and visionOS 2, as there would | |
/// be if `pushWindow` was a method. | |
/// | |
/// # Example usage: | |
/// | |
/// In your `App` struct's `body`, define a primary window from which a secondary | |
/// window will be later pushed, assigning it the `windowPusher_visionOS2` view | |
/// modifier: | |
/// ``` | |
/// WindowGroup { | |
/// MyPrimaryWindowView() | |
/// .windowPusher_visionOS2() | |
/// } | |
/// ``` | |
/// | |
/// Then define the secondary window that will be pushed: | |
/// ``` | |
/// WindowGroup(id: "my-secondary-window") { | |
/// MySecondaryWindowView() | |
/// } | |
/// ``` | |
/// | |
/// Then define `MyPrimaryWindowView`: | |
/// ``` | |
/// struct MyPrimaryWindowView: View { | |
/// | |
/// @Environment(\.pushWindow_visionOS2) var pushWindow_visionOS2 | |
/// | |
/// var body: some View { | |
/// Button("Push Secondary Window") { | |
/// if #available(visionOS 2.0, *) { | |
/// // Calls `pushWindow(id:)` from the `windowPusher_visionOS2` view modifier, | |
/// // assigned to `MyPrimaryWindowView` above. | |
/// // This pushes `MySecondaryWindowView`. | |
/// pushWindow_visionOS2(id: "my-secondary-window") | |
/// } else { | |
/// // TODO: Present `MySecondaryWindowView` using a mechanism supported by visionOS 1, | |
/// // for example using the `sheet` view modifier. | |
/// } | |
/// } | |
/// } | |
/// | |
/// } | |
/// ``` | |
extension View { | |
/// Adds an action that responds to a `pushWindow_visionOS2` environment action by | |
/// calling `pushWindow` on visionOS 2, and is a no-op on visionOS 1. | |
func windowPusher_visionOS2() -> some View { | |
if #available(visionOS 2.0, *) { | |
return modifier(PushWindowViewModifier_visionOS2()) | |
} else { | |
return self | |
} | |
} | |
} | |
@available(visionOS 2.0, *) | |
private struct PushWindowViewModifier_visionOS2: ViewModifier { | |
@Environment(\.pushWindow) var pushWindow | |
func body(content: Content) -> some View { | |
content | |
.onPushWindow_visionOS2 { id in | |
pushWindow(id: id) | |
} | |
} | |
} | |
@MainActor struct PushWindowAction_visionOS2 { | |
typealias Action = (_ id: String) -> Void | |
let action: Action | |
@MainActor func callAsFunction(id: String) { | |
action(id) | |
} | |
// TODO: Define `callAsFunction(id:value:)` and `callAsFunction(value:)` for full compatibility with `pushWindow`. | |
} | |
private struct PushWindowActionKey_visionOS2: EnvironmentKey { | |
static var defaultValue: PushWindowAction_visionOS2? | |
} | |
extension EnvironmentValues { | |
/// A window push action stored in a view's environment. | |
/// | |
/// Use the `pushWindow_visionOS2` environment value to get an | |
/// `PushWindowAction_visionOS2` instance for a given `Environment`. Then call the | |
/// instance to push a window on visionOS 2. | |
/// | |
/// This action has no effect on visionOS 1. | |
/// | |
/// The window from which the window is pushed must be assigned the | |
/// `onPushWindow_visionOS2` view modifier. | |
var pushWindow_visionOS2: PushWindowAction_visionOS2 { | |
get { | |
self[PushWindowActionKey_visionOS2.self] ?? PushWindowAction_visionOS2(action: { _ in }) | |
} | |
set { | |
self[PushWindowActionKey_visionOS2.self] = newValue | |
} | |
} | |
} | |
private extension View { | |
@available(visionOS 2.0, *) func onPushWindow_visionOS2(_ action: @escaping PushWindowAction_visionOS2.Action) -> some View { | |
self.environment(\.pushWindow_visionOS2, PushWindowAction_visionOS2(action: action)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment