Artisan
Artisan 是一个使用 Pharos 的绑定特性、Draftsman 的约束构建器和 Builder 的构建模式构建的 Swift MVVM 框架。
示例
要运行示例项目,请克隆仓库,然后先从 Example 目录中运行 pod install
需求
- Swift 5.3 或更高版本
- iOS 10.0 或更高版本
- Xcode 12.5 或更高版本
安装
CocoaPods
Artisan 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile 中
pod 'Artisan', '~> 5.1.0'
从 XCode 使用 Swift Package Manager
- 在 版本 标签下设置规则,选择 升级到下一个主要版本 选项,并将其版本设置为 5.1.0
- 使用 XCode 菜单 文件 > Swift 包 > 添加包依赖项 来添加它
- 将 https://github.com/hainayanda/Artisan.git 作为 Swift 包 URL 添加
- 点击“下一步”并等待
从 Package.swift 使用 Swift Package Manager
在 Package.swift 中将其作为目标依赖项添加
dependencies: [
.package(url: "https://github.com/hainayanda/Artisan.git", .upToNextMajor(from: "5.1.0"))
]
在目标中使用它作为 Artisan
.target(
name: "MyModule",
dependencies: ["Artisan"]
)
作者
Nayanda Haberty,[email protected]
许可
Artisan 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。
用法
在 wiki 中阅读以获取更多详细信息。
基本用法
使用Artisan创建MVVM模式非常简单。绑定支持由Pharos提供,视图构建支持由Draftsman提供,Artisan是使两者能够完美协同工作的工具。例如,如果您想创建一个简单的搜索屏幕
import UIKit
import Artisan
import Draftsman
import Builder
import Pharos
class SearchScreen: UIPlannedController, ViewBindable {
typealias Model = SearchScreenViewModel
@Subject var allResults: [Result] = []
// MARK: View
lazy var searchBar: UISearchBar = builder(UISearchBar.self)
.placeholder("Search here!")
.sizeToFit()
.tintColor(.text)
.barTintColor(.background)
.delegate(self)
.build()
lazy var tableView: UITableView = builder(UITableView.self)
.backgroundColor(.clear)
.separatorStyle(.none)
.allowsSelection(true)
.delegate(self)
.build()
@LayoutPlan
var viewPlan: ViewPlan {
tableView.drf
.edges.equal(with: .parent)
.cells(from: $allResults) { _, result in
Cell(from: ResultCell.self) { cell, _ in
cell.apply(result)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .background
tableView.keyboardDismissMode = .onDrag
applyPlan()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.tintColor = .main
navigationItem.titleView = searchBar
}
// MARK: This is where View Model bind with View
@BindBuilder
func autoBinding(with model: Model) -> BindRetainables {
model.searchPhraseBindable
.bind(with: searchBar.bindables.text)
}
@BindBuilder
func autoFireBinding(with model: Model) -> BindRetainables {
model.resultsObservable
.relayChanges(to: $allResults)
}
// more code for UITableViewDelegate and UISearchbarDelegate below
...
...
...
}
拥有如下ViewModel协议
protocol SearchScreenDataBinding {
var searchPhraseBindable: BindableObservable<String?> { get }
var resultsObservable: Observable<[Result]> { get }
}
protocol SearchScreenSubscriber {
func didTap(_ event: Result, at indexPath: IndexPath)
}
typealias SearchScreenViewModel = ViewModel & SearchScreenSubscriber & SearchScreenDataBinding
它将使用Draftsman
创建一个视图,并使用Pharos
将Model
绑定到View
。如您从上述代码中所见,它将searchBar.bindables.text
绑定到从Model
中的searchPhraseBindable
,并将变更从resultsObservable
转发到allResults
。这将确保来自searchBar
的每个变更都被转发到Model
,然后Model
中的每个结果变更都将转发回View
。<结果接着将通过由Artisan提供并由DiffableDataSource支持的内置数据源(由Artisan提供)来观察,然后用于更新UITableView
中的单元格。
您可以这样创建您的ViewModel
import UIKit
import Artisan
import Pharos
import Impose
// MARK: ViewModel
class SearchScreenVM: SearchScreenViewModel, ObjectRetainer {
@Injected var service: EventService
let router: SearchRouting
@Subject var searchPhrase: String?
@Subject var results: [Result] = []
// MARK: Data Binding
var searchPhraseBindable: BindableObservable<String?> { $searchPhrase }
var resultsObservable: Observable<[Result]> { $results }
init(router: SearchRouting) {
self.router = router
$searchPhrase
.whenDidSet(thenDo: method(of: self, SearchScreenVM.search(for:)))
.multipleSetDelayed(by: 1)
.retained(by: self)
.fire()
}
}
// MARK: Subscriber
extension EventSearchScreenVM {
func didTap(_ history: HistoryResult, at indexPath: IndexPath) {
searchPhrase = history.distinctifier as? String
}
func didTap(_ event: EventResult, at indexPath: IndexPath) {
guard let tappedEvent = event.distinctifier as? Event else { return }
router.routeToDetails(of: tappedEvent)
}
}
// MARK: Extensions
extension EventSearchScreenVM {
func search(for changes: Changes<String?>) {
service.doSearch(withSearchPhrase: changes.new ?? "") { [weak self] results in
self?.results = results
}
}
}
然后绑定View
和Model
将变得像这样简单
let searchScreen = SearchScreen()
let searchRouter = SearchRouter(screen: searchScreen)
let searchScreenVM = SearchScreenVM(router: searchRouter)
searchScreen.bind(with: searchScreenVM)
别忘了,每次BindableView
与新的ViewModel
绑定时,都将释放其所有保留的旧Pharos转发。
您可以克隆并查看示例文件夹,或获取更多Wiki信息,请访问这里
贡献
你知道怎么做,只需克隆并提Pull Request