Skip to content

Instantly share code, notes, and snippets.

@rnapier
Last active September 20, 2016 11:05
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnapier/44bf23300959cca9c43f to your computer and use it in GitHub Desktop.
Save rnapier/44bf23300959cca9c43f to your computer and use it in GitHub Desktop.
// We're going to try to build a collectionview layout engine that can serve up cells and supplementary views
// in a generic way. I haven't built this all the way to the collection view, and I don't konw what the real
// brief is, so there may be lots of gotchas here that don't work as desired, but it scopes out several ways
// to get a handle on the problem and how I'd at least probably start.
// Ultimately, this requires converting (Model, IndexPath) -> Cell and (Model, IndexPath) -> SupView. I'm
// assuming that's the only feature of these factories.
// First we'll try it with straight protocols and generics. This is fairly straightforward, but has a problem.
// There's no way to force CellFactoryType and SupplementaryViewFactoryType to have the same Model. That
// might be ok, but I suspect in real usage it would tend to blow up. But let's start with this anyway.
// Note also that this invents a EmptySupplementaryViewFactory so that we have some type to feed into the
// layout engine when we have no type. There has to be *some* type, so we invent one.
import UIKit
protocol CellFactoryType {
typealias Cell: UICollectionViewCell
typealias Model
func cellForModel(model: Model, indexPath: NSIndexPath) -> Cell
}
protocol SupplementaryViewFactoryType {
typealias View: UICollectionReusableView
typealias Model
func suplementaryViewForModel(model: Model, indexPath: NSIndexPath) -> View?
}
struct EmptySupplementaryViewFactory<Model>: SupplementaryViewFactoryType {
typealias View = UICollectionReusableView
func suplementaryViewForModel(model: Model, indexPath: NSIndexPath) -> View? { return nil }
}
protocol LayoutEngineType {
typealias CellFactory: CellFactoryType
typealias SupplementaryViewFactory: SupplementaryViewFactoryType
var cellFactory: CellFactory { get }
var supplementaryViewFactory: SupplementaryViewFactory { get }
}
struct LayoutEngine<CellFactory: CellFactoryType, SupplementaryViewFactory: SupplementaryViewFactoryType
where CellFactory.Model == SupplementaryViewFactory.Model> : LayoutEngineType {
let cellFactory: CellFactory
let supplementaryViewFactory: SupplementaryViewFactory
init(cellFactory: CellFactory, supplementaryViewFactory: SupplementaryViewFactory) {
self.cellFactory = cellFactory
self.supplementaryViewFactory = supplementaryViewFactory
}
}
struct SimpleLayoutEngine<CellFactory: CellFactoryType> : LayoutEngineType {
typealias SupplementaryViewFactory = EmptySupplementaryViewFactory<CellFactory.Model>
let cellFactory: CellFactory
var supplementaryViewFactory: SupplementaryViewFactory { return SupplementaryViewFactory() }
init(cellFactory: CellFactory) {
self.cellFactory = cellFactory
}
}
// That's ok, but we can do a little better by adding type erasers. With these, we can actually force all the
// models to match. On the other hand, this code is getting really unweildy.
import UIKit
protocol CellFactoryType {
typealias Cell: UICollectionViewCell
typealias Model
func cellForModel(model: Model, indexPath: NSIndexPath) -> Cell
}
struct AnyCellFactory<Model, Cell: UICollectionViewCell>: CellFactoryType {
init<C: CellFactoryType where C.Model == Model, C.Cell == Cell>(_ factory: C) {
_cellForModel = { (model: Model, indexPath: NSIndexPath) -> Cell in
return factory.cellForModel(model, indexPath: indexPath)
}
}
let _cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell
func cellForModel(model: Model, indexPath: NSIndexPath) -> Cell {
return _cellForModel(model: model, indexPath: indexPath)
}
}
protocol SupplementaryViewFactoryType {
typealias View: UICollectionReusableView
typealias Model
func suplementaryViewForModel(model: Model, indexPath: NSIndexPath) -> View?
}
struct AnySupplementaryViewFactory<Model, View: UICollectionReusableView>: SupplementaryViewFactoryType {
init<S: SupplementaryViewFactoryType where S.Model == Model, S.View == View>(_ factory: S) {
_suplementaryViewForModel = { (model: Model, indexPath: NSIndexPath) -> View? in
return factory.suplementaryViewForModel(model, indexPath: indexPath)
}
}
let _suplementaryViewForModel: (model: Model, indexPath: NSIndexPath) -> View?
func suplementaryViewForModel(model: Model, indexPath: NSIndexPath) -> View? {
return _suplementaryViewForModel(model: model, indexPath: indexPath)
}
}
struct EmptySupplementaryViewFactory<Model>: SupplementaryViewFactoryType {
typealias View = UICollectionReusableView
func suplementaryViewForModel(model: Model, indexPath: NSIndexPath) -> View? { return nil }
}
protocol LayoutEngineType {
typealias CellFactory: CellFactoryType
typealias SupplementaryViewFactory: SupplementaryViewFactoryType
var cellFactory: CellFactory { get }
var supplementaryViewFactory: SupplementaryViewFactory { get }
}
struct LayoutEngine<Model, Cell: UICollectionViewCell, View: UICollectionReusableView> : LayoutEngineType {
let cellFactory: AnyCellFactory<Model, Cell>
let supplementaryViewFactory: AnySupplementaryViewFactory<Model, View>
init<CellFactory: CellFactoryType, SupplementaryViewFactory: SupplementaryViewFactoryType
where SupplementaryViewFactory.Model == Model,
CellFactory.Model == Model,
CellFactory.Cell == Cell,
SupplementaryViewFactory.View == View>
(cellFactory: CellFactory, supplementaryViewFactory: SupplementaryViewFactory) {
self.cellFactory = AnyCellFactory(cellFactory)
self.supplementaryViewFactory = AnySupplementaryViewFactory(supplementaryViewFactory)
}
}
struct SimpleLayoutEngine<Model, Cell: UICollectionViewCell> : LayoutEngineType {
typealias SupplementaryViewFactory = EmptySupplementaryViewFactory<Model>
let cellFactory: AnyCellFactory<Model, Cell>
var supplementaryViewFactory: SupplementaryViewFactory { return SupplementaryViewFactory() }
init<CellFactory: CellFactoryType
where CellFactory.Model == Model, CellFactory.Cell == Cell>(cellFactory: CellFactory) {
self.cellFactory = AnyCellFactory(cellFactory)
}
}
// But what if we ditched this whole "factory" thing. Get rid of the extra classes entirely. They're just big
// containers to hold a single function. What if we just used the function itself? Well, the code gets a *lot*
// simpler. And this is what I'd probably lean towards if I were really building this.
import UIKit
protocol LayoutEngineType {
typealias Model
typealias Cell: UICollectionViewCell
typealias SupplementaryView: UICollectionReusableView
var cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell { get }
var suplementaryViewForModel: (model: Model, indexPath: NSIndexPath) -> SupplementaryView? { get }
// Or suplementaryViewForModel could be an optional func rather than a func that returned an optional.
// That would let you test whether this function was implemented (which is probably necessary in reality).
// Almost like... @optional. Funny that, huh?
}
struct LayoutEngine<Model, Cell: UICollectionViewCell, SupplementaryView: UICollectionReusableView> : LayoutEngineType {
let cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell
let suplementaryViewForModel: (model: Model, indexPath: NSIndexPath) -> SupplementaryView?
init(cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell,
suplementaryViewForModel: (model: Model, indexPath: NSIndexPath) -> SupplementaryView?) {
self.cellForModel = cellForModel
self.suplementaryViewForModel = suplementaryViewForModel
}
}
struct SimpleLayoutEngine<Model, Cell: UICollectionViewCell> : LayoutEngineType {
let cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell
let suplementaryViewForModel: (model: Model, indexPath: NSIndexPath) -> UICollectionReusableView? = { _,_ in return nil }
init(cellForModel: (model: Model, indexPath: NSIndexPath) -> Cell) {
self.cellForModel = cellForModel
}
}
@jessesquires
Copy link

@rnapier this is awesome!

Great idea about ditching types and simply using closures.

I'll have to see if I can work out a similar approach to use in JSQDataSourcesKit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment