A state-driven view system, with service and notification system. A power tool to develop composite page and list.
pod 'DDPresenter'
Using custom collection view layout with subspecs. Now, support NECollectionViewLayout
, CHTCollectionViewWaterfallLayout
.
pod 'DDPresenter', :subspecs => ["Core", "NECollectionViewLayout", "CHTCollectionViewWaterfallLayout"]
You should put all states here to drive view updating.
- You can trigger updating by define a state type:
struct StateType {
// all your states
}
@StateChecker
private var _state: StateType = StateType()
@MainActor var state: StateType {
get {
_state
}
set {
// updating view only state changed
if _state != newValue {
setState {
_state = newValue
}
}
}
}
- Or just using
setState
:
@MainActor var text: String = smallText {
didSet {
setState {}
}
}
- Using a
ViewStatePresenter
is a better choice for complex states. And update view by:
func updateState() {
setState { state in
state.count += 1
}
}
+------------+
| Create |
+------------+
|
v
+------------+
+----| onAttach |
| +------------+
| |
| v
| +------------+
| | onBindView |
| +------------+
| |
| v
| +------------+ <---\
| | onUpdate | | setState
| +------------+ ----/
| |
| v
| +------------+
| |onUnbindView|
| +------------+
| |
| v
| +------------+
+--->| onDetach |
+------------+
|
v
+------------+
| Destroy |
+------------+
You can perform animations when updating view.
setState { $0.normalProgress = 1 - $0.normalProgress } context: { context in
context.animated = true
context.layoutIfNeeded = true
context.animator = UIViewDefaultAnimator(duration: 1)
}
An animator contains the animation params. You can custom animator to apply different animation params.
You can provide services in the view controller. Services can be used in the view controller and all presenter-tree in the view controller. When you getService
, make sure the presenter has attached to root.
func doSomething() {
getService(MyBusinessService.self).doSomething()
}
When needs add listeners, here is the best practice:
override func onAttachToRoot(_ presenter: RootViewPresentable) {
super.onAttachToRoot(presenter)
getService(MyService.self)?.addListener(self)
}
override func onDetachFromRoot(_ presenter: RootViewPresentable) {
super.onDetachFromRoot(presenter)
getService(MyService.self)?.removeListener(self)
}
Putting all business logics in a service is a best practice. Service is easy to reuse, and not acting with view updating.
Also, you can add your exists Service-System (IoC) by:
public struct GlobalServiceConfig {
/// If already have global service, `getService()` can use this to downgrade to global services.
public static var serviceDowngrade: ServiceProviderDowngrade? = nil
}
Notifying between presenters or services, may use delegate
, listener
, NSNotificationCenter
. But it will be very complex when having lots of messages. Here provide a new notification system. You can notify other presenters below the same root presenter who implementing the NotificationProtocol
.
Notify:
func notify() {
notifyGlobal(MyNotification.self) {
$0.onMyMessage()
}
}
And you can select notify scope as your needs.
public enum NotifyScope {
case global // Notify from root to all children presenters
case reusable // Notify to nearest reusable parent presenter and its children
case children // Notify to its chidren
case childrenAndSelf // Notify to its children and itself
case parents // Notify to its parents, until to root presenter
case manually // Nofity to listeners that added manually
}
UICollectionView
/ UITableView
delegate by Proxy
. It supports many features:
- data source diff
- size caching and size calculating automatically
- updating view only in the dirty part
Presenters for UICollectionView
with UICollectionViewFlowLayout
:
UICollectionViewFlowPresenter
will bindUICollectionView
UICollectionViewFlowSectionPresenter
is section data source typeUICollectionViewFlowItemPresenterHolder
is item data source typeUICollectionViewFlowReusableItemPresenter
will bindUICollectionViewCell
It will create more than one cell when animation or other situation. So seprate item to
holder
andreusable presenter
.holder
is unique, andreusable presenter
will bind cell that create by UICollectionView. At last, may have more than oneresusable presenter
in aholder
.
And the same as UITableView
:
UITableViewPresenter
will bindUICollectionView
UITableViewSectionPresenter
is section data source typeUITableViewCellPresenterHolder
is item data source typeUITableViewReusableCellPresenter
will bindUICollectionViewCell
When custom UICollectionViewLayout
, need different presenters listed below:
Layout | UICollectionViewFlowLayout | NECollectionViewLayout | CHTCollectionViewWaterfallLayout |
---|---|---|---|
CollectionView | UICollectionViewFlowPresenter | NECollectionViewFlowPresenter | CHTCollectionViewWaterfallPresenter |
Section | UICollectionViewFlowSectionPresenter | NECollectionViewFlowSectionPresenter | CHTCollectionViewWaterfallSectionPresenter |
Item Holder | UICollectionViewFlowItemPresenterHolder | UICollectionViewReusablePresenterHolder | UICollectionViewReusablePresenterHolder |
Item Reusable | UICollectionViewFlowReusableItemPresenter | UICollectionViewFlowReusableItemPresenter | UICollectionViewFlowReusableItemPresenter |