iOS使用Texture和Rxswitf進行MVVM架構的沸點頁面仿寫
老習慣了的先展示下結果吧:
# 技術路線
# 三方庫選擇:
Texture
Moya
RxSwift
Kingfisher
Lantern
: 照片瀏覽器Toast-Swift
: toast 提示- etc...
# 整體架構
使用 Texture
進行的介面元素搭建,RxSwift
實現的 VM
資料流驅動 View
更新,Moya + Rx
的網路請求。
# Texture
對於 Texture
的使用其實上手難度不如我預想的大,官方的大量例子,和一些優秀的技術部落格都能讓我們很好地接入 Texture
。
佈局:可以通過在網頁(ASDK🐸)上以玩樂的形式上手 FlexBox
佈局。
原理與使用:
這裡看一個佈局點贊使用者頭像列表的佈局吧:
```Swift
// Title private lazy var titleNode: ASTextNode = { let node = ASTextNode() let attr: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 12, weight: .regular), .foregroundColor: UIColor.XiTu.digCount ] node.attributedText = NSAttributedString(string: "等人贊過", attributes: attr) return node }()
// 佈局 override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let imageHeihgt = constrainedSize.min.height - 1
imageNode1.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
imageNode2.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
imageNode3.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
imageNode1.cornerRadius = imageHeihgt / 2
imageNode2.cornerRadius = imageHeihgt / 2
imageNode3.cornerRadius = imageHeihgt / 2
let overlayWidth: CGFloat = 5
let inset1 = ASInsetLayoutSpec(insets: .only(.right, value: 2 * (imageHeihgt - overlayWidth)), child: imageNode1)
let inset2 = ASInsetLayoutSpec(insets: .only(.horizontal, value: imageHeihgt - overlayWidth), child: imageNode2)
let inset3 = ASInsetLayoutSpec(insets: .only(.left, value:2 * (imageHeihgt - overlayWidth) ), child: imageNode3)
let overlay1 = ASOverlayLayoutSpec(child: inset1, overlay: inset2)
let overlay = ASOverlayLayoutSpec(child: overlay1, overlay: inset3)
let hStack = ASStackLayoutSpec.horizontal()
hStack.spacing = 5
hStack.alignItems = .center
hStack.children = [overlay, titleNode]
return ASInsetLayoutSpec(insets: .only(.left, value: 7), child: hStack)
}
```
# MVVM
與 RxSwift
Demo 中並沒有使用 雙向資料繫結,未對 RxSwift
進行運算子過載,也未使用上次的 RxTableViewDataSource
。整體程式碼可讀性應該會有所提高吧,適合 RxSwift
入門學習。
ViewModel: VM
層使用協議區分了 input
和 output
,介面隔離:
```Swift protocol DynamicListViewModelInputs {
func viewDidLoad()
func refreshDate()
func moreData(with cursor: String)
// FIXED: - 以下 view 層介面需要根據產品的邏輯而定. 例如: 是否要處理一部分埋點之類的額外操作
/// 檢視詳情 func showDetail()
/// 檢視點贊使用者 func diggUserClick() }
protocol DynamicListViewModelOutputs {
//var willRefreshData: Observable
var refreshData: Observable
var moreData: Observable
var endRefresh: Observable
var hasMoreData: Observable
var showError: Observable
protocol DynamicListViewModelType { var input: DynamicListViewModelInputs { get } var output: DynamicListViewModelOutputs { get } }
final class DynamicListViewModel: DynamicListViewModelType, DynamicListViewModelInputs, DynamicListViewModelOutputs {
var input: DynamicListViewModelInputs { self }
var output: DynamicListViewModelOutputs { self } // ect... } ```
View
(VC
) 中的使用:
```Swift extension DynamicListViewController { func eventListen() { self.mjHeader.refreshingBlock = { [unowned self] in self.viewModel.input.refreshDate() }
self.mjFooter.refreshingBlock = { [unowned self] in self.viewModel.input.moreData(with: self.dataSource.nextCursor) } } }
// MARK: - 繫結 viewModel
extension DynamicListViewController {
func bindViewModel() {
viewModel.output.refreshData.subscribe(onNext: { [weak self] wrappedModel in
self?.dataSource.newData(from: wrappedModel)
self?.tableNode.reloadData()
}).disposed(by: disposeBag)
viewModel.output.moreData.subscribe(onNext: { [weak self] wrappedModel in
if let insertIndexPath = self?.dataSource.moreData(from: wrappedModel), !insertIndexPath.isEmpty {
self?.tableNode.insertRows(at: insertIndexPath, with: UITableView.RowAnimation.automatic)
}
}).disposed(by: disposeBag)
// etc...
}
} ```
DataSource:對 ASTableNode
(UITableView
) 拆分出了 DataSource
來儲存和處理 Model
、遵守 ASTableDataSource
協議,將 VM
層的職責進一步的細分,便於維護。
這裡就說這麼多吧,更多的是在 cellNode
與 cellNodeModel
裡面的邏輯處理,和此類似。
備註:最近被大佬教育過不敢自稱是 MVVM
架構,但資料處理層仍使用了 XxxNodeMode
/ XxxViewModel
的名稱。
Model: 直接使用 struct
和 Codable
進行 JSON
解析。注意,如果使用 UITableView
實現類似的介面效果可能需要在 model
中補充額外的儲存屬性和 Decoder
邏輯程式碼。
# Moya
掘金裡相關技術文章也不少,這裡不做說明了。
# 相關知識點
# ASImageNode + Kingfrisher
對於 ASImageNode
使用第三方圖片庫其實並不困難,特別是對於面向協議封裝的 Kingfirsh
,這裡簡單的擴充套件一下吧:
```Swift import Kingfisher import AsyncDisplayKit
extension ASImageNode: KingfisherCompatible {}
public extension KingfisherWrapper where Base: ASImageNode {
func setImage(
with source: Resource?,
placeholder: UIImage? = nil,
failureImage: UIImage? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil)
{
guard let source = source else {
self.base.image = placeholder ?? failureImage
return
}
KingfisherManager.shared.retrieveImage(with: source, options: options, progressBlock: progressBlock, downloadTaskUpdated: nil) { result in
switch result {
case .success(let retrieveResult):
self.base.image = retrieveResult.image
case .failure(_):
self.base.image = failureImage ?? placeholder
}
}
}
} ```
備註:老久沒看 SDWebImage
的原始碼了,最近才發現 SDWebImage
內部也開始使用 協議 加 實現協議的預設類 重構了,是我太久沒關注的原因嗎😅?
# 其他
- 使
ASControl
支援RxSwift
- 正則(
regx
)表示式在Swift
中的使用 - 名稱空間的建立
- etc...
具體見原始碼吧。
# 關於原始碼
原始碼中已經將介面進行了移除,如果您有介面抓取能力(太簡單的操作了)請在
-
SceneDelegate.swift
中如下程式碼將rootVC
更換為HomeTabBarController
:```Swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let _ = (scene as? UIWindowScene) else { return }
guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow.init(frame: windowScene.coordinateSpace.bounds) window?.windowScene = windowScene //let rootVC = HomeTabBarController() //let rootVC = UINavigationController(rootViewController: SimpleRegxViewController()) let rootVC = UINavigationController(rootViewController: TextureDemoViewController()) window?.rootViewController = rootVC window?.makeKeyAndVisible() UIWindow.setupLayoutFitInfo()
}
`` 2.
XTNetworkService.swift中將
jjBaseUrl和
var path{xxx}` 替換到對應的介面。
如果僅是學習 Texture
佈局等,原始碼中也附帶了 xxx.json
檔案供您本地使用(直接執行就好了)。
# 絮叨
2020年掘金的iOS客戶端介面還在使用 Texture
,其中的沸點頁面令初見 Texture
的我大吃一驚,也是從此開始接觸並使用 Texture
,其和 CSS
相似的 FlexBox
佈局思想令人著迷。如今掘金的iOS版本經歷了大的重構,去除了很多第三方庫,這其中就包括 Texture
,常用App中使用 Texture
的廠家又少了一個😞。這裡也希望掘金iOS團隊能夠分享一下 Texture
使用過程中的利弊吧。