RxSwiftでは、_View Model
_のDriver
またはObservable
をViewController
(つまり、UILabel
)のオブザーバーにバインドするのは非常に簡単です。
私は通常、「PublishSubject
」を介して「強制的に」値をプッシュするのではなく、オブザーバブル他のオブザーバブルから作成を使用してパイプラインを構築することを好みます。
この例を使用してみましょう:ネットワークからデータをフェッチした後、UILabel
を更新します
_final class RxViewModel {
private var dataObservable: Observable<Data>
let stringDriver: Driver<String>
init() {
let request = URLRequest(url: URL(string:"https://www.google.com")!)
self.dataObservable = URLSession.shared
.rx.data(request: request).asObservable()
self.stringDriver = dataObservable
.asDriver(onErrorJustReturn: Data())
.map { _ in return "Network data received!" }
}
}
_
_final class RxViewController: UIViewController {
private let disposeBag = DisposeBag()
let rxViewModel = RxViewModel()
@IBOutlet weak var rxLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
rxViewModel.stringDriver.drive(rxLabel.rx.text).disposed(by: disposeBag)
}
}
_
UIKitベースのプロジェクトでは、同じパターンを維持できるようです。
_final class CombineViewModel: ObservableObject {
private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
var stringPublisher: AnyPublisher<String, Never>
init() {
self.dataPublisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.eraseToAnyPublisher()
self.stringPublisher = dataPublisher
.map { (_, _) in return "Network data received!" }
.replaceError(with: "Oh no, error!")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
_
_final class CombineViewController: UIViewController {
private var cancellableBag = Set<AnyCancellable>()
let combineViewModel = CombineViewModel()
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
combineViewModel.stringPublisher
.flatMap { Just($0) }
.assign(to: \.text, on: self.label)
.store(in: &cancellableBag)
}
}
_
SwiftUIは、_@Published
_などのプロパティラッパーとObservableObject
、ObservedObject
などのプロトコルを使用して、バインディングを自動的に処理します(Xcode 11b7以降)。
(AFAIK)プロパティラッパーは「オンザフライで作成」できないため、同じパターンを使用して上記の例を再作成する方法はありません。次のコンパイルしません
_final class WrongViewModel: ObservableObject {
private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
@Published var stringValue: String
init() {
self.dataPublisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.eraseToAnyPublisher()
self.stringValue = dataPublisher.map { ... }. ??? <--- WRONG!
}
}
_
私が思いつく可能性がある最も近いのはビューモデルでサブスクライブする(UGH!)とプロパティを強制的に更新するです。
_final class SwiftUIViewModel: ObservableObject {
private var cancellableBag = Set<AnyCancellable>()
private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
@Published var stringValue: String = ""
init() {
self.dataPublisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.eraseToAnyPublisher()
dataPublisher
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in }) { (_, _) in
self.stringValue = "Network data received!"
}.store(in: &cancellableBag)
}
}
_
_struct ContentView: View {
@ObservedObject var viewModel = SwiftUIViewModel()
var body: some View {
Text(viewModel.stringValue)
}
}
_
この新しいIViewController-lessの世界では、「バインディングを行うための古い方法」は忘れられて置き換えられますか?
以前の回答を投稿した後、この記事を読んでください: https://nalexn.github.io/swiftui-observableobject/
同じようにすることにします。 @Stateを使用し、@ Publishedは使用しない
一般的なViewModelプロトコル:
protocol ViewModelProtocol {
associatedtype Output
associatedtype Input
func bind(_ input: Input) -> Output
}
ViewModelクラス:
final class SwiftUIViewModel: ViewModelProtocol {
struct Output {
var dataPublisher: AnyPublisher<String, Never>
}
typealias Input = Void
func bind(_ input: Void) -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher)
}
}
SwiftUIビュー:
struct ContentView: View {
@State private var dataPublisher: String = "ggg"
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)
}
.onReceive(output.dataPublisher) { value in
self.dataPublisher = value
}
}
}
私は多少の妥協に終わった。 viewModelで@Published
を使用していますが、SwiftUI Viewでサブスクライブしています。このようなもの:
final class SwiftUIViewModel: ObservableObject {
struct Output {
var dataPublisher: AnyPublisher<String, Never>
}
@Published var dataPublisher : String = "ggg"
func bind() -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher)
}
}
およびSwiftUI:
struct ContentView: View {
private var cancellableBag = Set<AnyCancellable>()
@ObservedObject var viewModel: SwiftUIViewModel
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
let bindStruct = viewModel.bind()
bindStruct.dataPublisher
.assign(to: \.dataPublisher, on: viewModel)
.store(in: &cancellableBag)
}
var body: some View {
VStack {
Text(self.viewModel.dataPublisher)
}
}
}