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:
vc.changeDisplayState(...)
or changeToVc(...)
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.
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.
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.
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.
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 struct
s, 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With