Ios 将SwiftUI按钮绑定到任何订户,如RxCocoa';s按钮点击
我使用以下基于Ios 将SwiftUI按钮绑定到任何订户,如RxCocoa';s按钮点击,ios,swiftui,rx-swift,combine,rx-cocoa,Ios,Swiftui,Rx Swift,Combine,Rx Cocoa,我使用以下基于UIViewController和RxSwift/RxCocoa的代码段编写一个非常简单的MVVM模式,绑定UIButton点击事件,触发一些可观察的工作并监听结果: import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var someButton: UIButton! var viewModel: ViewMode
UIViewController
和RxSwift/RxCocoa
的代码段编写一个非常简单的MVVM模式,绑定UIButton
点击事件,触发一些可观察的
工作并监听结果:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var someButton: UIButton!
var viewModel: ViewModel!
private var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel = ViewModel()
setupBindings()
}
private func setupBindings() {
someButton.rx.tap
.bind(to: self.viewModel.input.trigger)
.disposed(by: disposeBag)
viewModel.output.result
.subscribe(onNext: { element in
print("element is \(element)")
}).disposed(by: disposeBag)
}
}
class ViewModel {
struct Input {
let trigger: AnyObserver<Void>
}
struct Output {
let result: Observable<String>
}
let input: Input
let output: Output
private let triggerSubject = PublishSubject<Void>()
init() {
self.input = Input(trigger: triggerSubject.asObserver())
let resultObservable = triggerSubject.flatMap { Observable.just("TEST") }
self.output = Output(result: resultObservable)
}
}
由于两个错误(在代码中注释),此示例无法编译:
(1)问题1:如何像上面的RxSwift
那样从按钮的动作关闭触发出版商的作品
(2)问题2与建筑设计有关,而不是编译错误:
错误显示:。。。无法传递不可变值,因为inout参数:“self”是不可变的…
,这是因为SwiftUI
视图是结构,它们被设计为只能通过各种绑定进行更改(@State
,@ObservedObject
等…),我有两个子问题与问题2相关:
[A] :在SwiftUI
视图中接收发布者是否被认为是一种不良做法?在视图
的结构范围中存储可取消的
可能需要一些变通方法
[B] :在MVVM体系结构模式方面,对于SwiftUI/Combine
项目,哪一个更好:使用带有[Input[Subscribers]、Output[AnyPublishers]]模式的ViewModel,还是使用
observeObject
ViewModel与[@Published
属性]?我在理解最佳mvvm方法时遇到了同样的问题。
建议您也查看此线程
将发布我的工作示例。应该很容易转换成你想要的
快捷视图:
struct ContentView: View {
@State private var dataPublisher: String = "ggg"
@State private var sliderValue: String = "0"
@State private var buttonOutput: String = "Empty"
let viewModel: SwiftUIViewModel
let output: SwiftUIViewModel.Output
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
self.output = viewModel.bind(())
}
var body: some View {
VStack {
Text(self.dataPublisher)
Text(self.sliderValue)
Slider(value: viewModel.$sliderBinding, in: 0...100, step: 1)
Button(action: {
self.viewModel.buttonBinding = ()
}, label: {
Text("Click Me")
})
Text(self.buttonOutput)
}
.onReceive(output.dataPublisher) { value in
self.dataPublisher = value
}
.onReceive(output.slider) { (value) in
self.sliderValue = "\(value)"
}
.onReceive(output.resultPublisher) { (value) in
self.buttonOutput = value
}
}
}
抽象视图模型:
protocol ViewModelProtocol {
associatedtype Output
associatedtype Input
func bind(_ input: Input) -> Output
}
final class SwiftUIViewModel: ViewModelProtocol {
struct Output {
let dataPublisher: AnyPublisher<String, Never>
let slider: AnyPublisher<Double, Never>
let resultPublisher: AnyPublisher<String, Never>
}
typealias Input = Void
@SubjectBinding var sliderBinding: Double = 0.0
@SubjectBinding var buttonBinding: Void = ()
func bind(_ input: Void) -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.delay(for: 5.0, scheduler: DispatchQueue.main)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.share()
.eraseToAnyPublisher()
let resultPublisher = _buttonBinding.anyPublisher()
.dropFirst()
.flatMap { Just("TEST") }
.share()
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher,
slider: _sliderBinding.anyPublisher(),
resultPublisher: resultPublisher)
}
}
视图模型:
protocol ViewModelProtocol {
associatedtype Output
associatedtype Input
func bind(_ input: Input) -> Output
}
final class SwiftUIViewModel: ViewModelProtocol {
struct Output {
let dataPublisher: AnyPublisher<String, Never>
let slider: AnyPublisher<Double, Never>
let resultPublisher: AnyPublisher<String, Never>
}
typealias Input = Void
@SubjectBinding var sliderBinding: Double = 0.0
@SubjectBinding var buttonBinding: Void = ()
func bind(_ input: Void) -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.delay(for: 5.0, scheduler: DispatchQueue.main)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.share()
.eraseToAnyPublisher()
let resultPublisher = _buttonBinding.anyPublisher()
.dropFirst()
.flatMap { Just("TEST") }
.share()
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher,
slider: _sliderBinding.anyPublisher(),
resultPublisher: resultPublisher)
}
}
最终类SwiftUIViewModel:ViewModelProtocol{
结构输出{
让dataPublisher:AnyPublisher
让滑块:AnyPublisher
让结果发布者:AnyPublisher
}
typealias输入=无效
@SubjectBinding变量sliderBinding:Double=0.0
@SubjectBinding变量buttonBinding:Void=()
func bind(u输入:Void)->输出{
让dataPublisher=URLSession.shared.dataTaskPublisher(用于:URL(字符串):https://www.google.it")!)
.delay(对于:5.0,调度程序:DispatchQueue.main)
.map{“仅用于测试-\($0)”}
.replaceError(带有“发生错误”)
.receive(在:DispatchQueue.main上)
.share()
.删除任何发布者()
让resultPublisher=\u buttonBinding.anyPublisher()
.dropFirst()
.flatMap{Just(“TEST”)}
.share()
.删除任何发布者()
返回输出(dataPublisher:dataPublisher,
滑块:\ u sliderBinding.anyPublisher(),
结果发布者:结果发布者)
}
}
SubjectBinding属性包装器:
@propertyWrapper
struct SubjectBinding<Value> {
private let subject: CurrentValueSubject<Value, Never>
init(wrappedValue: Value) {
subject = CurrentValueSubject<Value, Never>(wrappedValue)
}
func anyPublisher() -> AnyPublisher<Value, Never> {
return subject.eraseToAnyPublisher()
}
var wrappedValue: Value {
get {
return subject.value
}
set {
subject.value = newValue
}
}
var projectedValue: Binding<Value> {
return Binding<Value>(get: { () -> Value in
return self.subject.value
}) { (value) in
self.subject.value = value
}
}
}
@propertyWrapper
结构主题绑定{
私人租赁主体:CurrentValueSubject
init(wrappedValue:Value){
主题=当前值主题(wrappedValue)
}
func anyPublisher()->anyPublisher{
返回subject.eraseToAnyPublisher()
}
var wrappedValue:Value{
得到{
返回subject.value
}
设置{
subject.value=newValue
}
}
var projectedValue:绑定{
返回绑定(在中获取:{()->值
返回自我主体价值
}){(值)在
self.subject.value=值
}
}
}
所以我最近也在想,既然我们没有开始在SwiftUI中编写视图,我该怎么做
我制作了一个helper对象来封装从函数调用到发布服务器的转换。我称之为接力赛
@available(iOS 13.0, *)
struct Relay<Element> {
var call: (Element) -> Void { didCall.send }
var publisher: AnyPublisher<Element, Never> {
didCall.eraseToAnyPublisher()
}
// MARK: Private
private let didCall = PassthroughSubject<Element, Never>()
}
然后你可以做任何你喜欢的事情
relay.publisher
如果有人开始回答,他们会重复官方文件,所以只需从头开始。简而言之,使用
observateObject
-它是本机的、简单的,并且自动与SwiftUI集成。检查一些MVVM Xcode模板这很好!通过在视图中公开didTapLogin:Relay,帮助我将UViewController(UIHostingController的子类)绑定到SwiftUI视图的按钮点击