Artisan 是一个 Swift 的 MVVM 框架,它使用了来自 Pharos 的绑定特性,来自 Draftsman 的约束构建器,以及来自 Builder 的构建器模式。
要运行示例项目,请克隆仓库,并首先从 Example 目录运行 pod install。
Artisan 可以通过 CocoaPods 获得。 要安装它,只需将以下行添加到您的 Podfile 中
pod 'Artisan', '~> 5.1.0'
在 Package.swift 中添加它作为您的 target 依赖
dependencies: [
.package(url: "https://github.com/hainayanda/Artisan.git", .upToNextMajor(from: "5.1.0"))
]
在您的 target 中使用它,名称为 Artisan
.target(
name: "MyModule",
dependencies: ["Artisan"]
)
Nayanda Haberty, hainayanda@outlook.com
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 创建一个 View,并使用 Pharos 将 Model 绑定到 View。 正如您从上面的代码中看到的那样,它会将 searchBar.bindables.text 与 Model 中的 searchPhraseBindable 绑定,并将 resultsObservable 中的更改转发到 allResults。 这将确保来自 searchBar 的每个更改都将转发到 Model,然后来自 Model 的每个结果更改都将转发回 View。 然后,这些结果将被 UITableView 内置数据源(由 Artisan 提供并由 DiffableDataSource 提供支持)观察,用于更新 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 relay 都将被释放。
您可以克隆并查看 Example folder,或者要获得更多 wiki,请访问 这里
你知道怎么做,只需克隆并提交 pull request