为什么我的视图没有在 SwiftUI 中呈现,即使 Publisher 发出了一个值
Why my views are not Rendered in SwiftUI even though the Publisher emits a value
我在 swiftUI 中有一个用户名 textField。我正在尝试在发布者的帮助下验证输入。
这是我的代码:
View
struct UserView: View {
@StateObject private var userViewModel = UserViewModel()
init(){
UITextField.appearance().semanticContentAttribute = .forceRightToLeft
UITextField.appearance().keyboardAppearance = .dark
}
var body: some View {
SecureField("", text: $userViewModel.passwordText)
Text(userViewModel.passwordError).foregroundColor(.red)
.frame(width: 264, alignment: .trailing)
}
}
视图模型
ViewModel
final class UserViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
@Published var userText: String = ""
@Published var userTextError = ""
private var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> {
return $userText
.dropFirst()
.map{(username:[=11=], isValid: ![=11=].isEmpty)}
.eraseToAnyPublisher()
}
private var usernamevalidated: AnyPublisher<Bool,Never> {
return usernamevalidation
.filter{[=11=].isValid}
.map{[=11=].username.isValidUserName()}
.eraseToAnyPublisher()
}
init(){
usernamevalidation.receive(on: RunLoop.main)
.map{[=11=].isValid ? "": "Emptyusername "}
.assign(to: \.userTextError, on: self)
.store(in: &cancellables)
usernamevalidated.receive(on: RunLoop.main)
.map{[=11=] ? "" : "wrong username "}
.assign(to: \.userTextError, on: self)
.store(in: &cancellables)
}
}
扩展
extension String {
func isValidUserName() -> Bool {
let usernameRegex = "^[a-zA-Z0-9_-]*$"
let usernamepred = NSPredicate(format:"SELF MATCHES %@", usernameRegex)
return usernamepred.evaluate(with: self)
}
}
在 ViewModel 的 init() 块中验证的用户名中,我将错误分配给 userTextError 属性,这应该反映在文本视图中。如果输入了特殊字符,如 @ 或 % .. 等,就会发生这种情况。发生的情况是,有时错误会以红色显示,而其他错误则不会显示,即使我尝试在 map 运算符之后打印字符串的值,我也可以看到打印正常的字符串。只是错误有时会反映在视图中,有时不会。我是漏掉了什么还是做错了什么
之前的答案(如果需要请查看编辑)是不正确的,因为 Emptyusername
错误将被覆盖,这不是我们需要的(我的错误)。
事实证明,问题是 iOS 14 更新 UI!我之前不得不为 TextField
使用的修复程序看起来有点不寻常,但确实有效。
只需在视图正文中添加:
var body: some View {
let _ = userViewModel.userTextError
/* Rest of view as before */
}
不要为您的发布商使用可计算的存储属性(以保持它们的生命力),例如
private lazy var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> = {
return $userText
.dropFirst()
.map{(username:[=10=], isValid: ![=10=].isEmpty)}
.eraseToAnyPublisher()
}()
问题是 usernamevalidation
和 usernamevalidated
都是计算属性。存储它们将解决问题,但您也可以通过观察对 userText
的更改、验证它们并分配给 userTextError
来简化视图模型,如下所示:
final class UserViewModel: ObservableObject {
@Published var userText: String = ""
@Published private(set) var userTextError = ""
init() {
$userText
.dropFirst()
.map { username in
guard !username.isEmpty else {
return "Username is empty"
}
guard username.isValidUserName() else {
return "Username is invalid"
}
return ""
}
.receive(on: RunLoop.main)
.assign(to: &$userTextError)
}
}
还值得一提的是,将 .assign(to: \.userTextError, on: self)
替换为 .assign(to: &$userTextError)
可以消除内存泄漏,这意味着您确实需要将其存储在 cancellables
中。
我在 swiftUI 中有一个用户名 textField。我正在尝试在发布者的帮助下验证输入。
这是我的代码:
View
struct UserView: View {
@StateObject private var userViewModel = UserViewModel()
init(){
UITextField.appearance().semanticContentAttribute = .forceRightToLeft
UITextField.appearance().keyboardAppearance = .dark
}
var body: some View {
SecureField("", text: $userViewModel.passwordText)
Text(userViewModel.passwordError).foregroundColor(.red)
.frame(width: 264, alignment: .trailing)
}
}
视图模型
ViewModel
final class UserViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
@Published var userText: String = ""
@Published var userTextError = ""
private var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> {
return $userText
.dropFirst()
.map{(username:[=11=], isValid: ![=11=].isEmpty)}
.eraseToAnyPublisher()
}
private var usernamevalidated: AnyPublisher<Bool,Never> {
return usernamevalidation
.filter{[=11=].isValid}
.map{[=11=].username.isValidUserName()}
.eraseToAnyPublisher()
}
init(){
usernamevalidation.receive(on: RunLoop.main)
.map{[=11=].isValid ? "": "Emptyusername "}
.assign(to: \.userTextError, on: self)
.store(in: &cancellables)
usernamevalidated.receive(on: RunLoop.main)
.map{[=11=] ? "" : "wrong username "}
.assign(to: \.userTextError, on: self)
.store(in: &cancellables)
}
}
扩展
extension String {
func isValidUserName() -> Bool {
let usernameRegex = "^[a-zA-Z0-9_-]*$"
let usernamepred = NSPredicate(format:"SELF MATCHES %@", usernameRegex)
return usernamepred.evaluate(with: self)
}
}
在 ViewModel 的 init() 块中验证的用户名中,我将错误分配给 userTextError 属性,这应该反映在文本视图中。如果输入了特殊字符,如 @ 或 % .. 等,就会发生这种情况。发生的情况是,有时错误会以红色显示,而其他错误则不会显示,即使我尝试在 map 运算符之后打印字符串的值,我也可以看到打印正常的字符串。只是错误有时会反映在视图中,有时不会。我是漏掉了什么还是做错了什么
之前的答案(如果需要请查看编辑)是不正确的,因为 Emptyusername
错误将被覆盖,这不是我们需要的(我的错误)。
事实证明,问题是 iOS 14 更新 UI!我之前不得不为 TextField
使用的修复程序看起来有点不寻常,但确实有效。
只需在视图正文中添加:
var body: some View {
let _ = userViewModel.userTextError
/* Rest of view as before */
}
不要为您的发布商使用可计算的存储属性(以保持它们的生命力),例如
private lazy var usernamevalidation: AnyPublisher<(username:String, isValid: Bool), Never> = {
return $userText
.dropFirst()
.map{(username:[=10=], isValid: ![=10=].isEmpty)}
.eraseToAnyPublisher()
}()
问题是 usernamevalidation
和 usernamevalidated
都是计算属性。存储它们将解决问题,但您也可以通过观察对 userText
的更改、验证它们并分配给 userTextError
来简化视图模型,如下所示:
final class UserViewModel: ObservableObject {
@Published var userText: String = ""
@Published private(set) var userTextError = ""
init() {
$userText
.dropFirst()
.map { username in
guard !username.isEmpty else {
return "Username is empty"
}
guard username.isValidUserName() else {
return "Username is invalid"
}
return ""
}
.receive(on: RunLoop.main)
.assign(to: &$userTextError)
}
}
还值得一提的是,将 .assign(to: \.userTextError, on: self)
替换为 .assign(to: &$userTextError)
可以消除内存泄漏,这意味着您确实需要将其存储在 cancellables
中。