joslinm
joslinm

Reputation: 8105

How to downcast / cast a struct's generic type in Swift

I've refactored my work with UICollectionViewCells into the following

struct CollectionViewCellModel<T: UICollectionViewCell> {
    let reuseIdentifier: NSString
    let allowsSelection: Bool

    // Optional callbacks
    var onCreate: ((T) -> Void)?            = nil
    var onSelection: ((T) -> Void)?         = nil
    var onWillBeDisplayed: ((T) -> Void)?   = nil
    var onDelete: ((T) -> Void)?            = nil

    // Create cell
    func toCell(collectionView: UICollectionView, indexPath: NSIndexPath) -> T {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as T
        if let onCreate = onCreate { onCreate(cell) }
        return cell
    }
}

This has made it easier for me to create a list of specific cells (For things like forms) and then work with them.

However, I keep getting tripped up in how to store these objects. I can't downcast them to CollectionViewCellModel<UICollectionViewCell> and therefore can't store a list of [CollectionViewCellModel<UICollectionViewCell>

I thought Swift supported down-casting, but apparently not for generic types?

let cell = CollectionViewCellModel<A>(...)
let genericCell = cell as CollectionViewCellModel<UICollectionViewCell>
// ERROR: UICollectionViewCell is not identical to A
let genericMaybeCell = cell as? CollectionViewCellModel<UICollectionViewCell>
// ERROR: UICollectionViewCell is not identical to A

Do I have to store these as an array of Any and then cast them every time or am I just misunderstanding something (or both)?


Update: I've done some work in the playground to clearly illustrate what I mean:

protocol IA {
    func name() -> String
}

class A:IA { func name() -> String { return "A (Base)" } }
class B: A { override func name() -> String { return "B (Child of A)" } }
class C: B { override func name() -> String { return "C (Child of B)" } }

struct SA<T: A> {
}

let struct0: Any = SA<B>()
// OK: yes, the struct of B is an Any! 
// but since B inherits from A, isn't it safe to 
// say that SA<B> is also of type SA<A>?
let struct1: SA<A> = SA<B>() as SA<A>
// NO
// ERROR: 'B' is not identical to 'A'
let struct1Optional: SA<A> = SA<B>() as? SA<A>
// not even optionally? NO
// ERROR: 'B' is not identical to 'A'

I guess it's not possible. Maybe in Swift 1.3. See thread in comments.

Update (2/17/15)


For those of you interested in why I'm even doing this in the first place, you have to understand how I'm working with my CollectionViewControllers (CVCs). I've abstracted a base CVC to perform the common methods every screen needs. This CVC has a protocol that expects a Factory that creates CVC models. These models know how to transform themselves, respond to actions, and very much act like a controller. They're fat & active. My views on the other hand are all dumb. All they know how to do is move stuff around on the screen or go into different display states. When configuring a cell from a CVC, you end up doing these big switch statements that really don't tell you much from a readability perspective expect "route this". It gets worse you start to configure your view in there. It's not horrible, but generally speaking -- to me -- a view controller controls the view it's responsible for. While this VC might be the parent VC of the cell, this does not give it proper access to manipulate it heavily. This now makes your VC do things like cell.changeDisplayToActiveState(); in short, it now bears the burden of controlling its children cells. This is what contributes to all the "fat" VCs out there. I first chose to deviate from this path by adopting the VIPER pattern, but I found it overkill -- especially for a new project. I scrapped it and began to work with a base CVC. This is currently how my project works:

Upvotes: 9

Views: 3322

Answers (1)

Dave Abrahams
Dave Abrahams

Reputation: 7623

I'm not sure exactly what you're after, but I'll try to answer. Presumably you are not interested in storing a bunch of CollectionViewCellModel<X>'s for some specific X, because then the answer to "how do I store them?" would be simple: [CollectionViewCellModel<X>]. So I'm guessing you want a you want an array of CollectionViewCellModel<T> for heterogeneous T's. The best way to achieve that is to give all your CollectionViewCellModel<T>'s a common protocol (or base class, but in your case they're structs, so not that), something roughly like this:

protocol CollectionViewCellModelType {
  var reuseIdentifier: NSString {get}
  var allowsSelection: Bool {get}

  func toCell(
    collectionView: UICollectionView, indexPath: NSIndexPath
  ) -> UICollectionViewCell
}

struct CollectionViewCellModel<T: UICollectionViewCell>
  : CollectionViewCellModelType {
  let reuseIdentifier: NSString
  let allowsSelection: Bool

  // Optional callbacks
  var onCreate: ((T) -> Void)?            = nil
  var onSelection: ((T) -> Void)?         = nil
  var onWillBeDisplayed: ((T) -> Void)?   = nil
  var onDelete: ((T) -> Void)?            = nil

  // Create cell
  func toCell(
    collectionView: UICollectionView, indexPath: NSIndexPath
  ) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
      reuseIdentifier, forIndexPath: indexPath) as! T

    if let onCreate = onCreate { onCreate(cell) }
    return cell
  }
}

var a: [CollectionViewCellModelType] = [] // put them in here

Hoping this helps,

Dave

Upvotes: 6

Related Questions