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:
If I change the modifiers, the compiler will not warn me that I need to update the cast, and
Since the "gobbledygook" contains single underscored values, I must assume those are internal details that could change. Hence without my changing any code, an OS update could cause the cast to start failing.
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.
Related
I just started learning Swift as my first coding language. My current challenge is trying to automate the transitions from a current level setup as LevelOne.sks to another level also created in Xcode level editor as LevelTwo.sks. What I'm attempting to do is trigger a transition to the next level with the following set of code.
In my base scene I have this function to send the player to the next level
private func goToNextLevel(nextLevel: String) {
//When hard coding the arguments as...
//loadScene(withIdentifier: .levelTwo)
//The level two scene loads and is playable...however,
//When trying to change the level argument in code
// by passing a nextLevel variable
// the optional unwraps nil and crashes the app.
loadScene(withIdentifier: SceneIdentifier(rawValue: nextLevel)!)
}
This is then passed to a SceneLoadManager File
enum SceneIdentifier: String {
case levelOne = "LevelOne"
case levelTwo = "LevelTwo"
// case levelThree = "LevelThree"
}
private let sceneSize = CGSize(width: 768, height: 1024)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
func loadScene(withIdentifier identifier: SceneIdentifier) {
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let nextLevel = SKScene(fileNamed: identifier.rawValue)
nextLevel?.scaleMode = .aspectFill
self.view?.presentScene(nextLevel!, transition: reveal)
}
I think this has something to do with how I'm trying to set nextLevel. Currently I'm setting this up as follows
let nxtLvl = String?
nxtLvl = ".levelOne"
goToNextLevel(nextLevel: nxtLvl)
Hopefully you can make sense of what I'm trying to achieve and that I'm at least close to being on the right track here. Any help would be greatly appreciated. Thanks!
What could really help you to solve :
SceneIdentifier.levelOne.rawValue returns this -> "LevelOne"
BUT
SceneIdentifier(rawValue : "LevelOne") returns -> SceneIdentifier.levelOne
See the difference ?
In the first you get the string , in that case what you want
And in the second you get the "left member" (I don't know the therm)
An example to see clearer if you have an enum :
enum SceneIdentifier: String {
case case1 = "LevelOne"
case case2 = "LevelTwo"
}
SceneIdentifier(rawValue : "LevelOne") !
returns this : SceneIdentifier.case1
and SceneIdentifier.case1.rawValue
returns this : "levelOne"
Since you are initializing with a raw value, you need to actually use the raw value.
let nxtLvl = String?
nxtLvl = "LevelOne" //this is the rawValue of the .levelOne case
goToNextLevel(nextLevel: nxtLvl)
Alternatively, you could change your API so that goToNextLevel simply accepts a SceneIdentifier like this.
//tip: use the parameter name as part of the method name
private func goTo(nextLevel: SceneIdentifier) {
//Then calling the function
goTo(nextLevel: .levelOne)
Though, that might not be appropriate in the larger scope of your API.
Given a struct of some generic type, I want to limit which methods may be called to only those which are subtypes of the generic type.
struct ViewBuilder<T:UIView> {
let parent:UIView
func createView() -> UIView {
let view = UIView()
parent.addSubview(view)
return view
}
}
extension ViewBuilder where T:UIButton {
func createButton() -> UIButton {
let button = UIButton()
parent.addSubview(button)
return button
}
}
In this code I specify the builder should only create UIControls:
let builder = ViewBuilder<UIControl>(parent:UIView())
let view = builder.createView() // ok
let button = builder.createButton() // 🛑 'UIControl' is not a subtype of 'UIButton'
However, as you can see, I get the opposite behavior; a UIView is not a UIControl; a UIButton is a UIControl. Any way to do this in Swift? (I'm using Swift 4.) I've tried several things using generics and protocols but no luck.
(I understand why the above code works the way it does, but I'm looking for alternative the overall problem if one exists. If anyone can propose a good solution perhaps I can improve the question to fit.)
It is not possible to remove a method from a subtype (T where T:... is a subtype of T). That's a fundamental feature of types. A subtype must be able to do everything a type can do. Otherwise, if a function took a generic ViewBuilder<T>, how would it know whether createView were allowed on it or not? Extensions are extensions. They may not even be visible to consumers of the type.
In principle, this is what you're really asking for:
struct ViewBuilder<T: UIView> {
let parent: UIView
func create<U:T>() -> U {
let view = U()
parent.addSubview(view)
return view
}
}
This provides a create method for any type that is a subtype of T, which must itself be a subtype of UIView. Unfortunately, this is not currently legal Swift (SR-5213). The problem is that a generic type parameter that is constrained by a class is not itself considered a class for the purpose of constraining additional type parameters.
Given this limitation, in most cases I would probably use composition like this:
struct ViewBuilder {
let parent: UIView
func create<View: UIView>(_ type: View.Type) -> View {
let view = View()
parent.addSubview(view)
return view
}
}
struct ControlBuilder {
private let builder: ViewBuilder
var parent: UIView { return builder.parent }
init(parent: UIView) {
builder = BaseViewBuilder(parent: parent)
}
func create<Control: UIControl>(_ type: Control.Type) -> Control {
return builder.create(type)
}
}
let builder = ControlBuilder(parent:UIView())
let button = builder.create(UIButton.self)
Here a ControlBuilder HASA ViewBuilder rather than ISA ViewBuilder. This has some limitations if you want to accept "something that can create a kind of view" since it's pretty much impossible (as far as I can tell) to create a protocol that covers both of these because of SR-5213. But given your examples, this looks like it would match your use case.
(I'm kind of suspicious of this entire use case, though. I'm unclear how "a view that can only contain controls" is useful. It feels like what you really want are extensions on UIView. "Builder" feel like an attempt to import Java patterns into Swift that may not fit.)
This is one of those things that seems simple enough, but doesn't work as you'd expect.
I'm working on a 'fluent/chaining'-style API for my classes to allow you to set properties via functions which can be chained together so you don't have to go crazy with initializers. Plus, it makes it more convenient when working with functions like map, filter and reduce which share the same kind of API.
Consider this RowManager extension...
extension RowManager
{
#discardableResult
public func isVisible(_ isVisible:Bool) -> RowManager
{
self.isVisible = isVisible
return self
}
}
This works exactly as one would expect. But there's a problem here... if you're working with a subclass of RowManager, this downcasts the object back to RowManager, losing all of the subclass-specific details.
"No worries!" I thought. "I'll just use Self and self to handle the type!" so I changed it to this...
extension RowManager
{
#discardableResult
public func isVisible(_ isVisible:Bool) -> Self // Note the capitalization representing the type, not instance
{
self.isVisible = isVisible
return self // Note the lowercase representing the instance, not type
}
}
...but that for some reason won't even compile giving the following error...
Command failed due to signal: Segmentation fault: 11
UPDATE
Doing more research, this seems to be because our code both is in, and also uses, dynamic libraries. Other questions here on SO also talk about that specific error in those cases. Perhaps this is a bug with the compiler because as others have correctly pointed out, this code works fine in a stand-alone test but as soon as the change is made in our code, the segmentation fault shows up.
Remembering something similar with class functions that return an instance of that type, I recalled how you had to use a private generic function to do the actual cast, so I tried to match that pattern with the following...
extension RowManager
{
#discardableResult
public func isVisible(_ isVisible:Bool) -> Self // Note the capitalization
{
self.isVisible = isVisible
return getTypedSelf()
}
}
private func getTypedSelf<T:RowManager>() -> T
{
guard let typedSelfInstance = self as? T
else
{
fatalError() // Technically this should never be reachable.
}
return typedSelfInstance
}
Unfortunately, that didn't work either.
For reference, here's the class-based code I attempted to base that off of (className is another extension that simply returns the string-representation of the name of the class you called it on)...
extension UITableViewCell
{
/// Attempts to dequeue a UITableViewCell from a table, implicitly using the class name as the reuse identifier
/// Returns a strongly-typed optional
class func dequeue(from tableView:UITableView) -> Self?
{
return self.dequeue(from:tableView, withReuseIdentifier:className)
}
/// Attempts to dequeue a UITableViewCell from a table based on the specified reuse identifier
/// Returns a strongly-typed optional
class func dequeue(from tableView:UITableView, withReuseIdentifier reuseIdentifier:String) -> Self?
{
return self.dequeue_Worker(tableView:tableView, reuseIdentifier:reuseIdentifier)
}
// Private implementation
private class func dequeue_Worker<T:UITableViewCell>(tableView:UITableView, reuseIdentifier:String) -> T?
{
return tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as? T
}
}
At WWDC Apple confirmed this was a Swift compiler issue that something else In our codebase was triggering, adding there should never be a case where you get a Seg11 fault in the compiler under any circumstances, so this question is actually invalid. Closing it now, but I will report back if they ever address it.
What is the recommended approach for using 'as' in combination with 'if' if there are multiple possibilities for the base class, e.g.
var delegate:AnyObject?
func myFunction(){
if let delegate = self.delegate as? A1ViewController {
delegate.callFunction()
}
if let delegate = self.delegate as? A2ViewController{
delegate.callFunction()
}
}
Is there a way to combine the two if statements above?
e.g.
if let delegate = self.delegate as? A1ViewController || let delegate = self.delegate = self.delegate as? A2ViewController {
delegate.callFunction()
}
Regardless of the purpose of what you want to achieve (in my opinion there are some issues in your code design if you need this), this is my 2 cents on writing the cleanest code for this kind of checks.
You can't currently OR two let ... = ... statements. You could anyway work around that creating a common protocol that includes the common call, extend the classes and use a single code path.
protocol CommonDelegateProtocol {
func callFunction()
}
extension A1ViewController : CommonDelegateProtocol {}
extension A2ViewController : CommonDelegateProtocol {}
// then...
if let delegate = self.delegate as? CommonDelegateProtocol {
delegate.callFunction()
}
Version with switch in case of different code paths.
If you instead need different code paths, this is my best bet. In this way you also force your code to evaluate all the possible cases.
switch self.delegate {
case let d as A1ViewController:
// "d" is of type A1ViewController
d.callA1Function()
case let d as A2ViewController:
// "d" is of type A2ViewController
d.callA2Function()
default:
print("Uncovered case")
}
If you have only two options and assure that at least one will not be null, one way to make it a single line would be using ternary operator:
delegate = self.delegage as? A1ViewController != null ? self.delegage as? A1ViewController : self.delegage as? A2ViewController
Alessandro's answer above is the closest thing to what you're trying to do. The reason you can't combine these two statements in an if let binding is that you're attempting to bind to two different classes. What type do you expect delegate to be inside the if-let block? It could be either A1ViewController or A1ViewController depending on what the delegate is at runtime. So this can't be compiled because delegate needs a static type at compile time. I suggest you do what Alessandro suggests with the switch statement or create a protocol to abstract both types.
Can one use an array of types to refactor the following condition?
vc is ViewController1 || vc is ViewController2 || vc is ViewController3...
YES, but you shouldn't.
You CAN create an array of types:
let types:[Any.Type] = [CGFloat.self, Double.self, Float.self]
You MUST specify the type of the array as [Any.Type]. It can't figure it out on it's own.
You can even get a type from a variable that matches those.
let double = 42.13
double.dynamicType --> Double.Type
types.contains(double.dynamicType) --> true
As #werediver suggests, probably the most swift idiomatic way to do such in swift is to define an empty protocol:
protocol ExaltedViewController { }
and then conform your various classes to it:
extension ViewController1:ExaltedViewController { }
extension ViewController2:ExaltedViewController { }
extension ViewController3:ExaltedViewController { }
Then you can just test with
if vc is ExaltedViewController { ... }
The advantage of doing this is that you don't have to have a centrally managed list. If your code were a framework, where you wanted other peoples view controllers to be added into that list, they could simply adopt the protocol, rather than having to hunt down where the special list was and edit it.
An alternate approach is to use a switch statement with stacked is tests. Consider the following:
func typeWitch(something:Any) { // type divining function
switch something {
case is CGFloat, is Float, is Double:
print("it's got floating point superpowers")
case is Int, is Int16, is Int32, is Int8, is Int64:
print("it's a signed int")
default:
print("it's something else")
}
}
typeWitch("boo")
typeWitch(13)
typeWitch(42.42)
And of course, you can put the two approaches together.
You could make an extension like this:
import Foundation
extension SequenceType where Generator.Element == Any.Type{
func containsType(object:Any)->Bool{
let mirror = Mirror(reflecting: object)
let objectType = mirror.subjectType
return self.contains{objectType == $0}
}
}
And you call it like this:
if [ViewController1.self, ViewController2.self, ViewController3.self].containsType(vc){
//statement is true
}else{
//Statement is false
}
In the documentation here it says This type may differ from the subject's dynamic type when self is the superclassMirror() of another mirror. This is in regards to .subjectType.
One possible way:
let types: [Any.Type] = [CGFloat.self, Double.self, Float.self]
let myVar = 10.0
let isFloat = types.contains { myVar.dynamicType == $0 }
print("Is float: \(isFloat)")
Unfortunately, Swift doesn't allow us to test myVar is $0 directly with dynamic types. Therefore this is not very useful if we want to check subclasses.
I would really recommend the solution with a custom protocol.