Skip to content

Instantly share code, notes, and snippets.

@cmc5788
Last active April 11, 2017 05:25
Show Gist options
  • Save cmc5788/7e68ebb9ae853b4fc58c286798d2b96d to your computer and use it in GitHub Desktop.
Save cmc5788/7e68ebb9ae853b4fc58c286798d2b96d to your computer and use it in GitHub Desktop.
rxy collection view
import UIKit
import RxSwift
class RxCollectionView<Element : Comparable>: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public typealias E = Element
var elements : [E] = []
public var cellSizeChooser : (RxCollectionView<E>, E, Int) -> CGSize = { cv, _, _ in
let dim = cv.bounds.size.width / 3.0
return CGSize(width: dim, height: dim)
}
public var reuseIdentifierFactory : (RxCollectionView<E>, E, Int) -> String = { _, _, _ in "RxCollectionViewCell" }
public var cellBinder : (E, UICollectionViewCell, Int) -> Void = { e, c, _ in
let l : UILabel = c.viewWithTag(1) as? UILabel ?? UILabel()
if l.tag == 0 {
l.tag = 1
c.addSubview(l)
l.snp.makeConstraints { make in
make.width.height.equalToSuperview().offset(-16)
make.center.equalToSuperview()
}
l.lineBreakMode = .byWordWrapping
l.numberOfLines = 0
}
l.text = "\(e)"
}
init() {
super.init(frame: CGRect(), collectionViewLayout: UICollectionViewFlowLayout())
_init()
}
override init(frame: CGRect, collectionViewLayout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: collectionViewLayout)
_init()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
_init()
}
private func _init() {
self.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "RxCollectionViewCell")
self.delegate = self
self.dataSource = self
}
deinit {
self.delegate = nil
self.dataSource = nil
}
private func deleteElements(_ e : [E]) {
if e.count > 1 { print("delete got a batch > 1 !!!!!!!") }
var indexPaths : [IndexPath] = []
var filteredElements = self.elements
e.forEach { e in
if let i = self.elements.index(of: e) {
filteredElements = filteredElements.filter { $0 != e }
indexPaths.append(IndexPath(row: i, section: 0))
}
}
if indexPaths.isEmpty { return }
self.elements = filteredElements.sorted()
self.deleteItems(at: indexPaths)
}
private func updateOrAddElements(_ e : [E]) {
if e.count > 1 { print("append got a batch > 1 !!!!!!!") }
var updatingRows : [IndexPath] = []
let `e` : [E] = e.filter {
if let i = self.elements.index(of: $0) {
updatingRows.append(IndexPath(row: i, section: 0))
self.elements[i] = $0
return false
}
return true
}
if !updatingRows.isEmpty { self.reloadItems(at: updatingRows) }
if e.isEmpty { return }
self.elements.append(contentsOf: e)
self.elements.sort()
var elementNotFound = false
var addingRows : [IndexPath] = []
e.forEach {
if let i = self.elements.index(of: $0) {
addingRows.append(IndexPath(row: i, section: 0))
} else {
print("addElements elementNotFound: SHOULD NEVER HAPPEN")
elementNotFound = true
}
}
if elementNotFound || addingRows.isEmpty {
print("addElements falling back to reloadSections: SHOULD NEVER HAPPEN")
self.reloadSections([0])
} else {
self.insertItems(at: addingRows)
}
}
public func bindAdd(_ source : Observable<E>, bufferTimeSpan : RxTimeInterval = 0.1) -> Disposable {
return source
.buffer(timeSpan: bufferTimeSpan, count: Int.max, scheduler: Schedulers.main)
.filter { !$0.isEmpty }
.observeOn(Schedulers.main)
.do(onNext: self.updateOrAddElements)
.subscribe()
}
public func bindDelete(_ source : Observable<E>, bufferTimeSpan : RxTimeInterval = 0.1) -> Disposable {
return source
.buffer(timeSpan: bufferTimeSpan, count: Int.max, scheduler: Schedulers.main)
.filter { !$0.isEmpty }
.observeOn(Schedulers.main)
.do(onNext: self.deleteElements)
.subscribe()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let row = indexPath.row
let e = elements[row]
return cellSizeChooser(self, e, row)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets.zero
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return elements.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let row = indexPath.row
let e = elements[row]
let reuseIdentifier = reuseIdentifierFactory(self, e, row)
let cell = dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cellBinder(e, cell, row)
return cell
}
}
extension ObservableType where Self.E : Comparable {
func bindCollectionViewAdd<T>(_ collectionView : RxCollectionView<T>, bufferTimeSpan : RxTimeInterval = 0.1) -> Disposable {
return collectionView.bindAdd(self as! Observable<T>, bufferTimeSpan: bufferTimeSpan)
}
func bindCollectionViewDelete<T>(_ collectionView : RxCollectionView<T>, bufferTimeSpan : RxTimeInterval = 0.1) -> Disposable {
return collectionView.bindDelete(self as! Observable<T>, bufferTimeSpan: bufferTimeSpan)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment