如何在 RxSwift 中设置属性并从 UIAlertController 获取回调?

How to set properties and get callback from UIAlertController in RxSwift?

我有一个 ViewModelType 来绑定 UIViewController 和 ViewModel。

import Foundation

protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}

HomeViewModel 符合 ViewModelType 并定义所需的输入和输出,然后根据输入执行返回输出的工作。

为简单起见,我删除了存储库并始终返回 syncData 任务的失败。

import Foundation
import RxSwift
import RxCocoa

class HomeViewModel: ViewModelType {

  struct Input {
    let syncData: Driver<Void>
  }
  
  struct Output {
    let message: Driver<String>
  }
  
  func transform(input: Input) -> Output {
    let fetching = input.syncData.flatMapLatest { _ -> Driver<String> in
      return Observable<String>.from(optional: "Choose below options to proceed") // This message will be returned by server.
        .delay(.seconds(1), scheduler: MainScheduler.instance)
        .asDriverOnErrorJustComplete()
    }
    return Output(message: fetching)
  }
}

我有一个带字符串的警报活页夹。

UIAlertController 有一个重试按钮,点击重试按钮我想从 HomeViewModelInput 调用 syncData 我该怎么做?

import UIKit
import RxSwift
import RxCocoa

class HomeViewController: UIViewController {

  private let disposeBag = DisposeBag()
  
  var viewModel = HomeViewModel()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let viewDidAppear = rx.sentMessage(#selector(UIViewController.viewDidAppear(_:)))
    .mapToVoid()
    .asDriverOnErrorJustComplete()
    
    // How to merge viewWillAppear & alert for callback of retry button?
    let input = HomeViewModel.Input(syncData: viewDidAppear)
    let output = viewModel.transform(input: input)
    
    output.message.drive(alert)
      .disposed(by: disposeBag)
  }

  var alert: Binder<String> {
    return Binder(self) { (vc, message) in
      let alert = UIAlertController(title: "Sync failed!",
                                    message: message,
                                    preferredStyle: .alert)
      let okay = UIAlertAction(title: "Retry", style: .default, handler: { _ in
        // how to call syncData of Input?
      })
      let dismiss = UIAlertAction(title: "Dismiss",
                                 style: UIAlertAction.Style.cancel,
                                 handler: nil)

      alert.addAction(okay)
      alert.addAction(dismiss)
      vc.present(alert, animated: true, completion: nil)
    }
  } 
}

首先,您应该使用一些 Coordinator 来调用 push/present 控制器。 并制作代表警报的功能。

例如:

class Router {
private let rootViewController: UIViewController
let retryAction = PublishSubject<Void>()

func showGalleryAlert() {
    let alert = UIAlertController(title: "Your Title", message: "Your Message", preferredStyle: .alert)

    let settings = UIAlertAction(title: "Name Action", style: .default) { _ in
        // send here your action to PublishSubject
        self.retryAction.send()
    }
    let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    alert.addAction(cancel)
    alert.addAction(settings)
    rootViewController.present(alert, animated: true)
}}

您需要将此路由器注入您的 ViewModel 并收听此 PublishSubject。

或者你可以使用Single/Maybe函数,这里是一个小例子如何使用它:

  public func openList() -> Maybe<Void> {          
        return .create { observer -> Disposable in
              let alert = UIAlertController(title: "Your Title", message: "YourMessage", preferredStyle: .alert)

      let settings = UIAlertAction(title: "Name Action", style: .default) { _ in
        // send here your action
        observer.send() 
     }
      let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
      alert.addAction(cancel)
      alert.addAction(settings)
      rootViewController.present(alert, animated: true)
            return Disposables.create {
                DispatchQueue.main.async {
                    vc.dismiss(animated: true, completion: nil)
                }
            }
        }
    }

并通过 ViewModel 处理。

P.S: 您应该在 ViewModel 中使用输入主题,而不是驱动程序。驱动程序 必须 仅用于输出等视图。

有两种情况你应该使用 Subjects,当你将 non-RxCode 转换成 RxCode 时(比如使 UIAlertAction 响应式),当你必须创建一个循环时(比如提供视图的输出模型返回到它自己的输入。)

如果您发现自己经常这样做,那么您可能要考虑制作多个视图模型。

您还会注意到,我创建了警报它自己的、独立的协调器函数。这样它就可以在多个地方使用。如果需要,您可以对传递给 flatMapFirst 的闭包执行相同的操作。

class HomeViewModel {

    struct Input {
        let syncData: Observable<Void>
        let retry: Observable<Void>
    }

    struct Output {
        let message: Observable<String>
    }

    func transform(input: Input) -> Output {
        let fetching = Observable.merge(input.syncData, input.retry)
            .flatMapLatest { _ -> Observable<String> in
                return Observable<String>.from(optional: "Choose below options to proceed") // This message will be returned by server.
                    .delay(.seconds(1), scheduler: MainScheduler.instance)
        }
        return Output(message: fetching)
    }
}

class HomeViewController: UIViewController {

    private let disposeBag = DisposeBag()

    var viewModel = HomeViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        let viewDidAppear = rx.sentMessage(#selector(UIViewController.viewDidAppear(_:)))
            .mapToVoid()

        let retry = PublishSubject<Void>()

        let input = HomeViewModel.Input(syncData: viewDidAppear, retry: retry.asObservable())
        let output = viewModel.transform(input: input)

        output.message
            .flatMapFirst { [weak self] (message) -> Observable<Void> in
                let (alert, trigger) = createAlert(message: message)
                self?.present(alert, animated: true)
                return trigger
            }
            .subscribe(retry)
            .disposed(by: disposeBag)
    }
}

func createAlert(message: String) -> (UIViewController, Observable<Void>) {
    let trigger = PublishSubject<Void>()
    let alert = UIAlertController(title: "Sync failed!",
                                  message: message,
                                  preferredStyle: .alert)
    let okay = UIAlertAction(title: "Retry", style: .default, handler: { _ in
        trigger.onNext(())
        trigger.onCompleted()
    })
    let dismiss = UIAlertAction(title: "Dismiss",
                                style: UIAlertAction.Style.cancel,
                                handler: nil)

    alert.addAction(okay)
    alert.addAction(dismiss)
    return (alert, trigger)
}