简体   繁体   中英

ReactiveCocoa 4, correctly send my HTTP request based on a UI event and a validation

I'm trying to understand some concepts of ReactiveCocoa 4 and does not find a way to correctly validate and send a request based on inputs from a login form.
My current solution sends requests on each valid update of my inputs, which is not good.

It seems that I need to use Actions and CocoaActions to fix my issue but I don't understand how to correctly implement them.

Here is my code example :
I want the login request to be sent when the Login Button is pressed and both the login and password fields are not empty, else I just display an Error.
Currently, the producer stays alive and continue to send requests when I modify the input fields, which is not good...

I would love an example on how to do it properly :)

LoginViewModel.swift

class LoginViewModel {
    let login = MutableProperty<String>("")
    let password = MutableProperty<String>("")

    init() {

    }

    func logIn() -> SignalProducer<Int, IntranetError> {
        return SignalProducer {
            observer, disposable in
            combineLatest(self.login.producer, self.password.producer)
            .promoteErrors(Moya.Error)
            .filter { (credentials : (String, String)) in

                guard credentials.0.length > 0 else {
                    observer.sendFailed(IntranetError.MissingLoginError)
                    return false
                }
                guard credentials.1.length > 0 else {
                    observer.sendFailed(IntranetError.MissingPasswordError)
                    return false
                }

                return true
            }
            .flatMap(.Latest) { (credentials : (String, String)) -> SignalProducer<User, Moya.Error> in
                let login = credentials.0
                let password = credentials.1
                return IntranetProvider.request(Intranet.LogIn(login, password)).filterSuccessfulStatusCodes()
                    .mapObject(User)
            }
            .start { (event) -> Void in
                switch event {
                case .Next(let user):
                    UserManager.sharedManager.user = user;
                    print(user)
                    observer.sendCompleted()
                case .Failed(let error):
                    observer.sendFailed(.MoyaError(error))
                default:
                    break
                }
            }
        }
    }
}

LoginViewController.swift

class LoginViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var loginTextField: FramedTextField!
    @IBOutlet weak var passwordTextField: FramedTextField!
    @IBOutlet weak var connectButton: UIButton!
    let viewModel : LoginViewModel = LoginViewModel()


    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.login <~ loginTextField.rac_text
        viewModel.password <~ passwordTextField.rac_text
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func connectButtonTouched(sender: AnyObject) {
        loginAsked()
    }

    func loginAsked() -> Void {
        SVProgressHUD.showWithMaskType(.Black)
        viewModel.logIn().throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler).start { (event) in
            switch event {
            case .Completed:
                SVProgressHUD.dismiss()
                self.connectionSuccessfull()
            case .Failed(let error):
                switch error {
                case .MissingPasswordError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing password", comment: "User password is missing"))
                case .MissingLoginError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing login", comment: "User login is missing"))
                case .MoyaError(let error) :
                    SVProgressHUD.showErrorWithStatus(error.toString())
                default :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Internal error, please try again later", comment: ""))
                }
            default:
                break
            }
        }
    }
}

Thanks

Finally after a long night working on it I found how to implement it properly.
I was overthinking a bit the validation process in the end, and it's much simpler now.
Result :

LoginViewModel.swift

class LoginViewModel {

    let login = MutableProperty<String>("")
    let password = MutableProperty<String>("")

    var loginAction : Action<(String, String), User, IntranetError>!
    var cocoaActionLogin : CocoaAction!

    init() {
        loginAction = Action { (let login, let password) in
            return SignalProducer {
                observer, disposable in
                guard login.length > 0 else {
                    observer.sendFailed(IntranetError.MissingLoginError)
                    return
                }
                guard password.length > 0 else {
                    observer.sendFailed(IntranetError.MissingPasswordError)
                    return
                }
                print("SENT")
                IntranetProvider.request(Intranet.LogIn(login, password))
                .filterSuccessfulStatusCodes()
                .mapObject(User)
                .start { (event) in
                    switch event {
                    case .Next(let user):
                        UserManager.sharedManager.user = user;
                        print(user)
                        observer.sendCompleted()
                    case .Failed(let error):
                        observer.sendFailed(.MoyaError(error))
                    default:
                        observer.sendCompleted()
                    }
                }
            }
        }
        cocoaActionLogin = CocoaAction(loginAction) { _ in
            return (self.login.value, self.password.value)
        }
    }
}

LoginViewController.swift

class LoginViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var loginTextField: FramedTextField!
    @IBOutlet weak var passwordTextField: FramedTextField!
    @IBOutlet weak var connectButton: UIButton!
    let viewModel : LoginViewModel = LoginViewModel()


    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.login <~ loginTextField.rac_text
        viewModel.password <~ passwordTextField.rac_text
        connectButton.addTarget(self.viewModel.cocoaActionLogin, action: CocoaAction.selector, forControlEvents: .TouchUpInside)
        self.viewModel.loginAction.events
            .observeOn(UIScheduler())
            .observeNext { (event) in
            switch event {
            case .Completed:
                SVProgressHUD.dismiss()
                self.connectionSuccessfull()
            case .Failed(let error):
                switch error {
                case .MissingPasswordError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing password", comment: "User password is missing"))
                case .MissingLoginError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing login", comment: "User login is missing"))
                case .MoyaError(let error) :
                    SVProgressHUD.showErrorWithStatus(error.toString())
                default :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Internal error, please try again later", comment: ""))
                }
            default:
                break
            }

        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM