Быстрое Объединение: последующий Издатель, который потребляет других Издателей (используя CombineLatest), не "срабатывает" - PullRequest
0 голосов
/ 06 июля 2019

Я пытаюсь воспроизвести пример «Регистрация в школе волшебников», который был дан в сеансе WWDC 2019 «Объединение на практике» https://developer.apple.com/videos/play/wwdc2019/721/, начиная с 22:50, используя SwiftUI (в отличие от UIKit, который былиспользуется во время сеанса).

Я создал всех издателей из примера: validatedEMail, validatedPassword и validatedCredentials.В то время как validatedEMail и validatedPassword работают просто отлично, validatedCredentials, который использует оба издателя с помощью CombineLatest, никогда не срабатывает

//
//  RegistrationView.swift
//
//  Created by Lars Sonchocky-Helldorf on 04.07.19.
//  Copyright © 2019 Lars Sonchocky-Helldorf. All rights reserved.
//

import SwiftUI
import Combine

struct RegistrationView : View {
    @ObjectBinding var registrationModel = RegistrationModel()

    @State private var showAlert = false
    @State private var alertTitle: String = ""
    @State private var alertMessage: String = ""

    @State private var registrationButtonDisabled = true

    @State private var validatedEMail: String = ""
    @State private var validatedPassword: String = ""

    var body: some View {
        Form {
            Section {
                TextField("Enter your EMail", text: $registrationModel.eMail)
                SecureField("Enter a Password", text: $registrationModel.password)
                SecureField("Enter the Password again", text: $registrationModel.passwordRepeat)
                Button(action: registrationButtonAction) {
                    Text("Create Account")
                }
                .disabled($registrationButtonDisabled.value)
                    .presentation($showAlert) {
                        Alert(title: Text("\(alertTitle)"), message: Text("\(alertMessage)"))
                }
                .onReceive(self.registrationModel.validatedCredentials) { newValidatedCredentials in
                    self.registrationButtonDisabled = (newValidatedCredentials == nil)
                }
            }

            Section {
                Text("Validated EMail: \(validatedEMail)")
                    .onReceive(self.registrationModel.validatedEMail) { newValidatedEMail in
                        self.validatedEMail = newValidatedEMail != nil ? newValidatedEMail! : "EMail invalid"
                }
                Text("Validated Password: \(validatedPassword)")
                    .onReceive(self.registrationModel.validatedPassword) { newValidatedPassword in
                        self.validatedPassword = newValidatedPassword != nil ? newValidatedPassword! : "Passwords to short or don't matchst"
                }
            }
        }
        .navigationBarTitle(Text("Sign Up"))
    }

    func registrationButtonAction() {
        let trimmedEMail: String = self.registrationModel.eMail.trimmingCharacters(in: .whitespaces)

        if (trimmedEMail != "" && self.registrationModel.password != "") {
            NetworkManager.sharedInstance.registerUser(NetworkManager.RegisterRequest(uid: trimmedEMail, password: self.registrationModel.password)) { (status) in
                if status == 200 {
                    self.showAlert = true
                    self.alertTitle = NSLocalizedString("Registration successful", comment: "")
                    self.alertMessage = NSLocalizedString("please verify your email and login", comment: "")
                } else if status == 400 {
                    self.showAlert = true
                    self.alertTitle = NSLocalizedString("Registration Error", comment: "")
                    self.alertMessage = NSLocalizedString("already registered", comment: "")
                } else {
                    self.showAlert = true
                    self.alertTitle = NSLocalizedString("Registration Error", comment: "")
                    self.alertMessage = NSLocalizedString("network or app error", comment: "")
                }
            }
        } else {
            self.showAlert = true
            self.alertTitle = NSLocalizedString("Registration Error", comment: "")
            self.alertMessage = NSLocalizedString("username / password empty", comment: "")
        }
    }
}

class RegistrationModel : BindableObject {
    @Published var eMail: String = ""
    @Published var password: String = ""
    @Published var passwordRepeat: String = ""

    public var didChange = PassthroughSubject<Void, Never>()

    var validatedEMail: AnyPublisher<String?, Never> {
        return $eMail
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .removeDuplicates()
            .flatMap { username in
                return Future { promise in
                    self.usernameAvailable(username) { available in
                        promise(.success(available ? username : nil))
                    }
                }
        }
        .eraseToAnyPublisher()
    }

    var validatedPassword: AnyPublisher<String?, Never> {
        return Publishers.CombineLatest($password, $passwordRepeat)
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .map { password, passwordRepeat in
                guard password == passwordRepeat, password.count > 5 else { return nil }
                return password
        }
        .eraseToAnyPublisher()
    }

    var validatedCredentials: AnyPublisher<(String, String)?, Never> {
        return Publishers.CombineLatest(validatedEMail, validatedPassword)
            .map { validatedEMail, validatedPassword in
                guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
                return (eMail, password)
        }
        .eraseToAnyPublisher()
    }


    func usernameAvailable(_ username: String, completion: (Bool) -> Void) {
        let isValidEMailAddress: Bool = NSPredicate(format:"SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}").evaluate(with: username)

        completion(isValidEMailAddress)
    }
}

#if DEBUG
struct RegistrationView_Previews : PreviewProvider {
    static var previews: some View {
        RegistrationView()
    }
}
#endif

Я ожидал, что кнопка формы включится, когда допустимое имя пользователя (действительный адрес электронной почты) и двасоответствующие пароли с правильной длиной предоставляются.Два издателя, отвечающие за эти две задачи, работают, я вижу validatedEMail и validatedPassword в пользовательском интерфейсе в двух текстах, которые я добавил для целей отладки.

Просто третий издатель (также сравните с приведенным кодомв видео сверху в 32:20) никогда не срабатывает.Я установил точки останова в этих издателях в validatedPassword Publisher в строке:

guard password == passwordRepeat, password.count > 5 else { return nil }

, которая остановилась там просто отлично, но аналогичная точка останова в validatedCredentials Publisher в строке:

guard let eMail = validatedEMail, let password = validatedPassword else { return nil }

быланикогда не достиг.

Что я сделал не так?

Редактировать:

Для того, чтобы вышеуказанный код работал под Xcode-beta 11.0 beta 4didChange необходимо заменить на willChange

Ответы [ 2 ]

0 голосов
/ 12 июля 2019

Просто замените

.debounce(for: 0.5, scheduler: RunLoop.main)

с

.throttle(for: 0.5, scheduler: RunLoop.main, latest: true)

Поскольку в подписке издателей нет дорогостоящего кода, отсроченная обработка в основном не потребуется. Регулирование ключевых событий с помощью latest: true будет выполнять работу почти таким же образом.

Я не такой специалист по реактивному программированию, что могу судить, в чем причина, я предполагаю выбор дизайна.

0 голосов
/ 06 июля 2019

Возможно, вам придется сгруппировать некоторые из этих проверок издателя в одного потребителя.Есть классная игровая площадка, очерчивающая каркас комбайна, и вот как они делают сценарий использования .В этом примере они проверяют имя пользователя и пароль в пределах одного подписчика.Подписчик не выполняет до тех пор, пока что-то будет опубликовано для издателей имени пользователя и пароля.

Если вы хотите сохранить их отдельно, вам нужно будет добавить еще несколько издателей, в которых в общих чертах указано, действителен ли пароль и допустимо ли имя пользователя.Затем попросите подписчиков слушать, когда издатели имени и пароля действительны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...