Last active
November 5, 2019 03:55
-
-
Save 5SMNOONMS5/d295e328d34812062188be88960ccdf4 to your computer and use it in GitHub Desktop.
Generic types collection view with default selection [ Copy and paste it into your playground ]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: A UIKit based Playground for presenting user interface | |
import UIKit | |
import PlaygroundSupport | |
// ****************************************** | |
// | |
// MARK: - Config injection | |
// | |
// ****************************************** | |
struct CCCollectionViewConfig { | |
var itemSize: CGSize? | |
var minimumInteritemSpacing: CGFloat? | |
var minimumLineSpacing: CGFloat? | |
var allowsDefaultSelection: Bool = true | |
} | |
// ****************************************** | |
// | |
// MARK: - Generic CCCollectionView | |
// | |
// ****************************************** | |
final class CCCollectionView<T, Cell: UICollectionViewCell>: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegate { | |
typealias SelectHandler = (T, IndexPath?) -> Void | |
typealias CellConfigure = (Cell, T) -> Void | |
var contents: [T] = [] { | |
didSet { | |
DispatchQueue.main.async { | |
self.reloadData() | |
self.selectedIndexPath = IndexPath(item: (self.config.allowsDefaultSelection) ? 0 : -1, section: 0) | |
} | |
} | |
} | |
var configure: CellConfigure | |
var selectHandler: SelectHandler | |
var selectedIndexPath: IndexPath | |
private var config: CCCollectionViewConfig | |
private let identifier = "identifier" | |
init(contents: [T], config: CCCollectionViewConfig, configure: @escaping CellConfigure, selectHandler: @escaping SelectHandler) { | |
self.config = config | |
self.contents = contents | |
self.configure = configure | |
self.selectHandler = selectHandler | |
self.selectedIndexPath = IndexPath(item: (config.allowsDefaultSelection) ? 0 : -1, section: 0) | |
let layout = UICollectionViewFlowLayout() | |
if let size = config.itemSize { | |
layout.itemSize = size | |
} | |
layout.minimumInteritemSpacing = config.minimumInteritemSpacing ?? 0 | |
layout.minimumLineSpacing = config.minimumLineSpacing ?? 0 | |
layout.scrollDirection = .vertical | |
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) | |
super.init(frame: .zero, collectionViewLayout: layout) | |
setup() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
private func setup() { | |
register(Cell.self, forCellWithReuseIdentifier: identifier) | |
super.delegate = self | |
super.dataSource = self | |
allowsMultipleSelection = false | |
isScrollEnabled = false | |
backgroundColor = .clear | |
} | |
// ****************************************** | |
// | |
// MARK: - UICollectionViewDataSource, UICollectionViewDelegate | |
// | |
// ****************************************** | |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
return contents.count | |
} | |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell | |
cell.isSelected = (selectedIndexPath == indexPath) | |
let content = contents[indexPath.row] | |
configure(cell, content) | |
return cell | |
} | |
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | |
let content = contents[indexPath.row] | |
selectHandler(content, indexPath) | |
/// If selected item is equal to current selected item, ignore it | |
if selectedIndexPath == indexPath { | |
return | |
} | |
/// Handle selected cell | |
let selectedCell = collectionView.cellForItem(at: indexPath) | |
selectedCell?.isSelected = true | |
/// Handle deselected cell | |
let deselectItem = collectionView.cellForItem(at: selectedIndexPath) | |
deselectItem?.isSelected = false | |
selectedIndexPath = indexPath | |
} | |
} | |
// ****************************************** | |
// | |
// MARK: - CollectionViewCell | |
// | |
// ****************************************** | |
final class CCCollectionViewCell: UICollectionViewCell { | |
var title: String = "" { | |
didSet { | |
titleLabel.text = title | |
} | |
} | |
private let titleLabel: UILabel = { | |
let label = UILabel() | |
return label | |
}() | |
var _isSelected: Bool = false | |
override var isSelected: Bool { | |
get { | |
return _isSelected | |
} | |
set(newValue) { | |
_isSelected = newValue | |
updateSelection() | |
} | |
} | |
private func updateSelection() -> Void { | |
contentView.layer.borderColor = isSelected ? UIColor.red.cgColor : UIColor.green.cgColor | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
contentView.addSubview(titleLabel) | |
contentView.layer.cornerRadius = 2.0 | |
contentView.layer.borderWidth = 2.0 | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
} | |
// ****************************************** | |
// | |
// MARK: - MyViewController | |
// | |
// ****************************************** | |
class MyViewController : UIViewController { | |
private let fakeContents = Array(repeating: "123123", count: 40) | |
/// Collection View Config | |
private lazy var config: CCCollectionViewConfig = { | |
return CCCollectionViewConfig(itemSize: CGSize(width: 100, height: 30), | |
minimumInteritemSpacing: 4.0, | |
minimumLineSpacing: 4.0) | |
}() | |
/// Collection View | |
private lazy var collectionView: CCCollectionView<String, CCCollectionViewCell> = { | |
let cv = CCCollectionView<String, CCCollectionViewCell>(contents: fakeContents, config: config, configure: { (cell: CCCollectionViewCell, content) in | |
cell.title = content | |
}) { [weak self] (content, indexPath) in | |
guard let self = self else { return } | |
guard let row = indexPath?.row else { return } | |
} | |
return cv | |
}() | |
override func loadView() { | |
let view = UIView() | |
view.backgroundColor = .white | |
self.view = view | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
collectionView.frame = UIScreen.main.bounds | |
view.addSubview(collectionView) | |
} | |
} | |
// Present the view controller in the Live View window | |
PlaygroundPage.current.liveView = MyViewController() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment