SourceModel 1.3.3

SourceModel 1.3.3

Tal ZionTalZ 维护。



  • stanwood

banner

Swift Version iOS 9+ Maintainability Build Status

SourceModel 是对 UICollectionViewDataSource/DelegateUITableViewDataSource/Delegate 的轻量级封装。它消除了使用 tableViewscollectionViews 时编写样板代码的负担,让您可以专注于重要的事情,即用数据填充单元格。

SourceModdel 专注于 关注点分离,其中单元格应只可供获取要呈现的数据并决定如何呈现。不应由 dataSource 控制单元格如何填充其数据,也不应了解视图。

使用方法

让我们跳入一些您需要熟悉的协议,以开始使用。让我们考虑一个显示产品的视图。

ModelCollection 协议

ModelCollection 代表一组相似的项目,在我们的例子中是产品。

public protocol ModelCollection {
    
    /// Returns the number of items
    var numberOfItems: Int { get }
    
    /// Returns the number of sections
    var numberOfSections: Int { get }
    
    /// A subscript to return a type in an indexPath
    subscript(indexPath: IndexPath) -> Model? { get }
    
    /// A subscript to return a collection dataType within a section
    subscript(section: Int) -> ModelCollection { get }
    
    /// Returns the cell type at indexPath
    func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type?
}

Model 协议

Model 代表集合中的单个项,即产品。

两者结合使用

让我们创建一个项目集合来观察其作用

我们将在稍后的阶段深入研究 Fillable 协议,现在,让我们假设我们有一个 ProductCell

struct Product { ... }

struct Products: ModelCollection {
    
    let items: [Product]
    
    var numberOfItems: Int {
    	return items.count
    }
    
    var numberOfSections: Int { 
    	return 1
    }
    
    subscript(indexPath: IndexPath) -> Model? { 
    	return items[indexPath.row] 
    }
    
    subscript(section: Int) -> ModelCollection { 
    	return self 
    }
    
    func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type? {
    	return ProductCell.self
    }
}

好吧,这很酷,但我们还可以让它更简单。 SourceModel 提供了一个 Elements 类,它封装了上面的内容,你只需要处理单元格。这就是它是如何工作的

class Products: Elements<Product>, Codable {
   func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type? {
    	return ProductCell.self
    }
}

现在我们完成了我们的集合,让我们创建一个 Products 的实例

let products = Products(items: [Product(), Product()])

Fillable 协议

每个单元格都必须遵循 Fillable 协议。这是您注入模型的地方,也是您填充单元格的地方。

class ProductCell: UICollectionViewCell, Fillable {

	....
	
	func fill(with model: Model?) {
		guard let product = model as? Product else { return }
		
		/// Handle product
	}
}

准备数据源和代理

现在我们有了模型、模型集合和单元格,让我们创建 DataSourceDelegate

class ProductsViewController: UIViewController {
   
   var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
   var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
   
   let products = Products(items: [Product(), Product()])
   
   override func viewDidLoad() {
      super.viewDidLoad()
      setupCollectionView(with: products)
   }
   
   private func setupCollectionView(with modelCollection: ModelCollection?)
      delegate = CollectionDataSource(modelCollection: modelCollection)
      dataSource = CollectionDelegate(modelCollection: modelCollection)
        
      collectionView.register(cellType: ProductCell.self)
        
      collectionView.delegate = delegate
      collectionView.dataSource = dataSource
   }
}

注意:要获取 register(cellType:) 函数,请在项目中添加 StanwoodCore

就是这样!😊

你可能想知道接下来该做什么?让我们看看更复杂的案例,以及 SourceModel 如何帮助您。

处理 Sections

处理分区可能会是一个繁琐的任务。你可以使用 ModelCollection 并注入一组分区(其他 ModelCollection)。或者,SourceModel 提供了 Sections。让我们看看我们之前使用的 Products 元素,并将其迁移到不同产品类别的分区中

class ProductsViewController: UIViewController {
   
   var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
   var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
   
   let sections: Sections!
   
   override func viewDidLoad() {
      super.viewDidLoad()
      
      let beautyProducts = Products(items: [Product(), Product()])
	   let groceryProducts = Products(items: [Product(), Product()])
	   
	   sections = Sections(sections: [beautyProducts, groceryProducts])
      setupCollectionView(with: products)
   }
   
   private func setupCollectionView(with modelCollection: ModelCollection?)
      delegate = CollectionDataSource(modelCollection: modelCollection)
      dataSource = CollectionDelegate(modelCollection: modelCollection)
        
      collectionView.register(cellType: ProductCell.self)
        
      collectionView.delegate = delegate
      collectionView.dataSource = dataSource
   }
}

处理 Headers

如果您想要添加分区标题,请确保您的 ModelCollection 符合 Headerable。您有一些可选的选项,具体取决于您是填充 collectionView 还是 tableView

Headerable

/// Headerable protocol to add header support for `UICollectionView` and `UITableView`
@objc public protocol Headerable {
    
    /// `UITableView` section header view
    @objc optional var headerView: UIView { get }
    
    /// `UICollectionView` header reusable header view
    @objc optional var reusableView: UICollectionReusableView { get }
}

示例

让我们给我们的产品添加一个标题

class Products: Elements<Product>, Codable, Headerable {
   
   var headerView: UIView { 
   		let view = CategoryHeaderView.loadFromNib()
       view?.backgroundColor = .clear
       view?.set(title: title)
       return view ?? UIView()
   }
   ...
}

注意:要获取loadFromNib()函数,请将StanwoodCore添加到您的项目中。[GitHub链接](https://github.com/stanwood/Stanwood_Core_iOS)

重要!

已知问题:在此阶段,ModelCollection不支持可重复使用的头部视图。这正在开发中。为了避免任何内存问题,您应该只对单个头部视图使用Headerable

实现Delegate模式

为了使用源设计模式源模型提供了一个Delegateable协议。让我们看看它如何与我们的ProductCell一起工作。

示例:单元格

protocol ProductCellDelegate: class {
	func productCellDidDoSomething(with product: Product)
}

/// Conform to `Delegateable`
class ProductCell: UICollectionViewCell, Fillable, Delegateable {

	private weak var delegate: ProductCellDelegate?
	
	....
	
	/// Set the delegate
	func set(delegate: AnyObject) {
		self.delegate = delegate as? ProductCellDelegate
	}
	
	@IBAction didTapSomeButton(_ sender: UIButton) {
	   delegate?. productCellDidDoSomething(with: product)
	}
}

**示例:ViewController

class ProductsViewController: UIViewController {
   
   var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
   var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
   
   let products = Products(items: [Product(), Product()])
   
   override func viewDidLoad() {
      super.viewDidLoad()
      setupCollectionView(with: products)
   }
   
   private func setupCollectionView(with modelCollection: ModelCollection?)
   
      delegate = CollectionDataSource(modelCollection: modelCollection)
      
      /// Inject `self` as the delegate
      dataSource = CollectionDelegate(modelCollection: modelCollection, delegate: self)
        
      collectionView.register(cellType: ProductCell.self)
        
      collectionView.delegate = delegate
      collectionView.dataSource = dataSource
   }
}

extension ProductsViewController: ProductCellDelegate {
	
	func productCellDidDoSomething(with product: Product) {
		/// Do something with product
	}
}

处理单元格索引

在某些情况下,我们需要知道单元格的位置索引。遵循Indexable协议来获取索引。

示例

class ProductCell: UICollectionViewCell, Fillable, Indexable {
	
	....
	
	func inject(_ indexPath: IndexPath) {
		/// Do something with the index
	}
}

路线图

  • 添加对delegate模式的支持
  • 添加对headers的支持
  • 为单元格注入indexPath添加Indexable协议
  • 添加reloadData(animated:)来重新加载已更改的模型
  • 将泛型添加到Fillable协议中
  • 添加可重复使用的Header/Footer视图
  • 添加对SwiftUI的支持
  • 添加对Combine的支持

请提交一个功能请求 issue。

示例

要运行示例项目,首先从 Git 仓库克隆,然后从 Example 目录运行pod install

要求

安装

可以通过CocoaPods获取SourceModel。要安装它,只需要将以下行添加到您的Podfile中

pod 'SourceModel'

作者

Tal Zion, [email protected]

授权

SourceModel可以使用MIT授权协议。有关更多信息,请参阅LICENSE文件。