gpt4 book ai didi

ios - 将 SwiftUI 按钮绑定(bind)到 AnySubscriber,例如 RxCocoa 的按钮点击

转载 作者:行者123 更新时间:2023-12-01 16:06:19 25 4
gpt4 key购买 nike

我使用以下 UIViewControllerRxSwift/RxCocoa基于一段代码来编写一个非常简单的 MVVM 模式来绑定(bind) UIButton点击事件触发一些 Observable工作并听取结果:

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)
}
}

它编译并运行良好。但是,我需要 CombinSwiftUI 确认此模式,所以我将该代码转换为以下内容:
import SwiftUI
import Combine

struct ContentView: View {
var viewModel: ViewModel
var subscriptions = Set<AnyCancellable>()

init(viewModel: ViewModel) {
self.viewModel = viewModel
setupBindings()
}

var body: some View {

Button(action: {
// <---- how to trigger viewModel's trigger from here
}, label: {
Text("Click Me")
})
}

private func setupBindings() {
self.viewModel.output.result.sink(receiveValue: { value in
print("value is \(value)")
})
.store(in: &subscriptions) // <--- doesn't compile due to immutability of ContentView
}
}

class ViewModel {

struct Input {
let trigger: AnySubscriber<Void, Never>
}

struct Output {
let result: AnyPublisher<String, Never>
}

let input: Input
let output: Output

private let triggerSubject = PassthroughSubject<Void, Never>()

init() {
self.input = Input(trigger: AnySubscriber(triggerSubject))

let resultPublisher = triggerSubject
.flatMap { Just("TEST") }
.eraseToAnyPublisher()

self.output = Output(result: resultPublisher)
}
}


由于两个错误(在代码中注释),此示例无法编译:

(一)第1题 : 如何像 RxSwift 的情况下从按钮的 Action 关闭中触发发布者的工作以上 ?

(2) 第2题以某种方式与架构设计有关,而不是编译错误:
错误说: ... Cannot pass immutable value as inout argument: 'self' is immutable ... , 那是因为 SwiftUI View 是结构,它们被设计为仅通过各种绑定(bind)( @State@ObservedObject 等...)进行更改,我有两个与问题 2 相关的子问题:

[A]: sink 被认为是一种不好的做法吗? SwiftUI 中的出版商看法 ?这可能需要一些解决方法来存储 cancellableView的结构范围?

[B]: SwiftUI/Combine 哪个更好MVVM 架构模式方面的项目:使用具有 [ Input[Subscribers]、Output[AnyPublishers] ] 模式的 ViewModel 或 ObservableObject带有 [ @Published 的 ViewModel特性] ?

最佳答案

我在理解最佳 mvvm 方法时遇到了同样的问题。
推荐也看看这个线程Best data-binding practice in Combine + SwiftUI?

将发布我的工作示例。应该很容易转换成你想要的。

SwiftUI View :

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
}
}
}

抽象 View 模型:
protocol ViewModelProtocol {
associatedtype Output
associatedtype Input

func bind(_ input: Input) -> Output
}

View 模型:
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)
}
}

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
}
}
}

关于ios - 将 SwiftUI 按钮绑定(bind)到 AnySubscriber,例如 RxCocoa 的按钮点击,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59848687/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com