Tips-1. 高雅的注册可复用的表格视图
张狂的热身运动
协议 WsReusable
包括一个只读的特点 identifier
,这个特点回来的是一个遵从该协议的类的类名的字符串儿,有点儿绕口,但不难了解
public protocol WsReusable: class {
static var identifier: String { get }
}
extension WsReusable {
public static var identifier: String {
// 这儿的 describing 能够变成 reflecting ,reflecting
// 愈加完整的表述了类名
return String(describing: Self.self)
}
}
只需让咱们自定义的 UICollectionViewCell
遵从该协议,他就免费获得了下面的注册办法:
extension Ws where Base: UICollectionView {
// 封装了一层 UICollectionViewCell的注册办法,自动把 cell 的 identifier
// 特点作为 ReuseIdentifier
public func register<T: UICollectionViewCell>(cell: T.Type) where T: WsReusable {
base.register(cell, forCellWithReuseIdentifier: T.identifier)
}
}
然后又增加了一个复用办法,复用的时分也不需求强转cell类型了
extension Ws where Base: UICollectionView {
.../
public func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T where T: WsReusable {
// 这儿把cell类型解包
guard let cell = base.dequeueReusableCell(withReuseIdentifier: T.identifier, for: indexPath) as? T else {
fatalError("Could not dequeue reusable cell with identifier: \(T.identifier)")
}
return cell
}
}
当然,获得这些办法的条件还是你的 UICollectionViewCell
遵从 WsReusable
协议哦~
直奔主题
// 第一步,恪守协议
class FamilyManagerCell: UICollectionViewCell, WsReusable { ... }
// 第二步,注册
collectionView.ws.register(cell: FamilyManagerCell.self)
// 第三步,复用
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 无需强转
let cell: FamilyManagerCell = collectionView.ws.dequeueReusableCell(for: indexPath)
return cell
}
高潮冲刺
不仅是 UICollectionViewCell
,UITableViewCell
也能够这么用
乃至继承自 UICollectionReusableView
类型的 UICollectionView
的 sectionHeader
和 sectionFooter
也能够恪守 WsReusable
协议,只不过注册时分的写法稍稍有所改变,篇幅约束,不再赘述。
贤者模式
粗犷的字符串赋值就像潘多拉魔盒,是bug之源,而swift的协议+泛型真的能够把一切粗犷的东西变的高雅和赏心悦目。
Tips-2 命名空间由 rx_ 到 rx.
你是否有给自己封装的办法或特点加过前缀呢?在 OC
时代引荐的是 前缀_
的办法,而在 Swift
时代,由于其语言的特性,面向协议编程,咱们触摸到了 .rx
.kf
这样比较高雅的办法,所以在万顺的 WSUIKit
组件库里就有了这个东西 WS.swift.
完成原理,代码如下
public struct Ws<Base> {
public let base: Base
init(_ base: Base) {
self.base = base
}
}
// 凡是恪守了 WsProtocol 协议的类型就都获取了 ws 特点
// ws 的类型是 Ws, Ws的相关类型(泛型)就是他自己
public protocol WsProtocol {}
extension WsProtocol {
public var ws: Ws<Self> {
return Ws(self)
}
public static var ws: Ws<Self>.Type {
return Ws.self
}
}
// 一切 NSObject 的子类都默认遵从了 WsProtocol
extension NSObject: WsProtocol {}
运用
extension Ws where Base: UICollectionView {
public func register<T: UICollectionViewCell>(cell: T.Type) where T: WsReusable {
base.register(cell, forCellWithReuseIdentifier: T.identifier)
}
}
collectionView.ws.register(cell: FamilyManagerCell.self)
高雅,永不过期
Tips-3 将 MJRefresh 用的更 RxSwift 一点
在主工程的 home
目录下有一个 MJRefresh+ws.swift
目的
- 监听
MJRefreshComponent
的改写状况,将这个状况封装成一个 ControlEvent,咱们只订阅“正在改写中(refreshing)”的状况。 - 将各种改写动作抽象成一个
MJRefreshAction
,将header和footer的各种相似beginRefreshing
这样的动作封装起来,便于解耦viewModel
和viewController
之间的粘连代码,防止在viewModel
中呈现相似header.beginRefreshing
的代码,将mvvm的架构变得愈加纯洁。 -
MJRefresh+ws
还贴心的为UIScrollView
增加了addHeader
办法,你能够直接调tableVie.ws.addHeader()
这样的代码,而不必去初始化一个
MJRefreshNormalHeader
或许WSRefreshGifHeader
。如果某天咱们的产品或许规划突然开窍了,觉得现在的下拉改写菊花作用太丑了,要规划一个带动效的样式,那咱们能够坚决果断的把代码改成这样tableView.ws.addHeader(animation: true)
或许其他端两天的工作量,咱们只需求两分钟!这属所以提早预判了产品需求~
MJRefresh+ws 正确运用五步走
1、增加 header/footer
tableView.ws.addHeader()
2、在 vc 中设置事情
if let header = tableView.mj_header {
header.rx.refresh.subscribe(onNext: { [weak self] in
guard let self = self else { return }
self.viewModel.requestFetchBalanceList(checkMore: false)
}).disposed(by: bag)
}
3、在 viewModel 中创建一个特点,一致处理Refresh的各种状况
let refreshAction = PublishRelay<MJRefreshAction>()
4、refreshAction 的状况变更,在恳求结束的回调里履行
self.refreshAction.accept(.stopRefresh)
5、在 vc 中把 refreshAction 绑定到 scrollView 上
viewModel.refreshAction
.asSignal()
.emit(to: tableView.rx.refreshAction)
.disposed(by: bag)
留意事项⚠️:
showNomoreData 状况设置后,一定要记住在适宜的机遇重置。设置为 resetNomoreData
参照
运用场景能够参照 `OtherPaidListController`
设置-亲情号办理-亲情号代付订单列表页
提示: 如果需求详细代码,
关注后私信
,看到会发你代码。
Tips-4 张狂打call的 ActivityIndicator(loading显示器)
怎么运用?
// viewModel 中的触发
class AddFamilyMemberViewModel {
var loadable: Driver<Bool>
init() {
// 1.初始化一个 ActivityIndicator
let activity = ActivityIndicator()
// 2.loadable 供外界订阅
loadable = activity.asDriver()
dataSource = service.getFamilyType()
// 3.在网络恳求的办法后调用trackActivity
.trackActivity(activity)
.map({ users in
...
})
.asDriver(onErrorJustReturn: [])
}
}
// viewController 中的订阅
class AddFamilyMemberController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 4.vc中订阅loadable,便是整个异步恳求函数的 loading 过程
viewModel.loadable
.drive(rx.isHUDLoadingDisplay)
.disposed(by: bag)
}
}
完成原理?
ActivityIndicator
使用 using 操作符,捕获了前一个 Observable
的生命周期(留意这个生命周期是一个信号开始被订阅到 onNext
或 onError
闭包被履行。这儿需求深入了解RxSwift 信号流通的全过程)。也就是说 activity
的相关类型是一个默认为 false
的 Bool
值,当时一个 Observable
开始发射信号的时分,activity
的相关值是 true
, 当时一个Observable
发射的信号被监听到今后,activity
的相关值变回了 false
。然后有用的监听到了一个网络恳求的全过程,loadable
顺其自然的绑定到了一个HUD上面。
番外废话:
ActivityIndicator
是 RxSwift
作者Krunoslav Zaher
给出来的处理方案,并不是我瞎编的~想学习源码的 github.com/ReactiveX/R…
Tips-5 站在阴暗里的英豪 ErrorIndicator
得益于对 ActivityIndicator
的充分了解,我觉得 ViewModel
中的网络恳求过错处理相同能够用这种思路处理。
先看用法
// viewModel
class AddFamilyMemberViewModel: AddFamilyMemberViewModelDataType {
var networkError: Driver<NetworkingError>
init () {
// 网络恳求中或许呈现的过错捕捉
let error = ErrorIndicator()
networkError = error.compactMap { ($0 as! NetworkingError) }
.asDriver()
dataSource = service.getFamilyType()
.trackError(error)
.map({ users in
...
})
.asDriver(onErrorJustReturn: [])
}
}
// viewController
class AddFamilyMemberController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 捕捉网络恳求的过错,提示用户
viewModel.networkError.drive(onNext: { error in
HUD.showMessage("\(error.description)")
}).disposed(by: bag)
}
}
这和ActivityIndicator
的用法基本没两样,乃至 ErrorIndicator
的完成代码愈加简略
public class ErrorIndicator: SharedSequenceConvertibleType {
public typealias Element = Swift.Error?
public typealias SharingStrategy = DriverSharingStrategy
private let _lock = NSRecursiveLock()
private let _relay = PublishRelay<Element>()
private let _error: SharedSequence<SharingStrategy, Element>
public init() {
_error = _relay.asDriver(onErrorJustReturn: nil)
}
fileprivate func trackErrorOfObservable<Source: ObservableConvertibleType>(
_ source: Source,
justReture element: Source.Element?) -> Observable<Source.Element> {
// 这儿只需求运用 catchError 这个操作符把 error 捕捉到,回传给
// ErrorIndicator 即可
return source.asObservable().catchError { error in
self._lock.lock()
self._relay.accept(error)
self._lock.unlock()
if let e = element {
return Observable<Source.Element>.just(e)
} else {
return Observable<Source.Element>.empty()
}
}
}
public func asSharedSequence() -> SharedSequence<DriverSharingStrategy, Element> {
return _error
}
}
extension ObservableConvertibleType {
public func trackError(_ errorIndicator: ErrorIndicator,
justReturn element: Element? = nil)
-> Observable<Element> {
return errorIndicator.trackErrorOfObservable(self, justReture: element)
}
}
这叫抄吗?这叫学以致用~
后续
之后我还会更新我在实践项目里是怎么规划 ViewModel 的心得,以及一些参考代码,关注我,一同学习~
Swift 实践小技巧继续更新中~