Artisan 5.1.0

Artisan 5.1.0

nayanda1 维护。



 
依赖
Draftsman~> 3.0.6
Pharos~> 2.3.5
DiffableDataSources~> 0.5.0
 

Artisan 5.1.0

  • 作者
  • nayanda

Artisan

Artisan 是一个使用 Pharos 的绑定特性、Draftsman 的约束构建器和 Builder 的构建模式构建的 Swift MVVM 框架。

codebeat badge build test SwiftPM Compatible Version License Platform

示例

要运行示例项目,请克隆仓库,然后先从 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创建一个视图,并使用PharosModel绑定到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
        }
    }
}

然后绑定ViewModel将变得像这样简单

let searchScreen = SearchScreen()
let searchRouter = SearchRouter(screen: searchScreen)
let searchScreenVM = SearchScreenVM(router: searchRouter)
searchScreen.bind(with: searchScreenVM)

别忘了,每次BindableView与新的ViewModel绑定时,都将释放其所有保留的旧Pharos转发。

您可以克隆并查看示例文件夹,或获取更多Wiki信息,请访问这里

贡献

你知道怎么做,只需克隆并提Pull Request