Skip to content

Instantly share code, notes, and snippets.

@kazuhiro4949
Last active February 11, 2020 03:08
Show Gist options
  • Save kazuhiro4949/230268f146e891540d4849b963dbe6cd to your computer and use it in GitHub Desktop.
Save kazuhiro4949/230268f146e891540d4849b963dbe6cd to your computer and use it in GitHub Desktop.
UICollectionViewController including type erased delegate
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
class MyViewController<Delegate: MyViewControllerDelegate>: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var delegate: AnyMyViewControllerDelegate<Delegate>?
var items = [Array<Delegate.Item>]() {
didSet {
collectionView?.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "identifier")
}
init(_ delegate: AnyMyViewControllerDelegate<Delegate>, configure: (register: (cellClass: AnyClass, identifier: String) -> Void) -> Void = { _ in }) {
super.init(collectionViewLayout: UICollectionViewFlowLayout())
let register = { [weak self] (cellClass: AnyClass, identifier: String) -> Void in
self?.collectionView?.registerClass(cellClass, forCellWithReuseIdentifier: identifier)
}
self.delegate = delegate
configure(register: register)
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
func dequeue(from indexPath: NSIndexPath) -> ((identifier: String) -> UICollectionViewCell?) {
return { (identifier: String) -> UICollectionViewCell? in
return collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath)
}
}
collectionView.dequeueReusableCellWithReuseIdentifier("identifier", forIndexPath: indexPath)
let dequeueHandler = dequeue(from: indexPath)
let item = items[indexPath.section][indexPath.row]
guard let cell = delegate?.myViewController(self, dequeue: dequeueHandler, cellFor: item) else {
fatalError("cannot get cell")
}
return cell
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].count
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
delegate?.myViewController(self, didSelect: items[indexPath.section][indexPath.row])
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return items.count
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let contentSize = delegate?.myViewController(self, layout: collectionViewLayout, sizeFor: items[indexPath.section][indexPath.row])
return contentSize ?? collectionViewLayout.collectionViewContentSize()
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
}
class AnyMyViewControllerDelegate<Delegate: MyViewControllerDelegate> {
private weak var delegate: Delegate?
init(delegate: Delegate) {
self.delegate = delegate
}
func myViewController(vc: MyViewController<Delegate>, dequeue: (identifier: String) -> UICollectionViewCell?, cellFor item: Delegate.Item) -> UICollectionViewCell? {
return delegate?.myViewController(vc, dequeue: dequeue, cellFor: item)
}
func myViewController(vc: MyViewController<Delegate>, layout collectionViewLayout: UICollectionViewLayout, sizeFor item: Delegate.Item) -> CGSize? {
return delegate?.myViewController(vc, layout: collectionViewLayout, sizeFor: item)
}
func myViewController(vc: MyViewController<Delegate>, didSelect item: Delegate.Item) {
delegate?.myViewController(vc, didSelect: item)
}
}
protocol MyViewControllerDelegate: class {
associatedtype Item
func myViewController(vc: MyViewController<Self>, dequeue: ((identifier: String) -> UICollectionViewCell?), cellFor item: Item) -> UICollectionViewCell?
func myViewController(vc: MyViewController<Self>, layout collectionViewLayout: UICollectionViewLayout, sizeFor item: Item) -> CGSize?
func myViewController(vc: MyViewController<Self>, didSelect item: Item)
}
extension MyViewControllerDelegate {
func myViewController(vc: MyViewController<Self>, layout collectionViewLayout: UICollectionViewLayout, sizeFor item: Item) -> CGSize? { return nil }
func myViewController(vc: MyViewController<Self>, didSelect item: Item) {}
}
// #################################################
// Create sample ViewController the above Framework
// It has two types of cell in CollectionView.
// #################################################
enum Element {
case Banner(name: String)
case Content(name: String)
}
final class ViewController: UIViewController, MyViewControllerDelegate {
typealias Item = Element
lazy var childVc: MyViewController<ViewController> = {
return MyViewController(AnyMyViewControllerDelegate(delegate: self)) { (register) in
register(cellClass: UICollectionViewCell.self, identifier: "cell")
}
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Title"
addChildViewController(childVc)
childVc.view.frame = view.bounds
childVc.view.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
view.addSubview(childVc.view)
childVc.didMoveToParentViewController(self)
childVc.items = [[
.Banner(name: "バナー"),
.Content(name: "コンテンツ"), .Content(name: "コンテンツ"), .Content(name: "コンテンツ"), .Content(name: "コンテンツ"),
.Content(name: "コンテンツ"), .Content(name: "コンテンツ"), .Content(name: "コンテンツ"),.Content(name: "コンテンツ")
]]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK:- MyViewControlelrDelegate
func myViewController(vc: MyViewController<ViewController>, dequeue: ((identifier: String) -> UICollectionViewCell?), cellFor item: Item) -> UICollectionViewCell? {
let cell = dequeue(identifier: "cell")
switch item {
case .Banner(_):
cell?.backgroundColor = .redColor()
case .Content(_):
cell?.backgroundColor = .whiteColor()
}
return cell
}
func myViewController(vc: MyViewController<ViewController>, layout collectionViewLayout: UICollectionViewLayout, sizeFor item: Item) -> CGSize? {
let contentLength = Int(vc.view.frame.width) / 2
switch item {
case .Banner(_):
return CGSize(width: vc.view.frame.width, height: 60)
case .Content(_):
return CGSize(width: contentLength, height: contentLength)
}
}
}
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
XCPlaygroundPage.currentPage.liveView = UINavigationController(rootViewController: ViewController(nibName: nil, bundle: nil))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment