// // CollectionViewDataSource.swift // Khan Academy // // Created by Andy Matuschak on 10/14/14. // Copyright (c) 2014 Khan Academy. All rights reserved. // import UIKit /// A type which can produce and configure a cell for a given item. public protocol CollectionViewCellFactoryType { typealias Item typealias Cell: UICollectionViewCell func cellForItem(item: Item, inCollectionView collectionView: UICollectionView, atIndexPath indexPath: NSIndexPath) -> Cell } /// A concrete cell factory which makes use of UICollectionView's built-in cell reuse queue. public struct RegisteredCollectionViewCellFactory: CollectionViewCellFactoryType { private let reuseIdentifier: String private let cellConfigurator: (Cell, Item) -> () /// You must register Cell.Type with your collection view for `reuseIdentifier`. public init(reuseIdentifier: String, cellConfigurator: (Cell, Item) -> ()) { self.reuseIdentifier = reuseIdentifier self.cellConfigurator = cellConfigurator } public func cellForItem(item: Item, inCollectionView collectionView: UICollectionView, atIndexPath indexPath: NSIndexPath) -> Cell { // Will abort if you haven't already registered this reuse identifier for Cell.Type. let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as Cell cellConfigurator(cell, item) return cell } } /// A type-safe collection view data source. Clients can specify its sections and their contents via any CollectionType. Configuration of the cells is delegated to an external factory type. /// Use `bridgedDataSource` to get a UICollectionViewDataSource instance. public class CollectionViewDataSource< SectionCollection: CollectionType, Factory: CollectionViewCellFactoryType, Item where SectionCollection.Index == Int, SectionCollection.Generator.Element: CollectionType, SectionCollection.Generator.Element.Generator.Element == Item, SectionCollection.Generator.Element.Index == Int, Factory.Item == Item > { /// Clients are responsible for inserting/removing items in the collection view itself. public var sections: SectionCollection /// Returns an adapter for this data source that its bridgeable to Objective-C. public var bridgedDataSource: UICollectionViewDataSource { return bridgedCollectionViewDataSource } private let cellFactory: Factory private let bridgedCollectionViewDataSource: BridgeableCollectionViewDataSource! // The bridge is initialized with a reference to self, so Swift thinks (correctly) that this could conceivably be used uninitialized. public init(sections: SectionCollection, cellFactory: Factory) { self.sections = sections self.cellFactory = cellFactory bridgedCollectionViewDataSource = BridgeableCollectionViewDataSource( numberOfItemsInSectionHandler: { [weak self] in self?.numberOfItemsInSection($0) ?? 0 }, cellForItemAtIndexPathHandler: { [weak self] in self?.collectionView($0, cellForItemAtIndexPath: $1) }, numberOfSectionsHandler: { [weak self] in self?.numberOfSections() ?? 0 } ) } private func numberOfItemsInSection(section: Int) -> Int { // This collection's index is Int, which is a RandomAccessIndexType, so this is O(1). return countElements(sections[section]) } private func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { return cellFactory.cellForItem(sections[indexPath.section][indexPath.row], inCollectionView: collectionView, atIndexPath: indexPath) } private func numberOfSections() -> Int { // This collection's index is Int, which is a RandomAccessIndexType, so this is O(1). return countElements(sections) } } /// This separate type is necessary because CollectionViewDataSource itself is necessarily generic, so it can't be bridged to Objective-C. @objc private class BridgeableCollectionViewDataSource: NSObject, UICollectionViewDataSource { private let numberOfItemsInSectionHandler: Int -> Int private let cellForItemAtIndexPathHandler: (UICollectionView, NSIndexPath) -> UICollectionViewCell? private let numberOfSectionsHandler: () -> Int init(numberOfItemsInSectionHandler: Int -> Int, cellForItemAtIndexPathHandler: (UICollectionView, NSIndexPath) -> UICollectionViewCell?, numberOfSectionsHandler: () -> Int) { self.numberOfItemsInSectionHandler = numberOfItemsInSectionHandler self.cellForItemAtIndexPathHandler = cellForItemAtIndexPathHandler self.numberOfSectionsHandler = numberOfSectionsHandler } func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return numberOfSectionsHandler() } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return numberOfItemsInSectionHandler(section) } // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { return cellForItemAtIndexPathHandler(collectionView, indexPath)! // Better not have the data source bridge outlive the data source itself. } }