Skip to content

Get the current window scene from SwiftUI #3

Description

@aheze

If you want support multiple screens, and use frame tags or Popovers.popover(tagged:), you need to specify the window scene. The problem is, getting a view's window scene in SwiftUI is weird.

My current workaround (involves 0.5 second hardcoded delay):

/// Get the parent window from a view. Help needed! From https://stackoverflow.com/a/63276688/14351818
struct WindowSceneReader: UIViewRepresentable {
/// A closure that's called when the window is found.
var found: ((UIWindowScene?) -> Void)
func makeUIView(context: Context) -> UIView {
let view = UIView()
/**
The 0.5 second delay is needed to wait until the window is first initialized.
However, it's hardcoded. Does anyone know how to work around this?
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak view] in
if let windowScene = view?.window?.windowScene {
found(windowScene)
}
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) { }
}
/**
Read the window scene from a view and inject it as an environment object. iOS 14+ due to `@StateObject`.
*/
@available(iOS 14, *)
public struct WindowSceneInjectorModifier: ViewModifier {
/// This is only iOS 14+. Looking for help in making this available for iOS 13.
@StateObject var windowSceneModel = WindowSceneModel()
public func body(content: Content) -> some View {
content
/// Inject the window into the subview.
.environmentObject(windowSceneModel)
/// Read the window.
.background(
WindowSceneReader { scene in
windowSceneModel.windowScene = scene
}
)
}
}

You can then use this in your app's root view via injectWindowScene.

/**
Get a view's window and make it accessible to all subviews. iOS 14+ due to `@StateObject` - looking for help in making it available for iOS 13.
Only necessary if your app supports multiple windows.
*/
@available(iOS 14, *)
func injectWindowScene() -> some View {
return self.modifier(WindowSceneInjectorModifier())
}

WindowGroup {
    ContentView()
        .injectWindowScene() /// Make the window scene available to all subviews. Not ideal, but it works (usually).
}

struct ContentView: View {
    @EnvironmentObject var windowSceneModel: WindowSceneModel

    /// ... 

    Text("Hello").frameTag("Your Frame Tag Name", in: windowSceneModel.windowScene)
}

The hardcoded delay especially is annoying. Typing out @EnvironmentObject var windowSceneModel: WindowSceneModel every time you want to access the window scene is ok, but it's still kind of long.

Does anyone know how to get a view's window in SwiftUI? Preferably, something like this:

struct ContentView: View {
    var body: some View {
        Text("Hello")
            .frameTag("Your Frame Tag Name")
    }
}
public extension View {
    func frameTag(_ tag: String) -> some View {
        let windowScene = theWindowSceneThatHostsThisView /// Something like this? So no more need to specify the window scene manually.
        return self.modifier(FrameTagModifier(tag: tag, windowScene: windowScene))
    }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions