Add DocC (#2)
This commit is contained in:
parent
e92d676416
commit
dae3d67e9a
|
@ -0,0 +1,5 @@
|
|||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- platform: ios
|
||||
documentation_targets: [ListDiffUI]
|
|
@ -1,5 +1,7 @@
|
|||
import UIKit
|
||||
|
||||
/// Cell class that extends from UICollectionViewCell.
|
||||
///
|
||||
open class ListCell: UICollectionViewCell {
|
||||
|
||||
class var reuseIdentifier: String {
|
||||
|
@ -13,6 +15,10 @@ open class ListCell: UICollectionViewCell {
|
|||
controller?.cellDidLayoutSubviews()
|
||||
}
|
||||
|
||||
/// Disallow overriding `prepareForReuse()`.
|
||||
///
|
||||
/// For cell reuse logic, it should be handled in ``ListCellController``'s life cycle methods,
|
||||
/// e.g., ``ListCellController/didMount(onCell:)``, ``ListCellController/willUnmount(onCell:)``.
|
||||
public override func prepareForReuse() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import UIKit
|
||||
|
||||
/// Extend this protocol from ListCellController subclass and provide the associatedtype to use the delegate pattern.
|
||||
/// The same delegate passed in from ``ListSection`` will be available to the ListCellController as the `delegate` property.
|
||||
public protocol ListCellControllerWithDelegate {
|
||||
associatedtype DelegateType
|
||||
var delegate: DelegateType? { get }
|
||||
|
@ -12,6 +14,7 @@ extension ListCellControllerWithDelegate where Self: AnyListCellController {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to wrap AnyObject with a weak reference.
|
||||
public final class WeakAnyObject {
|
||||
|
||||
public static var none = WeakAnyObject(object: nil)
|
||||
|
@ -28,6 +31,7 @@ public protocol ListCellControllerViewModelProviding {
|
|||
associatedtype ListViewModelType: ListViewModel
|
||||
}
|
||||
|
||||
/// Generic cell controller class.
|
||||
open class ListCellController<
|
||||
ListViewModelType: ListViewModel & Equatable,
|
||||
ListViewStateType: ListViewState,
|
||||
|
@ -45,6 +49,7 @@ open class ListCellController<
|
|||
private var updatingViewModel = false
|
||||
private var didLayoutSubiewsWhenInvalidatingLayout = false
|
||||
|
||||
/// Designated initializer.
|
||||
required public init(
|
||||
viewModel: ListViewModel,
|
||||
delegate: WeakAnyObject,
|
||||
|
@ -56,6 +61,10 @@ open class ListCellController<
|
|||
super.init(viewModel: viewModel, delegate: delegate, context: context)
|
||||
}
|
||||
|
||||
/// Used to update ViewState.
|
||||
///
|
||||
/// Calling it will trigger ``ListCellController/configureCell(cell:)`` as long as the controller has a mounted cell.
|
||||
///
|
||||
public func updateState(_ viewState: ListViewStateType) {
|
||||
self.viewState = viewState
|
||||
if !updatingViewModel {
|
||||
|
@ -63,36 +72,60 @@ open class ListCellController<
|
|||
}
|
||||
}
|
||||
|
||||
/// Subclassing point. Provide cell size based on the container size (collection view size inset by content edge insets).
|
||||
///
|
||||
/// If the collection view can change size, you need to implement proper layout invalidation logic in the flow layout object,
|
||||
/// in order to make sure this method is invoked to retrieve the updated cell size.
|
||||
///
|
||||
open override func itemSize(containerSize: CGSize) -> CGSize {
|
||||
return .zero
|
||||
}
|
||||
|
||||
/// Subclassing point. Configure cell based on ViewModel and ViewState.
|
||||
///
|
||||
/// It will be invoked when ViewModel or ViewState updates, and when a cell is mounted.
|
||||
///
|
||||
open func configureCell(cell: ListCellType) {
|
||||
}
|
||||
|
||||
/// Subclassing point. Called when cell appears within collection view's bounds.
|
||||
open func didAppear(cell: ListCellType) {
|
||||
}
|
||||
|
||||
// cell is optional in this case since the cell might already be reused.
|
||||
/// Subclassing point. Called when cell fully disappears from collection view's bounds.
|
||||
///
|
||||
/// Cell is optional, since the cell might already be reused.
|
||||
///
|
||||
open func willDisappear(cell: ListCellType?) {
|
||||
}
|
||||
|
||||
/// Subclassing point. Called when cell fully appears within collection view's bounds.
|
||||
open func didFullyAppear(cell: ListCellType) {
|
||||
}
|
||||
|
||||
// cell is optional in this case since the cell might already be reused.
|
||||
/// Subclassing point. Called when cell partially disappears from collection view's bounds.
|
||||
///
|
||||
/// Cell is optional, since the cell might already be reused.
|
||||
///
|
||||
open func willPartiallyDisappear(cell: ListCellType?) {
|
||||
}
|
||||
|
||||
/// Subclassing point.
|
||||
open func didMount(onCell cell: ListCellType) {
|
||||
}
|
||||
|
||||
/// Subclassing point.
|
||||
open func willUnmount(onCell cell: ListCellType) {
|
||||
}
|
||||
|
||||
/// Subclassing point. Invoked when ViewModel updates.
|
||||
///
|
||||
/// You may perform logic that updates ViewState here.
|
||||
///
|
||||
open func didUpdateViewModel(oldViewModel: ListViewModelType) {
|
||||
}
|
||||
|
||||
/// Subclassing point. Update cell layout based on ViewModel and ViewState.
|
||||
open func cellDidLayoutSubviews(cell: ListCellType) {
|
||||
}
|
||||
|
||||
|
@ -172,12 +205,14 @@ extension ListCellController: ListCellControllerViewModelProviding {
|
|||
public typealias ListViewModelType = ListViewModelType
|
||||
}
|
||||
|
||||
/// Type-erased base class of ``ListCellController``. Subclass ``ListCellController`` instead.
|
||||
open class AnyListCellController {
|
||||
|
||||
class var cellType: ListCell.Type {
|
||||
fatalError("Must be provided by subclass")
|
||||
}
|
||||
|
||||
/// Used for accessing context objects passed in from ``ListDiffDataSource/init(collectionView:appleUpdatesAsync:contextObjects:)``.
|
||||
public let context: ListDiffContext
|
||||
|
||||
weak var cell: ListCell? {
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import Foundation
|
||||
|
||||
/// Used for passing in dependencies from ``ListDiffDataSource/init(collectionView:appleUpdatesAsync:contextObjects:)``
|
||||
/// and available on ``AnyListCellController/context`` property.
|
||||
///
|
||||
/// Objects are stored in a dictonary with `ObjectIdentifier(type(of: object))` as its key/
|
||||
/// Therefore you can not pass in multiple instances of the same class.
|
||||
///
|
||||
/// Main use case for context object is to make certain dependencies available in ``ListCellController``,
|
||||
/// and not having to pass them in with ViewModels.
|
||||
///
|
||||
public final class ListDiffContext {
|
||||
|
||||
private let objects: [ObjectIdentifier: AnyObject]
|
||||
|
|
|
@ -3,22 +3,19 @@ import UIKit
|
|||
|
||||
private let cellAppearanceUpdateInterval: CFTimeInterval = 0.1
|
||||
|
||||
/// ListDiffDataSource
|
||||
/// Data source object to provide data to UICollectionView.
|
||||
///
|
||||
/// DataSource object to provide data to UICollectionView.
|
||||
/// To use ListDiffUI, create an instance of ListDiffDataSource with the corresponding UICollectionView.
|
||||
/// ListDiffDataSource will sets itself as both delegate and dataSource of the UICollectionView.
|
||||
/// Call ``ListDiffDataSource/setRootSection(_:animate:completion:)`` on the data source object to update the view model.
|
||||
///
|
||||
/// UICollectionView's layout must be an instance of UICollectionFlowLayout (or its subclass), as it implements
|
||||
/// func collectionView(_: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath)
|
||||
/// of UICollectionViewDelegateFlowLayout.
|
||||
///
|
||||
/// ListDiffDataSource sets itself as both delegate and dataSource of the UICollectionView. If you need to be the
|
||||
/// delegate of the UICollectionView as well, set your instance as the collectionViewDelegate of the
|
||||
/// ListDiffDataSource object instead.
|
||||
///
|
||||
/// appleUpdatesAsync: Set this to true on init to perform diffing and UI updates off main queue. Note that this will
|
||||
/// cause SectionComponent's build() calls to happen off main queue as well.
|
||||
public final class ListDiffDataSource: NSObject {
|
||||
|
||||
/// Forwards calls from the delegate of the UICollectionView.
|
||||
///
|
||||
/// ListDiffDataSource instance will set itself as the delegate of the collection view.
|
||||
/// If you need to be the delegate of the UICollectionView as well, set your instance as the collectionViewDelegate instead.
|
||||
///
|
||||
public weak var collectionViewDelegate: UICollectionViewDelegate?
|
||||
|
||||
private let collectionView: UICollectionView
|
||||
|
@ -34,6 +31,15 @@ public final class ListDiffDataSource: NSObject {
|
|||
private var viewDataModels: [ListDiffDataModel] = []
|
||||
private var viewDataModelsOnQueue: [ListDiffDataModel] = []
|
||||
|
||||
/// Designated initializer of ListDiffDataSource.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - collectionView: UICollectionView's layout must be an instance of UICollectionFlowLayout (or its subclass), as it implements
|
||||
/// `collectionView(_:layout:sizeForItemAt:)` of UICollectionViewDelegateFlowLayout.
|
||||
/// - appleUpdatesAsync: If true, diffing will be performed asynchronously on a background thread.
|
||||
/// - contextObjects: A list of objects passed in here will be available to use inside ``ListCellController``.
|
||||
/// This is a way to pass in dependencies (e.g. logger) so that you don't have to piggyback them on ViewModels.
|
||||
///
|
||||
public init(collectionView: UICollectionView, appleUpdatesAsync: Bool = false, contextObjects: AnyObject...) {
|
||||
precondition(collectionView.collectionViewLayout is UICollectionViewFlowLayout)
|
||||
self.collectionView = collectionView
|
||||
|
@ -45,6 +51,15 @@ public final class ListDiffDataSource: NSObject {
|
|||
collectionView.delegate = self
|
||||
}
|
||||
|
||||
/// Update view model with Section.
|
||||
///
|
||||
/// ``Section`` provides a descriptive interface to describe the structure of the collection that supports heterogenity by design.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - section: Root section that describes the entire collection.
|
||||
/// - animated: Whether diff update (insert/deletion/update) will be animated or not.
|
||||
/// - completion: Completion block that gets called after diff update.
|
||||
///
|
||||
public func setRootSection(_ section: Section?, animate: Bool = false, completion: (() -> Void)? = nil) {
|
||||
if appleUpdatesAsync {
|
||||
queue.async {
|
||||
|
|
|
@ -8,6 +8,33 @@ struct ListDiffDataModel {
|
|||
}
|
||||
|
||||
/// Base class and available for subclassing.
|
||||
///
|
||||
/// Section provides an intuitive interface for developers to describe how the UICollectionView should look like, that supports heterogeneity by design.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// CompositeSection(
|
||||
/// ListSection<
|
||||
/// Bool, LoadingSpinnerController
|
||||
/// >(isLoading) {
|
||||
/// $0 ? LoadingSpinnerViewModel() : nil
|
||||
/// },
|
||||
/// ListSection<
|
||||
/// ItemViewModel, ItemCellController
|
||||
/// >(items)
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// descibes an optional loading spinner cell, and a list of items.
|
||||
///
|
||||
/// For its concrete implementations, ``CompositeSection`` is used for composing multiple child sections.
|
||||
/// ``ListSection`` is used for declaring a list of homogeneous cells.
|
||||
/// ``ListRenderSection`` is used for declaring a list of heterogenous cells.
|
||||
/// It is also available for subclassing.
|
||||
///
|
||||
/// Note that it's ``Section/build()`` function will be called off main thread if asynchronous diffing is enabled for ``ListSectionDataSource``.
|
||||
///
|
||||
open class Section {
|
||||
|
||||
var identifier: String {
|
||||
|
@ -20,6 +47,20 @@ open class Section {
|
|||
}
|
||||
|
||||
/// Supports composing multiple Sections.
|
||||
///
|
||||
/// Example:
|
||||
/// ```swift
|
||||
/// CompositeSection(
|
||||
/// ListSection<
|
||||
/// Bool, LoadingSpinnerController
|
||||
/// >(isLoading) {
|
||||
/// $0 ? LoadingSpinnerViewModel() : nil
|
||||
/// },
|
||||
/// ListSection<
|
||||
/// ItemViewModel, ItemCellController
|
||||
/// >(items)
|
||||
/// )
|
||||
/// ```
|
||||
public final class CompositeSection: Section {
|
||||
|
||||
private let s: [Section?]
|
||||
|
@ -53,6 +94,13 @@ public final class ListSection<
|
|||
private let transform: (T) -> ListCellControllerType.ListViewModelType?
|
||||
private let delegate: WeakAnyObject
|
||||
|
||||
/// Initialize from a generic array of models.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - models: Generic model array.
|
||||
/// - delegate: Delegate object for ``ListCellController``.
|
||||
/// - transform: A transform function that transforms from model type T to ListViewModel
|
||||
/// may run on background thread if asynchronous diffing is enabled for ``ListSectionDataSource``.
|
||||
public init(
|
||||
_ models: [T], delegate: WeakAnyObject = .none,
|
||||
transform: @escaping (T) -> ListCellControllerType.ListViewModelType?
|
||||
|
@ -74,9 +122,9 @@ public final class ListSection<
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenience init for building a single cell.
|
||||
extension ListSection {
|
||||
|
||||
/// Convenience init for building a single cell.
|
||||
public convenience init(
|
||||
_ model: T, delegate: WeakAnyObject = .none,
|
||||
transform: @escaping (T) -> ListCellControllerType.ListViewModelType?
|
||||
|
@ -85,13 +133,14 @@ extension ListSection {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenience init for when input type is ListViewModelType.
|
||||
extension ListSection where T == ListCellControllerType.ListViewModelType {
|
||||
|
||||
/// Convenience init for when input type is ListViewModelType, where transform function is omitted.
|
||||
public convenience init(_ model: T, delegate: WeakAnyObject = .none) {
|
||||
self.init([model], delegate: delegate) { $0 }
|
||||
}
|
||||
|
||||
/// Convenience init for building a single cell, when input type is ListViewModelType.
|
||||
public convenience init(_ models: [T], delegate: WeakAnyObject = .none) {
|
||||
self.init(models, delegate: delegate) { $0 }
|
||||
}
|
||||
|
@ -103,6 +152,12 @@ public final class ListRenderSection<T: Identifiable>: Section {
|
|||
private let models: [T]
|
||||
private let transform: (T) -> Section?
|
||||
|
||||
/// Initialize from a generic array of models.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - models: Generic model array. T must conform to ``Identifiable``.
|
||||
/// - transform: A transform function that transforms from model type T to Section.
|
||||
/// may run on background thread if asynchronous diffing is enabled for ``ListSectionDataSource``.
|
||||
public init(_ models: [T], transform: @escaping (T) -> Section?) {
|
||||
self.models = models
|
||||
self.transform = transform
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import Foundation
|
||||
|
||||
/// Protocol to identify ViewModel.
|
||||
public protocol Identifiable {
|
||||
|
||||
var identifier: String { get }
|
||||
}
|
||||
|
||||
/// ViewModel protocol that defines interface for identity and equality check.
|
||||
///
|
||||
/// ``Identifiable`` protocol is used to uniquely identify ViewModels in the same ``ListSection``.
|
||||
/// Items in different sections are not required to have unique identifiers.
|
||||
///
|
||||
/// You don't need to implement its ``ListViewModel/isEqual(to:)`` function. View model should conform to Equatable protocol instead.
|
||||
/// The ``EquatableNoop`` annotation is also provided to ignore a certain property from equality check.
|
||||
///
|
||||
public protocol ListViewModel: Identifiable {
|
||||
|
||||
func isEqual(to: ListViewModel) -> Bool
|
||||
|
@ -18,6 +27,7 @@ extension ListViewModel where Self: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
/// A predefined concrete ViewModel struct to represent an empty ViewModel.
|
||||
public struct ListViewModelNone: ListViewModel, Equatable {
|
||||
|
||||
public var identifier: String {
|
||||
|
@ -27,6 +37,7 @@ public struct ListViewModelNone: ListViewModel, Equatable {
|
|||
public static let none = ListViewModelNone()
|
||||
}
|
||||
|
||||
/// Ignore a certain property from equality check.
|
||||
@propertyWrapper public struct EquatableNoop<T>: Equatable {
|
||||
|
||||
public var wrappedValue: T
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import Foundation
|
||||
|
||||
/// ViewState protocol.
|
||||
public protocol ListViewState {
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
/// A predefined concrete ViewState struct to represent an empty ViewState.
|
||||
public struct ListViewStateNone: ListViewState {
|
||||
|
||||
public init() {
|
||||
|
|
Loading…
Reference in New Issue