1

Anyone may have a clean pre-swift-4 solution in mind for this?

I've seen similar questions, but this is more specific and the other solutions don't work here

NOTE: I can't create the second viewCotnroller in init, it needs data to be passed to init, and that data comes from server

protocol Cool { }

class Class1 {

    let viewController: UIViewController
    let cool: Cool
    // Swift 4 : let viewController: UIViewController & Cool

    public init<T: UIViewController>(content: T) where T: Cool {
        self.viewController = content
        self.cool = content
    }

    func atSomePointLater () {
        // Ho to get this to compile?
        Class2(content: viewController as! Cool, someText: textfield.text)
    }
}

class Class2 {
    public init<T: UIViewController>(content: T, someText: String) where T: Cool { 
    }
}
aryaxt
  • 76,198
  • 92
  • 293
  • 442

2 Answers2

1

Since both Class1 and Class2 initializers require a view controller conforming to the Cool protocol for their content parameter, we can make Class1 a generic class of a type we'll call T (where T is of type UIViewController and conforms to the Cool protocol).

Since viewController is a always of type T, we can change its type in its declaration. The Class2 initializer will now accept viewController as parameter because it's now not just any UIViewController, but one that also conforms to the Cool protocol.

protocol Cool { }

class Class1<T: UIViewController> where T: Cool {

    let viewController: T
    let cool: Cool

    public init(content: T) {
        self.viewController = content
        self.cool = content
    }

    func atSomePointLater () {
        let cls2 = Class2(content: viewController, someText: "Hello")
        print("Class 2", cls2)
    }
}

class Class2 {
    public init<T: UIViewController>(content: T, someText: String) where T: Cool {

    }
}


class MyVC: UIViewController { }
extension MyVC: Cool {}   // Mark MyVC as conforming to 'Cool' protocol

let vc1 = MyVC()

let cls1 = Class1(content: vc1)
cls1.atSomePointLater()

// Both 'viewController' and 'cool' reference 'vc1'
print(cls1.viewController == vc1)   // true
print(cls1.cool)                    // the same instance of MyVC, i.e. vc1
paulvs
  • 11,963
  • 3
  • 41
  • 66
0

Disclaimer: I have yet to familiarize myself with Swift 4.. so hopefully this helps.

First, you dont have a subclass of UIViewController that adheres to Cool. So you need to do that. UIViewController, and Cool like you've shown here doesnt provide enough info to suggest that downcasting can be perfomed between the two. Afterwards, you need to update viewcontroller in Class1 as that subclass. You wont be able to cast UIViewController as Cool otherwise with what you're providing here.

Maybe some more context would be helpful, to help with the purpose of Cool. By this I mean that i do not know what you need Cool for/what Cool is for generally in your use-case, but the following gets it all to compile.

public protocol Cool {

    init<T: UIViewController>(content: T) where T: Cool

}

class CoolController : UIViewController, Cool {

    required init<T>(content: T) where T : UIViewController, T : Cool {
        super.init(nibName: nil, bundle: nil)
        //Do whatever you need here..?

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class Class1: NSObject, Cool {

    var viewController: CoolController?
    var cool: Cool?

    required init<T>(content: T) where T : UIViewController, T : Cool {
        super.init()
        if let cont = content as? Cool {
            cool = cont
        } else if let controller = content as? UIViewController{
            viewController = CoolController(content: content)
        }
    }


    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /*
    // Swift 4 : let viewController: UIViewController & Cool
    */
    func atSomePointLater() {
        // Ho to get this to compile? ///Optionally.. use a GUARD statement here..
        if let c = viewController {
            Class2(content: c, someText: nil)
        }
    }
}

class Class2 {
    public init<T: UIViewController>(content: T, someText: String?) where T: Cool {

    }
}

Sidenote - Update: I would definitely provide context for Cool, and as to why you're instantiating a UIViewController inside of an object-only class. Not mandatory, but definitely intriguing. Also, I could not use the cool = Cool? variable inside of your atSomePointLater() method because there's no way of knowing that Cool and UIViewController can be downcasted (as I said above).

Stay Cool .. sorry, i just had to...

jlmurph
  • 1,050
  • 8
  • 17
  • By using CoolController instead of UIViewController, the code relies on a concrete type, which goes against the whole point of using generics :( . `var viewController: CoolController?` – aryaxt Jun 22 '17 at 21:48
  • Hmm, indeed, it seems i cannot find a way to have UIViewController adhere to it :(.. I think it's a subtyping issue mixing with the non-final class nature of UIViewController and the approached to make your solution work (ie: extension requirements, etc.). Check these posts, it's what i read up on: https://stackoverflow.com/questions/37552955/self-protocol-extension-and-non-final-class and https://stackoverflow.com/questions/34801399/protocol-extension-initializer and https://twitter.com/_danielhall/status/737782965116141568 – jlmurph Jun 22 '17 at 22:23