P9CollectionViewHandler 1.0.0

P9CollectionViewHandler 1.0.0

Tae Hyun Na维护。



  • Tae Hyun Na

P9CollectionViewHandler

UICollectionView 有助于列出项目。但是,编写控制代码是一种 boilerplate。P9CollectionViewHandler 库帮助您轻松简单地处理 UICollectionView。

安装

您可以从我们的发行页面下载最新的框架文件。P9CollectionViewHandler 也可通过 CocoaPods 获取。要安装它,只需将以下行添加到 Podfile:pod 'P9CollectionViewHandler'

简单预览

let cellIdentifierForType:[String:String] = [ 
    "1" : RedCollectionViewCell.identifier(),
    "2" : GreenCollectionViewCell.identifier(),
    "3" : BlueCollectionViewCell.identifier()
] 

let supplementaryIdentifierForType:[String:String] = [ 
    "1" : HeaderCollectionReusableView.identifier(),
    "2" : FooterCollectionReusableView.identifier()
] 

let collectionView = UICollectionView(frame .zero)

let handler = P9CollectionViewHandler()
handler.delegate = self
handler.standby(identifier:"sample", cellIdentifierForType: cellIdentifierForType, supplementaryIdentifierForType: supplementaryIdentifierForType, collectionView: collectionView)

var records:[P9CollectionViewHandler.Record] = []
records.append(P9CollectionViewHandler.Record(type: "2", data: nil, extra: nil))
records.append(P9CollectionViewHandler.Record(type: "3", data: nil, extra: nil))

handler.sections.append(P9CollectionViewHandler.Section(headerType: "1", headerData: nil, footerType: nil, footerData: nil, records: records, extra: nil))

collectionView.reloadData()

func collectionViewHandlerCellDidSelect(handlerIdentifier: String, cellIdentifier: String, indexPath: IndexPath, data: Any?, extra: Any?) {
    // handling collectionview default select action
}

func collectionViewHandlerCellEvent(handlerIdentifier: String, cellIdentifier: String, eventIdentifier: String?, indexPath:IndexPath?, data: Any?, extra: Any?) {
    // handling custom event from cell
}

让我们一个个来看看。

使您的 collectionview cell 确认协议

为了使用 P9CollectionViewHandler,您的 collectionview cell 需要确认并实现以下 P9CollectionViewCellProtocol。如果您想要使用辅助视图,则还需要为它确认和实现相同的协议。

protocol P9CollectionViewCellProtocol: class {
    static func identifier() -> String
    static func instanceFromNib() -> UIView
    static func cellSizeForData(_ data: Any?, extra: Any?) -> CGSize
    func setData(_ data: Any?, extra: Any?)
    func setDelegate(_ delegate: P9CollectionViewCellDelegate)
    func setIndexPath(_ indexPath: IndexPath)
}

identifier 和 instanceFromNib 函数需要返回其标识符字符串和实例对象。但这两个函数是可选的。除非您需要自定义,否则不需要实现。因此,让 identifier 函数返回您的 collection view cell 的类名,并且 instanceFromNib 从标识符返回给定类的实例对象。

cellSizeForData 函数需要返回给定数据对应的 collectionView 单元格的大小。

static func cellSizeForData(_ data: Any?, extra: Any?) -> CGSize {
    guard let data = data as? CellDataModel else {
        return .zero
    }
    return CGSize(width: 40, height: 40)
}

setData 函数传递数据和额外对象以更新您的 collectionView 单元格。您可以执行业务代码来更新 collectionView 单元格。

func setData(_ data: Any?, extra: Any?) {
    guard let data = data as? CellDataModel else {
        return
    }
    self.data = data
    self.titleLabel.text = data.title ?? "Sample"
}

setDelegate 函数传递回调对象以反馈自定义事件。如果您的 collectionView 单元格有自定义事件,首先在控制器中确认 P9CollectionViewCellDelegate。

protocol P9CollectionViewCellDelegate: class {
    
    func collectionViewCellEvent(cellIdentifier:String, eventIdentifier:String?, indexPath:IndexPath?, data:Any?, extra:Any?)
}

extension ViewController: P9CollectionViewCellDelegate {
    // ...
}

并通过它设置代理和反馈自定义事件。

func setDelegate(_ delegate: P9CollectionViewCellDelegate) {
    self.delegate = delegate
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    delegate?.collectionViewCellEvent(cellIdentifier: SampleCollectionViewCell.identifier(), eventIdentifier: "touch", indexPath: nil, data: data, extra: nil)
}

setIndexPath 函数传递 indexPth 对象。您可以将此 indexPath 信息存储起来,并在事件代理调用时发送。

func setIndexPath(_ indexPath: IndexPath) {
    self.indexPath = indexPath
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    delegate?.collectionViewCellEvent(cellIdentifier: SampleCollectionViewCell.identifier(), eventIdentifier: "touch", indexPath: indexPath, data: data, extra: nil)
}

如果您的项目基于 Objective C,那么您需要确认并实现 P9CollectionViewCellObjcProtocol 以支持您的 collectionView 单元格。不是 P9CollectionViewCellProtocol,而是 P9CollectionViewCellObjcProtocol。它具有与 P9CollectionViewCellProtocol 相同的成员函数,但您必须实现所有函数,包括 identifier 和 instanceFromNib。

+ (NSString *)identifier {
    return @"SampleCollectionViewCell";
}

+ (UIView *)instanceFromNib {
    return [[[NSBundle mainBundle] loadNibNamed:[SampleCollectionViewCell identifier] owner:nil options:nil] firstObject];
}

处理

现在,您的 collectionView 单元格已准备好了。为 collectionView 单元格标识符和补充视图标识符创建字典。只需按您所愿创建唯一的类型键值,或者从服务器响应模型中使用某些类型值。

let cellIdentifierForType:[String:String] = [ 
    "1" : RedCollectionViewCell.identifier(),
    "2" : GreenCollectionViewCell.identifier(),
    "3" : BlueCollectionViewCell.identifier()
] 

let supplementaryIdentifierForType:[String:String] = [ 
    "1" : HeaderCollectionReusableView.identifier(),
    "2" : FooterCollectionReusableView.identifier()
] 

将它们设置为处理器。别忘了设置代理来从处理器获取反馈。

let handler = P9CollectionViewHandler()
handler.standby(identifier:"sample", cellIdentifierForType: cellIdentifierForType, supplementaryIdentifierForType: supplementaryIdentifierForType, collectionView: collectionView)
handler.delegate = self

并且,您需要为处理器创建模型数据。请放心,您可以使用自己的模型而无需任何更改。只需将它们包装到处理器模型中。以下是处理器模型的定义。

@objc(P9CollectionViewRecord) public class Record : NSObject {
    var type:String
    var data:Any?
    var extra:Any?
    @objc public init(type:String, data:Any?, extra:Any?=nil) {
        self.type = type
        self.data = data
        self.extra = extra
    }
}
    
@objc(P9CollectionViewSection) public class Section : NSObject {
    var headerType:String?
    var headerData:Any?
    var footerType:String?
    var footerData:Any?
    var extra:Any?
    var records:[Record]?
    @objc public init(headerType:String?, headerData:Any?, footerType:String?, footerData:Any?, records:[Record]?, extra:Any?) {
        self.headerType = headerType
        self.headerData = headerData
        self.footerType = footerType
        self.footerData = footerData
        self.records = records
        self.extra = extra
    }
}

您可以根据常规 collectionView 数据结构创建具有 N 个部分和每部分 M 个记录的模型。根据需要创建记录和部分,并将它们设置为处理器。

var records:[P9CollectionViewHandler.Record] = []
records.append(P9CollectionViewHandler.Record(type: "1", data: nil, extra: nil))
records.append(P9CollectionViewHandler.Record(type: "2", data: nil, extra: nil))

handler.sections.append(P9CollectionViewHandler.Section(headerType: nil, headerData: nil, footerType: nil, footerData: nil, records: records, extra: nil))

然后重新加载目标 collectionView。

collectionView.reloadData()

现在,通过确认协议 P9CollectionViewHandlerDelegate 从每个 collectionView 单元格获取消息。以下是协议和示例实现。

@objc public protocol P9CollectionViewHandlerDelegate: class {
    @objc optional func collectionViewHandlerWillBeginDragging(handlerIdentifier:String, contentSize:CGSize, contentOffset:CGPoint)
    @objc optional func collectionViewHandlerDidScroll(handlerIdentifier:String, contentSize:CGSize, contentOffset:CGPoint)
    @objc optional func collectionViewHandlerDidEndScroll(handlerIdentifier:String, contentSize:CGSize, contentOffset:CGPoint)
    @objc optional func collectionViewHandler(handlerIdentifier:String, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
    @objc optional func collectionViewHandler(handlerIdentifier:String, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath)
    @objc optional func collectionViewHandler(handlerIdentifier:String, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
    @objc optional func collectionViewHandler(handlerIdentifier:String, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath)
    @objc optional func collectionViewHandler(handlerIdentifier:String, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
    @objc optional func collectionViewHandler(handlerIdentifier:String, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
    @objc optional func collectionViewHandler(handlerIdentifier:String, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
    @objc optional func collectionViewHandlerCellDidSelect(handlerIdentifier:String, cellIdentifier:String, indexPath:IndexPath, data:Any?, extra:Any?)
    @objc optional func collectionViewHandlerCellEvent(handlerIdentifier:String, cellIdentifier:String, eventIdentifier:String?, indexPath:IndexPath?, data:Any?, extra:Any?)
}
extension ViewController: P9CollectionViewHandlerDelegate {
    
    func collectionViewHandlerCellDidSelect(handlerIdentifier: String, cellIdentifier: String, indexPath: IndexPath, data: Any?, extra: Any?) {
        
        print("handler \(handlerIdentifier) cell \(cellIdentifier) indexPath \(indexPath.section):\(indexPath.row) did select")
    }
    
    func collectionViewHandlerCellEvent(handlerIdentifier: String, cellIdentifier:String, eventIdentifier:String?, indexPath: IndexPath?, data: Any?, extra: Any?) {
        
        print("handler \(handlerIdentifier) cell \(cellIdentifier) event \(eventIdentifier ?? "")")
    }
}

如果您不喜欢庞大的 switch 代码,则可以使用每个事件标识符的回调函数(或块)。

enum EventId: String {
    case clickMe
}

handler.registerCallback(callback: doClickMe(indexPath:data:extra:), forCellIdentifier: CollectionViewCell.identifier(), withEventIdentifier: EventId.clickMe.rawValue)

extension TableViewCell {
    
    func doClickMe(indexPath:IndexPath?, data:Any?, extra:Any?) {
        
        print("Got Click Me.")
    }
}

您还可以通过不传递事件标识符来使用回调函数(或块)选择单元格事件。

handler.registerCallback(callback: collectionViewCellSelectHandler(indexPath:data:extra:), forCellIdentifier: CollectionViewCell.identifier())

许可

适用于 MIT 许可证。 http://en.wikipedia.org/wiki/MIT_License