从现有的 SwiftUI @States 派生绑定
Posted
技术标签:
【中文标题】从现有的 SwiftUI @States 派生绑定【英文标题】:Deriving binding from existing SwiftUI @States 【发布时间】:2019-10-25 00:27:52 【问题描述】:我一直在玩 SwiftUI 和 Combine,感觉可能有一种方法可以在视图中获取现有的 @State 属性并创建一个新属性。
例如,我有一个密码创建视图,其中包含用户的密码和密码确认字段。我想获取这两个@State 属性并派生一个新的@State,我可以在我的视图中使用它来断言输入是否有效。所以为简单起见:不为空且相等。
Apple 文档在绑定上说 there is a publisher,但我似乎无法掌握它。
这是一些不起作用的伪代码:
import SwiftUI
import Combine
struct CreatePasswordView : View
@State var password = ""
@State var confirmation = ""
lazy var valid =
return self.$password.publisher()
.combineLatest(self.$confirmation)
.map $0 != "" && $0 == $1
var body: some View
SecureField($password, placeholder: Text("password"))
SecureField($confirmation, placeholder: Text("confirm password"))
NavigationButton(destination: NextView()) Text("Done")
.disabled(!valid)
任何人都找到了。解决这个问题的适当方式/如果可能的话?
更新 Beta 2:
从 beta 2 开始,发布者可用,因此该代码的前半部分现在可以使用。在 View 中使用生成的发布者的后半部分我还没有弄清楚 (disabled(!valid)
)。
import SwiftUI
import Combine
struct CreatePasswordView : View
@State var password = ""
@State var confirmation = ""
lazy var valid =
Publishers.CombineLatest(
password.publisher(),
confirmation.publisher(),
transform: String($0) != "" && $0 == $1
)
()
var body: some View
SecureField($password, placeholder: Text("password"))
SecureField($confirmation, placeholder: Text("confirm password"))
NavigationButton(destination: NextView()) Text("Done")
.disabled(!valid)
谢谢。
【问题讨论】:
你试过var valid: Bool password != "" && password == confirmation
吗?因为password
和confirmation
都是@State
,所以body
在它们发生变化时总是会重新计算,valid
将反映新的状态
@RicoCrescenzio 我认为这是正确的想法
【参考方案1】:
你需要的是一个计算属性。顾名思义,每次访问属性时都会重新计算其值。如果你使用@State
变量来计算属性,SwiftUI会在valid
发生变化时自动重新计算View的body:
struct CreatePasswordView: View
@State var password = ""
@State var confirmation = ""
private var valid: Bool
password != "" && password == confirmation
var body: some View
SecureField($password, placeholder: Text("password"))
SecureField($confirmation, placeholder: Text("confirm password"))
NavigationLink(destination: NextView()) Text("Done")
.disabled(!valid)
【讨论】:
【参考方案2】:我不会玩 @State/@Published
,因为 Combine
目前处于测试阶段,但这里有一个简单的解决方法,可以解决您想要实现的目标。
我会实现一个视图模型来保存密码、密码确认以及它是否有效
class ViewModel: NSObject, BindableObject
var didChange = PassthroughSubject<Void, Never>()
var password: String = ""
didSet
didChange.send(())
var passwordConfirmation: String = ""
didSet
didChange.send(())
var isPasswordValid: Bool
return password == passwordConfirmation && password != ""
这样,只要密码或确认发生变化,就会重新计算视图。
然后我会为视图模型创建一个@ObjectBinding
。
struct CreatePasswordView : View
@ObjectBinding var viewModel: ViewModel
var body: some View
NavigationView
VStack
SecureField($viewModel.password,
placeholder: Text("password"))
SecureField($viewModel.passwordConfirmation,
placeholder: Text("confirm password"))
NavigationButton(destination: EmptyView()) Text("Done")
.disabled(!viewModel.isPasswordValid)
我不得不将视图放在NavigationView
中,因为如果NavigationButton
不在其中之一中,它似乎不会启用自己。
【讨论】:
你为什么说Combine被窃听了?您在使用 Combine 时遇到过什么重大问题? @SMP 我的意思是beta
- Swift 编译器还没有完全准备好像@State/@Published
这样的@propertyWrapper/@propertyDelegate attributes
。最后,Foundation 集成无法按照 Xcode 11 beta release notes 工作 - 搜索 51241500。
欣赏这个,谢谢。我实际上已经尝试过这种方法,尽管我实际上想做的不仅仅是简单的验证,还想发出一个带去抖动的网络请求(检查haveibeenpwned.com API 是否有泄露的密码)。 Apple 有 WWDC 视频,介绍了类似的集成 UIKit+Combine 的东西 - 也许 Combine+SwiftUI 还没有,或者他们有其他的想法。
@freebie 怀疑它主要是幻灯片,而不是实时编码 - 他们不想冒红点的风险!以上是关于从现有的 SwiftUI @States 派生绑定的主要内容,如果未能解决你的问题,请参考以下文章