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