Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • A base CVC expects a factory & factory expects a CVC (thus facilitating two way binding for the cells)
  • This factory produces cell & header models for THE CVC.
  • These models have callback & data & configuration functionality embedded within them.
  • They can call PUBLIC functions of the view controller from these callbacks (thus strictly separating responsibilities) such as vc.changeDisplayState(...) or changeToVc(...)
like image 601
joslinm Avatar asked Feb 13 '15 16:02

joslinm


People also ask

Can structure be type cast in Swift?

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type. You can also use type casting to check whether a type conforms to a protocol, as described in Checking for Protocol Conformance.

How to change type in Swift?

In Swift, you cannot change the data type of a variable. Not being able to change the data type is because different data types are stored differently in memory. If you declare a variable to store integers, it can only store integers. You can change its integer value.

What is the difference between Upcast and downcast in Swift?

Downcasting is the opposite of upcasting, and it refers to casting an object of a parent class type to an object of its children class. Downcasting is used to reconvert objects of a children class that were upcasted earlier to generalize. Let's say you own two cars and three trucks.

What are generics Swift?

Generics in Swift allows you to write generic and reusable code, avoiding duplication. A generic type or function creates constraints for the current scope, requiring input values to conform to these requirements.


1 Answers

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

like image 131
Dave Abrahams Avatar answered Sep 21 '22 12:09

Dave Abrahams