Can NSHostingController provide itself as environment variable to root view - (Battling Generics)

I'm trying to provide a swiftUI view with a reference to its parent NSHostingController

This allows (for example) the close button to call parent.dismiss()

I'm trying to do this by creating a subclass of NSHostingController which provides itself as an environment variable.

I'm getting tied up in knots though trying to specify the generics

Is there a way to achieve something like the following?

class HSHostingController<Content,Modified>:NSHostingController<Modified> where Content : View, Modified : View {
    
    
    init(rootView:Content) {
        
        let container = Container()
        let modified:Modified = rootView.environmentObject(container)
        
        super.init(rootView: modified)
        
        container.controller = self
    }
  
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

where

class Container:ObservableObject {
    weak var controller:NSViewController?
}

the error as written is that it Cannot convert value of type 'some View' to expected argument type 'Modified'

I think I need to specify the type of the parent more explicitly - but can't figure how to do that.

the modified root view is actually of type:

SwiftUI.ModifiedContent<Content, SwiftUI._EnvironmentKeyWritingModifier<Container?>>

Any help appreciated.

thank you

ok - answering my own question.

this works

class Container:ObservableObject {
    weak var controller:NSViewController?
}

class HSHostingController<Content>:NSHostingController<ModifiedContent<Content,SwiftUI._EnvironmentKeyWritingModifier<Container?>>> where Content : View {
    
    
    init(rootView:Content) {
        let container = Container()
        let modified = rootView.environmentObject(container) as! ModifiedContent<Content, _EnvironmentKeyWritingModifier<Container?>>
        super.init(rootView: modified)
        container.controller = self
    }
  
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

how problematic is it to be using _EnvironmentKeyWritingModifier

I think the easiest option would be to use AnyView and define your type as a subclass of NSHostingController<AnyView>, but if for whatever reason you want to avoid AnyView here's another option:

class Container: ObservableObject {
    
    weak var controller: NSViewController?
    
}

protocol RootViewModifyingHostingController {
    
    associatedtype ModifiedRootView: View
    
    associatedtype RootView: View
    
    
    @ViewBuilder static func modify(rootView: RootView, container: Container) -> ModifiedRootView
    
}

class MyHostingController<RootView: View>: NSHostingController<MyHostingController.ModifiedRootView>, RootViewModifyingHostingController {
    
    init(rootView: RootView) {
        let container = Container()
        let modified = Self.modify(rootView: rootView, container: container)
        
        super.init(rootView: modified)
        
        container.controller = self
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    static func modify(rootView: RootView, container: Container) -> some View {
        rootView
            .environmentObject(container)
    }
    
}
1 Like

I hadn't thought of just going for AnyView!

That's a neat trick using associated types to get the 'some View' indirectly into the constraint.

Thank you

1 Like
Terms of Service

Privacy Policy

Cookie Policy