That is, if I have a class C that takes two generics A and B, is there a way where I can cast an object to C where I don't care what B is?
My specific use case is that I need to bridge between NSView functionality and the new SwiftUI in a multi-window, but non-document based application. The problem I am having is, given an NSView, I need to obtain the SwiftUI View that it is managing (in my case a View called ContentView
).
Note that I do have a solution, which I include below, but it involves the use of Mirror
based reflection and I am wondering if there is a better way, most likely involving the use of as?
to cast to a partial match of a generic.
The bridging is done using the NSHostingView hence it should seem that one would just do the following:
if let hostingView = NSApplication.shared.keyWindow?.contentView as? NSHostingView<ContentView> {
// do what I need with 'hostingView.rootView'
}
Unfortunately, NSHostingView.rootView does not return the actual ContentView
that I created, it returns a modified version of that view dependant on the modifiers used. (In my case I'm using .environmentObject
modifier.) As a result the if
statement above never returns true because the type is not NSHostingView<ContentView>
but rather NSHostingView<ModifiedContent<ContentView, _bunch_Of_Gobbletygook_Representing_The_Modifiers>>
. One way to "solve" the problem is to print out the result of type(of: hostingView)
when I create the window, and then change my cast to include the current version of the "gobbledygook", but that is brittle for the following two reasons:
So I have created a solution in the form of the following NSView extension:
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
let mirror = Mirror(reflecting: self)
if let rootView = mirror.descendant("_rootView") {
let mirror2 = Mirror(reflecting: rootView)
if let content = mirror2.descendant("content") as? RootView {
return content
}
}
return nil
}
}
This allows me to handle my needs using the following:
private func currentContentView() -> ContentView? {
return NSApplication.shared.keyWindow?.contentView?.originalRootView()
}
... sometime later ...
if let contentView = currentContentView() {
// do what I need with contentView
}
What I would like to know is if there is a way to implement originalRootView
without the use of reflection, presumably by allowing a partially specified cast to the ModifiedContent
object. For example, something like the following (which does not compile):
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
if let hostingView = self as? NSHostingView<ModifiedContent<RootView, ANY>> {
return hostingView.rootView.content
}
return nil
}
}
The problem is what to put for "ANY". I would think some form of Any
or AnyObject
, but the complier complains about that. Essentially I would like to tell the compiler that I don't care what ANY is so long as the ModifiedContent has RootView as its content type.
Any ideas would be appreciated.
Just to make the results official, the answer is "no" there is no way to partially match a generic as of Swift 5.1.