Как установить свойства и получить обратный вызов от UIAlertController в RxSwift? - PullRequest
2 голосов
/ 03 августа 2020

У меня есть 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 есть кнопка повторной попытки при нажатии кнопки повторной попытки Я хочу позвонить на syncData с Input из HomeViewModel Как мне это сделать?

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

Ответы [ 2 ]

1 голос
/ 03 августа 2020

Есть два случая, когда вы должны использовать Subjects: когда вы конвертируете не-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)
}
0 голосов
/ 03 августа 2020

Прежде всего, вы должны использовать некий Координатор для вызова контроллеров push / present. И создайте функцию, которая представляет Alert.

Например:

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.

PS: Вы должны использовать на входах Subjects в ViewModel, а не Drivers. Драйвер ДОЛЖЕН быть только для таких представлений, как вывод.

...