快速合并:使用其他合并(使用CombineLatest)的其他发布者的后续发布者不会“解雇" [英] Swift Combine: subsequent Publisher that consumes other Publishers (using CombineLatest) doesn't "fire"
问题描述
我正在尝试复制WWDC 2019会议实践结合"中给出的向导学校注册"示例 我希望提供有效的用户名(有效的电子邮件地址)和两个正确长度的匹配密码时,启用表单按钮.负责这两个任务的两个发布者可以正常工作,我可以在用户界面的两个文本中看到validatedEMail和validatedPassword,我添加了这两个文本是为了调试. 第三个发布者(也与上面32:20的视频中显示的代码进行比较)永远不会触发.我确实在这些发布者的validatedPassword Publisher中设置了断点: 在此停下来就好了,但是在validatedCredentials Publisher的以下行有一个类似的断点: 从未到达. 我做错了什么? 为了使上述代码在Xcode-beta 11.0 beta 4下运行,需要将 我在这里回答了这个问题: 这就是所需要的. 这里又是整个代码的参考时间(已针对Xcode 11.0 beta 5(11M382q)更新): I am trying to replicate the "Wizard School Signup"-example which was given in the WWDC 2019 session "Combine in Practice" https://developer.apple.com/videos/play/wwdc2019/721/ starting at 22:50 using SwiftUI (as opposed to UIKit, which was used during the session). I have created all the publishers from the example: validatedEMail, validatedPassword and validatedCredentials. While validatedEMail and validatedPassword work just fine, validatedCredentials, which consumes both publishers using CombineLatest, never fires I expected the form button to get enabled when a valid username (valid E-Mail-address) and two matching passwords with the right length are provided. The two Publishers responsible for those two tasks work, I can see the validatedEMail and the validatedPassword in the user interface in the two Texts which I added for debugging purposes. Just the third Publisher (also compare to the code shown in the Video from above at 32:20) never fires. I did set breakpoints in those Publishers, in the validatedPassword Publisher at line: which stopped there just fine but a similar breakpoint in the validatedCredentials Publisher at line: was never reached. What did I do wrong? Edit: In order to make the above code run under Xcode-beta 11.0 beta 4 I've got this question answered here: https://forums.swift.org/t/crash-in-swiftui-app-using-combine-was-using-published-in-conjunction-with-state-in-swiftui/26628/9 by the very friendly and helpful Nanu Jogi, who is not on stackoverflow. It is rather straight forward: add this line: in This is all what is needed. And here one more time the whole code for reference (updated for Xcode 11.0 beta 5 (11M382q)):
这篇关于快速合并:使用其他合并(使用CombineLatest)的其他发布者的后续发布者不会“解雇"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!////RegistrationView.swift////由Lars Sonchocky-Helldorf在19年4月7日创建.//版权所有©2019 Lars Sonchocky-Helldorf.版权所有.//导入SwiftUI进口联合收割机struct RegistrationView:查看{@ObjectBinding var registrationModel = RegistrationModel()@State私人var showAlert = false@State私人var alertTitle:字符串="@State私人var alertMessage:字符串="@State私有var registrationButtonDisabled = true@State私人var validatedEMail:字符串="@State私人var validatedPassword:字符串="var body:some View {形式 {部分 {TextField(输入您的电子邮件",文本:$ registrationModel.eMail)SecureField(输入密码",文本:$ registrationModel.password)SecureField(再次输入密码",文本:$ registrationModel.passwordRepeat)Button(操作:registrationButtonAction){文字(创建帐户")}.disabled($ registrationButtonDisabled.value).presentation($ showAlert){Alert(标题:Text("\(alertTitle)"),消息:Text("\(alertMessage)"))}.onReceive(self.registrationModel.validatedCredentials){newValidatedCredentials inself.registrationButtonDisabled =(newValidatedCredentials ==零)}}部分 {文本(验证的电子邮件:\(validatedEMail)").onReceive(self.registrationModel.validatedEMail){self.validatedEMail = newValidatedEMail!=无吗?newValidatedEMail!:发送电子邮件无效"}文本(验证的密码:\(validatedPassword)").onReceive(self.registrationModel.validatedPassword){self.validatedPassword = newValidatedPassword!=无?newValidatedPassword!:密码短或不匹配"}}}.navigationBarTitle(Text("Sign Up"))}func registrationButtonAction(){let trimmedEMail:字符串= self.registrationModel.eMail.trimmingCharacters(在:.whitespaces中)if(trimmedEMail!="&& self.registrationModel.password!="){NetworkManager.sharedInstance.registerUser(NetworkManager.RegisterRequest(uid:trimmedEMail,password:self.registrationModel.password)){(状态)在如果状态== 200 {self.showAlert = trueself.alertTitle = NSLocalizedString(注册成功",注释:")self.alertMessage = NSLocalizedString(请验证您的电子邮件和登录名,注释:")}否则,如果status == 400 {self.showAlert = trueself.alertTitle = NSLocalizedString("Registration Error",注释:")self.alertMessage = NSLocalizedString(已经注册",注释:")} 别的 {self.showAlert = trueself.alertTitle = NSLocalizedString("Registration Error",注释:")self.alertMessage = NSLocalizedString(网络或应用程序错误",注释:")}}} 别的 {self.showAlert = trueself.alertTitle = NSLocalizedString("Registration Error",注释:")self.alertMessage = NSLocalizedString(用户名/密码为空",注释:")}}}类RegistrationModel:BindableObject {@Published var eMail:字符串="@已发布的var密码:字符串="@Published var passwordRepeat:字符串="公共var didChange = PassthroughSubject< Void,Never>()var validatedEMail:AnyPublisher< String ?,从不>{返回$ eMail.debounce(for:0.5,scheduler:RunLoop.main).removeDuplicates().flatMap {返回Future {承诺于self.usernameAvailable(username){在以下版本中可用promise(.success(可用?用户名:nil))}}}.eraseToAnyPublisher()}var validatedPassword:AnyPublisher< String ?,从不>{返回Publishers.CombineLatest($ password,$ passwordRepeat).debounce(for:0.5,scheduler:RunLoop.main).map {密码,密码重复输入保护密码== passwordRepeat,password.count>5 else {return nil}返回密码}.eraseToAnyPublisher()}var validatedCredentials:AnyPublisher<(String,String)?, Never>{返回Publishers.CombineLatest(validatedEMail,validatedPassword).map {守卫let eMail = validatedEMail,let password = validatedPassword else {return nil}返回(电子邮件,密码)}.eraseToAnyPublisher()}func usernameAvailable(_ username:String,complete:(Bool)-> Void){let isValidEMailAddress:Bool = NSPredicate(格式:"SELF MATCHES%@","[A-Z0-9a-z ._%+-] + @ [A-Za-z0-9 .-] + \\.[A-Za-z] {2,64}).evaluate(使用:用户名)完成(isValidEMailAddress)}}#if调试struct RegistrationView_Previews:PreviewProvider {静态var预览:某些视图{RegistrationView()}}#万一
保护密码== passwordRepeat,password.count>5 else {return nil}
guard设置让电子邮件= validatedEMail,让密码= validatedPassword否则{return nil}
didChange
替换为 willChange
////RegistrationView.swift//Combine-Beta-Feedback////由Lars Sonchocky-Helldorf在09.07.19创建.//版权所有©2019 Lars Sonchocky-Helldorf.版权所有.//导入SwiftUI进口联合收割机struct RegistrationView:查看{@ObservedObject var registrationModel = RegistrationModel()@State私有var registrationButtonDisabled = true@State私人var validatedEMail:字符串="@State私人var validatedPassword:字符串="var body:some View {形式 {部分 {TextField(输入您的电子邮件",文本:$ registrationModel.eMail)SecureField(输入密码",文本:$ registrationModel.password)SecureField(再次输入密码",文本:$ registrationModel.passwordRepeat)Button(操作:registrationButtonAction){文字(创建帐户")}.disabled($ registrationButtonDisabled.wrappedValue).onReceive(self.registrationModel.validatedCredentials){newValidatedCredentials inself.registrationButtonDisabled =(newValidatedCredentials ==零)}}部分 {文本(验证的电子邮件:\(validatedEMail)").onReceive(self.registrationModel.validatedEMail){self.validatedEMail = newValidatedEMail!=无吗?newValidatedEMail!:发送电子邮件无效"}文本(验证的密码:\(validatedPassword)").onReceive(self.registrationModel.validatedPassword){self.validatedPassword = newValidatedPassword!=无?newValidatedPassword!:密码不足或不匹配的密码"}}}.navigationBarTitle(Text("Sign Up"))}func registrationButtonAction(){}}类RegistrationModel:ObservableObject {@Published var eMail:字符串="@已发布的var密码:字符串="@Published var passwordRepeat:字符串="var validatedEMail:AnyPublisher< String ?,从不>{返回$ eMail.debounce(for:0.5,scheduler:RunLoop.main).removeDuplicates().map {返回Future {承诺于打印(用户名:\(用户名)")self.usernameAvailable(username){在以下版本中可用promise(.success(可用?用户名:nil))}}}.switchToLatest().eraseToAnyPublisher()}var validatedPassword:AnyPublisher< String ?,从不>{返回Publishers.CombineLatest($ password,$ passwordRepeat).debounce(for:0.5,scheduler:RunLoop.main).map {密码,密码重复输入打印(密码:\(密码),密码重复:\(密码重复)")保护密码== passwordRepeat,password.count>5 else {return nil}返回密码}.eraseToAnyPublisher()}var validatedCredentials:AnyPublisher<(String,String)?, Never>{返回Publishers.CombineLatest(validatedEMail,validatedPassword).receive(在:RunLoop.main上).map {print("validatedEMail:\(validatedEMail ??"未设置),validatedPassword:\(validatedPassword ??"未设置)")守卫let eMail = validatedEMail,let password = validatedPassword else {return nil}返回(电子邮件,密码)}.eraseToAnyPublisher()}func usernameAvailable(_ username:String,complete:(Bool)-> Void){let isValidEMailAddress:Bool = NSPredicate(格式:"SELF MATCHES%@","[A-Z0-9a-z ._%+-] + @ [A-Za-z0-9 .-] + \\.[A-Za-z] {2,64}).evaluate(使用:用户名)完成(isValidEMailAddress)}}#if调试struct RegistrationView_Previews:PreviewProvider {静态var预览:某些视图{RegistrationView()}}#万一
//
// 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
guard password == passwordRepeat, password.count > 5 else { return nil }
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
didChange
needs to be replaced with willChange
.receive(on: RunLoop.main) // run on main thread
validatedCredentials
so that it looks like this:var validatedCredentials: AnyPublisher<(String, String)?, Never> {
return Publishers.CombineLatest(validatedEMail, validatedPassword)
.receive(on: RunLoop.main) // <<—— run on main thread
.map { validatedEMail, validatedPassword in
print("validatedEMail: \(validatedEMail ?? "not set"), validatedPassword: \(validatedPassword ?? "not set")")
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
return (eMail, password)
}
.eraseToAnyPublisher()
//
// RegistrationView.swift
// Combine-Beta-Feedback
//
// Created by Lars Sonchocky-Helldorf on 09.07.19.
// Copyright © 2019 Lars Sonchocky-Helldorf. All rights reserved.
//
import SwiftUI
import Combine
struct RegistrationView : View {
@ObservedObject var registrationModel = RegistrationModel()
@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.wrappedValue)
.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 match"
}
}
}
.navigationBarTitle(Text("Sign Up"))
}
func registrationButtonAction() {
}
}
class RegistrationModel : ObservableObject {
@Published var eMail: String = ""
@Published var password: String = ""
@Published var passwordRepeat: String = ""
var validatedEMail: AnyPublisher<String?, Never> {
return $eMail
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
.map { username in
return Future { promise in
print("username: \(username)")
self.usernameAvailable(username) { available in
promise(.success(available ? username : nil))
}
}
}
.switchToLatest()
.eraseToAnyPublisher()
}
var validatedPassword: AnyPublisher<String?, Never> {
return Publishers.CombineLatest($password, $passwordRepeat)
.debounce(for: 0.5, scheduler: RunLoop.main)
.map { password, passwordRepeat in
print("password: \(password), passwordRepeat: \(passwordRepeat)")
guard password == passwordRepeat, password.count > 5 else { return nil }
return password
}
.eraseToAnyPublisher()
}
var validatedCredentials: AnyPublisher<(String, String)?, Never> {
return Publishers.CombineLatest(validatedEMail, validatedPassword)
.receive(on: RunLoop.main)
.map { validatedEMail, validatedPassword in
print("validatedEMail: \(validatedEMail ?? "not set"), validatedPassword: \(validatedPassword ?? "not set")")
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