Swift组合:使用其他发布者(使用CombineLatest)的后续发布者不会“触发”

2024-04-06

我正在尝试复制 WWDC 2019 会议“实践中组合”中给出的“向导学校注册”示例https://developer.apple.com/videos/play/wwdc2019/721/ https://developer.apple.com/videos/play/wwdc2019/721/从 22:50 开始,使用 SwiftUI(而不是会议期间使用的 UIKit)。

我已经从示例中创建了所有发布者:validatedEMail、validatedPassword 和 validatedCredentials。虽然 validatedEMail 和 validatedPassword 工作得很好,但 validatedCredentials (它使用了 JointLatest 来消耗两个发布者)永远不会触发

//
//  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

我希望在提供有效的用户名(有效的电子邮件地址)和两个具有正确长度的匹配密码时启用表单按钮。负责这两项任务的两个发布者正在工作,我可以在用户界面中的两个文本中看到验证电子邮件和验证密码,这是我出于调试目的而添加的。

只有第三个发布者(也与上面视频中 32:20 处显示的代码进行比较)永远不会触发。我确实在这些发布者中设置了断点,在 validPassword 发布者的行中:

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

它停在那里很好,但在 validateCredentials Publisher 行中有一个类似的断点:

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

从未达到。

我做错了什么?

Edit:

为了使上述代码能够在Xcode-beta 11.0 beta 4下运行didChange需要替换为willChange


我在这里回答了这个问题:https://forums.swift.org/t/crash-in-swiftui-app-using-combine-was-using-published-in-conjunction-with-state-in-swiftui/26628/9 https://forums.swift.org/t/crash-in-swiftui-app-using-combine-was-using-published-in-conjunction-with-state-in-swiftui/26628/9非常友好和乐于助人纳努·乔吉,谁不在 stackoverflow 上。

它相当简单:

添加这一行:

        .receive(on: RunLoop.main) // run on main thread 

in validatedCredentials这样它看起来像这样:

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

这就是所需要的一切。

这里再次提供整个代码供参考(针对 Xcode 11.0 beta 5 (11M382q) 进行了更新):

//
//  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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Swift组合:使用其他发布者(使用CombineLatest)的后续发布者不会“触发” 的相关文章

  • 如何覆盖应用程序中的内部框架方法(框架外部)

    在 Swift 中子类化时是否有重写内部框架方法 前任 超类 public class BarChartRenderer ChartDataRendererBase internal func drawDataSet context con
  • 失败:错误域=NSURLErrorDomain代码=-1004“无法连接到服务器。”

    首先 我想列出我已阅读并尝试实现答案的帖子 避免重复 iOS 9 和 iOS 10 中的应用程序传输安全问题 https stackoverflow com questions 40280936 app transport security
  • 使用 Swift 3 和 Realm 同步 Apple Watch 和 iPhone

    我需要从 Apple Watch 和 iPhone 显示和修改我的数据结构 数据库 我目前正在使用一个简单的领域结构 其中有一个对象 A 和一个可以容纳大量 A 的对象 B 因此 在 iPhone 上 用户可以创建 B 并添加 A 当然还可
  • 将浮点数截断为小数点后两位的最简单方法?

    在 Swift 中 有没有办法将浮点数截断为小数点后两位 以便您可以用它执行进一步的计算 我见过的所有线程都涉及到字符串的转换 我不知道如何在数学上使用它 我尝试使用扩展 在这个论坛上找到 认为我可以在截断后转换回浮动 但我最终回到了开始的
  • 沙盒测试帐户反复询问 iOS 应用内购买的密码

    我用 Swift 语言开发了一个应用程序 我添加了应用内购买来删除广告 我还创建了一个沙箱帐户来测试 但后来我忘记了这个账户的信息 我不确定信息 密码输入屏幕仍然以闪烁的屏幕显示方式显示 即使我重置设备并重新加载它 也没有任何好处 实际上一
  • Swift Firebase - 如何在使用 queryOrdered(byChild: ).queryEqual(toValue: ) 时获取所有 k/v

    root reviews postABC postId reviewXYZ I want everything under this reviewUID buyerUID 01010 text fast shipping responseT
  • 从 Firestore Swift 获取文档 ID

    我正在尝试从中获取文档IDFirestore通过执行这样的查询 func updateStatusInFirestore let orderid saleOrder first Orderid print orderid let setti
  • 无法在 Swift 中创建文件路径

    我尝试在 Swift 中打开该文件 为此 我创建了文件路径 这不起作用 maaaacy pwd Users tsypa maaaacy cat a txt test maaaacy a swift nil maaaacy 剧本 usr bi
  • Carthage 更新错误:“GitHub API 请求失败:凭据错误”

    这是我的购物车文件 Kanna HTML parsing library github tid kijyun Kanna gt 1 0 0 Realm database ORM github realm realm cocoa 当我跑步时c
  • 使用 Swift 创建 NSAlert

    我有在 Objective C 中创建和 NSAlert 的代码 但我现在想在 Swift 中创建它 该警报旨在确认用户想要删除文档 我想要 删除 按钮来运行删除功能 而 取消 按钮只是为了消除警报 我怎样才能用 Swift 写这个 NSA
  • swift 中闭包和函数作为参数的区别

    我有将近 4 年的 Objective C 经验 并且是 swift 的新手 我试图从 Objective C 的角度理解 swift 的概念 所以如果我错了 请指导我 在目标 c 中 我们有块 可以稍后异步执行的代码块 这绝对是完全合理的
  • 具有透明背景的 Swift 模态视图控制器 [重复]

    这个问题在这里已经有答案了 我知道这个话题很受欢迎 但我在编程语言中遇到了一些问题 事实是我仍然不明白我把代码放在哪里 好吧 我就来说说整个案子 我正在尝试制作一个与正常情况稍有不同的模态 Swift 通过单击按钮 ViewControll
  • Mapkit 在 IOS 13 中使用过多的 CPU

    最近 在一些用户更新到 iOS 13 x 后 我的 iOS 应用程序开始频繁崩溃 在 iOS 12 x 中没有出现该问题 我正在使用 Mapkit 渲染一些 MKPolygons 和 MKPolylines MKPolylines 被删除并
  • UIApplication.shared.delegate 相当于 SceneDelegate xcode11?

    我在 SceneDelegate 中定义了一个 let 属性 我希望一些 ViewController 能够在场景中访问它 在 UIKit 中 我可以像这样访问 App Delegate 属性 UIApplication shared de
  • 弱变量中间为零

    弱变量什么时候变为零 weak var backgroundNode SKSpriteNode texture SKTexture image initialBackgroundImage backgroundNode position C
  • 在 iOS 上使用 HEVC 编码器输出视频尺寸巨大

    我有一个项目 目前使用 H 264 编码器在 iOS 上录制视频 我想尝试在 iOS 11 中使用新的 HEVC 编码器来减小文件大小 但发现使用 HEVC 编码器会导致文件大小急剧膨胀 GitHub 上的一个项目显示了该问题 它使用 H
  • ios Vision VNImageRequestHandler方向问题

    我正在尝试使用相机通过相机检测脸部VNImageRequestHandler iOS 愿景 当我在横向模式下用相机指向照片时 它会检测到面部 但方向模式相反 let detectFaceRequestHandler VNImageReque
  • CustomNSError 协议有什么作用以及为什么我应该采用它?

    什么是CustomNSError协议的用途以及为什么我应该采用它 Apple提供的文档仅指出 描述错误类型 具体提供域 代码和 用户信息字典 我已经在谷歌上搜索过 但找不到与我的问题相关的任何内容 每种类型都符合Error协议是隐含地桥接的
  • Swift 中的 viewWillLayoutSubviews

    我正在尝试翻译SKScene scene GameScene sceneWithSize skView bounds size 进入 swift 但我收到错误 sceneWithSize 不可用 使用对象构造 SKScene size 我在
  • 进入后台时 Alamofire 请求卡住?

    我正在使用 Alamofire 调用 Web 服务 该服务需要相当长的时间才能加载 如果应用程序进入后台 当我返回应用程序时 我会被加载程序卡住 我想这是因为调用永远不会向我的完成处理程序返回任何内容 我该如何解决这个问题 您可以使用后台抓

随机推荐

  • Ofstream 创建但不会写入文件

    我编写了一些代码 执行基本的 fizzbuzz 程序来测试我的日志记录类 由于某种原因 数据被很好地转储到控制台 并且文件被创建得很好 但是每当我打开它时 日志文件都是空的 我的主要在这里 int main logger loggerObj
  • 查找数组中重复次数最多的对象

    我有一个充满字符串的数组 每个字符串都是一个名称 有些名称可能相同 有些可能不同 我正在使用的语言是 Objective C 我希望能够从该数组中找出哪个名称最受欢迎 该数组将根据用户提供给应用程序的信息是动态的 我不知道如何有效地实现这一
  • 使用用户名和密码在 C# 中启动进程会引发“访问被拒绝”异常

    在运行模拟的 NET 3 5 Web 应用程序中 我尝试通过以下方式执行进程 var process new Process StartInfo CreateNoWindow true FileName someFileName Domai
  • 如何包含与其中的 OBJECT 文件同名的静态库?

    我正在开发一个 iPad 应用程序 其中包含 2 个第三方静态库 这两个库中的目标文件的名称相同 在构建应用程序时我得到 Apple Mach O id 错误 因为这两个库中的目标文件名称相同 如何解决这个问题呢 错误看起来像 ld dup
  • 在android中使用SMTP无意图发送邮件

    您好 我正在开发一个 Android 应用程序 只需单击按钮即可发送邮件 代码最初可以工作 但由于某种原因现在无法工作 有人可以帮我解决这个问题吗 电子邮件受保护 cdn cgi l email protection是收件人 电子邮件受保护
  • 在 selenium 上使用 BeautifulSoup 和 Geckodriver 有什么区别?

    我目前对使用 selenium 3 的 beautiful soup 和 geckodriver 都是新手 我正在开发一个项目 我必须从网页中抓取 URL 我发现它们都用于网页抓取 但无法区分两者之间的区别 BeautifulSoup 和
  • 为什么需要用 pyqtSlot 装饰连接的槽?

    我正在使用 pyqt5 并且使用类似于以下的代码连接了几种方法 self progress canceled connect self cancel 例如 其中self cancel is def cancel self self time
  • iOS:条形样式外观缺少 Monotouch 绑定?

    我正在尝试将其转换为 Monotouch C UINavigationBar appearance setBarStyle UIBarStyleBlackOpaque 但在外观对象上似乎没有条形样式 是否有解决方法或替代接入点 此房源没有装
  • Angular ngRoute 突然将 URL 转换为编码字符 #!/#%2F

    我的应用程序中的所有内容都工作正常 直到我尝试添加 ngAnimate ngMaterial 和 ng image gallery 我不知道添加这些模块是否是问题的根源 但在问题发生之前我没有更改任何其他内容 从那时起 即使在我从 app
  • Android——在 OnDrawFrame 方法之外将 GLSurfaceView.Renderer 置于睡眠状态(如 Thread.sleep(20))

    我想控制 GLSurfaceView Renderer 的渲染速率 我在扩展 GLSurfaceView 的类中实现了一个线程 并在 while true 循环中定期将其置于睡眠状态 这不会减慢渲染器的速度 有一个很好的答案here htt
  • 自旋锁与信号量

    信号量和自旋锁之间的基本区别是什么 我们什么时候会使用信号量而不是自旋锁 自旋锁和信号量主要有四个不同点 1 它们是什么 A spinlock是锁的一种可能实现 即通过忙等待 旋转 实现的锁 信号量是锁的概括 或者 相反 锁是信号量的特例
  • 带有节标题的列表视图android

    在 android listview gt Headerbar section 中是否有可能不滚动 直到该部分的列表不滚动 就像 iPhone 的桌面视图一样 我使用了部分列表视图 但我想要像这个 iphone 表格视图 有没有可能 谢谢
  • Jenkins 在 ClearCase 中创建视图

    我正在使用 Jenkins 和 ClearCase 进行自动构建 但遇到了问题 我编写了一个批处理脚本 使用cleartool命令mkview在ClearCase中创建视图 当我通过单击脚本来执行该脚本时 一切正常 视图是在 ClearCa
  • 解析在tinyxml中

    如何在 TinyXML 中解析以下内容
  • netstandard 1.5 中的 BinaryFormatter

    根据 NET CoreFx API 及其关联的 NET 平台标准版本列表 https github com dotnet corefx blob master Documentation architecture net platform
  • 在 .Net 中使用私有集初始化属性

    public class Foo public string Name get private set lt Because set is private void Main var bar new Foo Name baz lt This
  • 二维数组作为函数的参数

    为什么不能像处理普通数组一样在函数中声明二维数组参数 void F int bar Ok void Fo int bar Not ok void Foo int bar SIZE Ok 为什么需要声明列的大小 静态数组 你似乎没有完全明白这
  • 如何在yii2高级模板中上传web文件夹中的文件?

    我尝试在后端上传文件 每次上传文件时 它都会成功上传并成功保存在数据库中 但它没有保存到我指定的目录中 因此我的应用程序找不到该文件 并且我已经给出了 777对 web 目录中的 uploads 文件夹的权限 下面是我的代码 处理和保存文件
  • 如何使用 Compact Framework 在 C# 中验证 X.509 证书

    我正在尝试使用 C 和 NetCF 验证 X 509 证书 我有 CA 证书 如果我理解正确的话 我需要使用该 CA 证书中的公钥来解密不受信任的证书的签名 这应该给我不可信证书的计算哈希值 然后我应该自己计算证书的哈希值并确保两个值匹配
  • Swift组合:使用其他发布者(使用CombineLatest)的后续发布者不会“触发”

    我正在尝试复制 WWDC 2019 会议 实践中组合 中给出的 向导学校注册 示例https developer apple com videos play wwdc2019 721 https developer apple com vi