Swift: Assigning an object of type sub class to a variable of type base class with generics produces error

Viewed 747

I want to assign a sub class (TapWhatYouHearViewController), that inherits from its parent class (BaseLectureViewController) with implementing the generic using an inherited type (TapWhatYouHear), to a variable of type parent class (BaseLectureViewController) with the generic of type base class (Game).

Swift procudes the following error:

Swift:: Error: cannot assign value of type 'TapWhatYouHearViewController' to type 'BaseLectureViewController?'

I've read, that one solution e.g. in Java would be using a wildcard generic, but this is not possible in Swift. Is there any other solution?


This is my setup:

/** GAME TYPES **/

enum GameType {
    case TapWhatYouHear,
    case ChooseTranslation
}

class Game {
    var title: String?
    var type: GameType?
}

class TapWhatYouHear : Game {
    var audio: String?
    var type: GameType = GameType.TapWhatYouHear
}

class ChooseTranslation : Game {
    var video: String?
    var type: GameType = GameType.ChooseTranslation
}

/** VIEW CONTROLLERS **/

class BaseLectureViewController<T: Game> {
   var game: T?
}

class TapWhatYouHearViewController : BaseLectureViewController<TapWhatYouHear> {

}

class ChooseTranslationViewController : BaseLectureViewController<ChooseTranslation> {

}

I want to do the following:

var game: Game = TapWhatYouHear() // <-- Depending on the user interaction, this can be any sub class of "Game"
var viewController: BaseLectureViewController<Game>

switch game.type {
    case .ChooseTranslation:
        viewController = TapWhatYouHearViewController() // <--Swift:: Error: cannot assign value of type 'TapWhatYouHearViewController' to type 'BaseLectureViewController<Game>?'
    case .TapWhatYouHear:
        viewController = ChooseTranslationViewController()
}

viewController.game = TapWhatYouHear()

What I'm trying to do is determine at runtime which sub class of BaseLectureViewController should be assigned to the viewController variable. To save code, I then want to assign a game of type Game to the game property of the BaseLectureViewController.

3 Answers

The generic approach you have in mind is not possible. My suggestion is to go with a simple inheritance solution where you override getGame() in your concrete ViewController (i.e. TapWhatYouHearViewController) and cast the game to your specific type.

import UIKit

enum GameType {
    case TapWhatYouHear
    case ChooseTranslation
}

class Game {
    var title: String?
    var type: GameType?
}

class TapWhatYouHear : Game {
    var audio: String?

    override init() {
        super.init()
        type = GameType.TapWhatYouHear
    }
}

class ChooseTranslation : Game {
    var video: String?

    override init() {
        super.init()
        type = GameType.ChooseTranslation
    }
}


/** VIEW CONTROLLERS **/



protocol Lecture {
    var game: Game? { get set }
}

class BaseLectureViewController: UIViewController, Lecture {

    var game: Game?
    func getGame() -> Game? {
        fatalError("Must be implemented in subclass")
    }
}

class TapWhatYouHearViewController : BaseLectureViewController {
    override func getGame() -> TapWhatYouHear? {
        return game as? TapWhatYouHear
    }
}

class ChooseTranslationViewController : BaseLectureViewController {
    override func getGame() -> ChooseTranslation? {
        return game as? ChooseTranslation
    }
}

var game: Game = TapWhatYouHear() // <-- Depending on the user interaction, this can be any sub class of "Game"
var viewController: BaseLectureViewController?

if let gameType = game.type {
    switch gameType {
    case GameType.ChooseTranslation:
        viewController = TapWhatYouHearViewController() // <--Swift:: Error: cannot assign value of type 'TapWhatYouHearViewController' to type 'BaseLectureViewController<Game>?'
    case GameType.TapWhatYouHear:
        viewController = ChooseTranslationViewController()
    }

    viewController?.game = TapWhatYouHear()
}



Change the variable declaration to

var viewController: BaseLectureViewController<TapWhatYouHear>

Or skip generics and declare your super class as

class BaseLectureViewController {
    var game: Game?
}

If all you want to do is to assign a game property with an instance of specific subclass of Game, then you might use something like this, omitting generics

class Game {
    var title: String?

    required init() {}
}

class TapWhatYouHear : Game {
    var audio: String?
}

class ChooseTranslation : Game {
    var video: String?
}

/** VIEW CONTROLLERS **/

class BaseLectureViewController : UIViewController {
    var game: Game

    init(gameType: Game.Type)
    {
        game = gameType.init()
        super.init(nibName: nil, bundle: nil)
    }

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

class TapWhatYouHearViewController : BaseLectureViewController{
    init()
    {
        super.init(gameType: TapWhatYouHear.self)
    }

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

class ChooseTranslationViewController : BaseLectureViewController{
    init()
    {
        super.init(gameType: ChooseTranslation.self)
    }

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

usage

var viewController : BaseLectureViewController
viewController = TapWhatYouHearViewController()
Related