I am trying to inject generic data into a View Controller using a generic parameter T
. I have a protocol Coordinator
which declares an associated type T and a data
variable of type T
. My AppCoordinator
adopts the Coordinator
protocol and declares the type of T
as a String
. In my AppCoordinator
class, I am trying to set the data on a view controller using the generic parameter T
.
My thinking is that the class WelcomeViewController
adopts a concrete type of AppData
for data
when it implements the Navigable protocol, and so I should be able to set the type of the data in the AppCoordinator, which is String in this case, and be able to set that on the view controller as well. Somehow I suppose that in order for this to work the type T in the Coordinator protocol must match the type T in the Navigable protocol but when I try to do this by setting associated type T: Navigable.T
in the Coordinator protocol, for example, I get this error: "Cannot access associated type 'T' from 'Navigable'; use a concrete type or generic parameter base instead".
I have read How do I inject dependencies into an iOS view controller?, and the sections: The problems that generics solve, Generic Functions, Type Parameters, Naming Type Parameters, Type Constraints, Associated Types, Generic Where Clauses, Associated Types with a Generic Where Clause, from Generics.
How can I solve this?
protocol Coordinator<T> {
associatedtype T
var navigationController: UINavigationController { get }
var data: T? { get }
func start<T>(data: T?, viewController: any Navigable)
}
class AppCoordinator: Coordinator {
typealias T = String
var data: T?
init(data: T? = nil, navigationController: UINavigationController) {
self.data = data
self.navigationController = navigationController
}
var navigationController: UINavigationController
func start<T>(data: T?, viewController: any Navigable) {
var viewController = viewController
viewController.data = data
}
}
protocol Navigable<T> where T == AppData {
associatedtype T
var data: T? { get set }
}
enum AppData {
case coordinatorData
}
class WelcomeViewController: UIViewController, Navigable {
var data: AppData?
var appCoordinator: (any Coordinator)?
typealias T = AppData
override func viewDidLoad() {
setUp()
}
}
You can add generic constraint to your start
function to acheive your goal:
protocol Coordinator<T> {
associatedtype T
var navigationController: UINavigationController { get }
var data: T? { get }
func start<U: Navigable>(data: T?, viewController: U) where U.T == T //<- constraint here
}
class AppCoordinator: Coordinator {
typealias T = AppData
typealias U = WelcomeViewController
var data: AppData?
init(data: T? = nil, navigationController: UINavigationController) {
self.data = data
self.navigationController = navigationController
}
var navigationController: UINavigationController
func start<U>(data: AppData?, viewController: U) where U : Navigable, AppData == U.T {
var viewController = viewController
viewController.data = data
}
}
protocol Navigable { // <- remove T == AppData here
associatedtype T
var data: T? { get set }
}
enum AppData {
case coordinatorData
}
class WelcomeViewController: UIViewController, Navigable {
var data: AppData?
var appCoordinator: (any Coordinator)?
typealias T = AppData
override func viewDidLoad() {
//setUp()
}
}
Now you can use your Coordinator like this:
var coor: any Coordinator<AppData> // <- can swap to any concrete type that conform Coordinator with T = AppData
coor = AppCoordinator(navigationController: UINavigationController())
coor.start(data: .coordinatorData, viewController: WelcomeViewController())