SwiftUI - 更新@State 不会改变视图

Posted

技术标签:

【中文标题】SwiftUI - 更新@State 不会改变视图【英文标题】:SwiftUI - updating @State is not changing the view 【发布时间】:2020-11-03 10:21:08 【问题描述】:

我是 SwiftUI 的新手,我想创建一个带有“快速开发人员访问”按钮(又名“User1”)的登录屏幕,它应该填写用户和密码字段。

当我点击“User1”按钮时,LoginScreen@State 电话和密码变量正确填充了正确的值,但CustomLoginField 没有显示更新后的值。

我也尝试使用@Binding var phone:String 代替@State,但结果相同。

我知道@State 应该是私有的。所以我也想知道编写代码的正确方法。

import SwiftUI

struct LoginScreen: View 
    
    private let developers:[User] = [
        .init(name: "User1", phone: "12345678", password: "1234")]
    
    private let minSpaceFromBottom:CGFloat = 30
    @State fileprivate var phone:String = ""
    @State fileprivate var password:String = ""
    
    var body: some View 
        GeometryReader  geo in
            ZStack 
                Color(#colorLiteral(red: 0.9965313077, green: 0.9966740012, blue: 0.9965001941, alpha: 1))
                VStack(alignment: .center) 
                    Spacer().frame(height: geo.safeAreaInsets.top)
                        
                    CustomLoginField(inputStr:phone, keyboard: .numberPad, placeholder:"PHONE", formatedPhoneNumber:true, maxLength: 10)
                    CustomLoginField(inputStr:password, keyboard: .numberPad, placeholder: "PASSWORD", isSecure: true, maxLength: 4)
                       
                            HStack 
                                ForEach(developers, id:\.id)  developer in
                                    Text(developer.name!)
                                        .frame(width: 70, height: 35, alignment: .center)
                                        .onTapGesture 
                                            phone = developer.phone
                                            password = developer.password
                                        
                                
                        
                        
                        Spacer()
                        
                    
                    .frame(width: geo.size.width * 0.9, alignment: .leading)
                    Spacer()
                        .frame(height: minSpaceFromBottom)
                
            
    


private struct CustomLoginField: View 
    
    @State var inputStr:String
    var keyboard:UIKeyboardType = .default
    var placeholder:String
    var isSecure:Bool = false
    var formatedPhoneNumber = false
    var maxLength = 0
    var underlineColor:UIColor 
        let underlineOnColor = UIColor(.blue)
        let underlineOffColor:UIColor = .lightGray
        return inputStr.count >= maxLength ? underlineOnColor : underlineOffColor
    
    
    var body: some View 
        
        VStack(alignment:.leading, spacing:-8) 
            Text(placeholder)
                .opacity(!inputStr.isEmpty ? 1 : 0)
                .animation(Animation.easeIn(duration: !inputStr.isEmpty ? 0.3 : 0))
            
            if isSecure 
                SecureField(placeholder, text: $inputStr)
                    .modifier(CustomTextFieldModifire(parent: self))
             else 
                TextField(placeholder, text: $inputStr)
                    .modifier(CustomTextFieldModifire(parent: self))
            
            
            Color(underlineColor)
                .frame(width: UIScreen.main.bounds.width * 0.88, height: 1)
                .animation(Animation.easeIn(duration:0.3))
        
    


private struct CustomTextFieldModifire: ViewModifier 
    var parent:CustomLoginField
    
    func body(content: Content) -> some View 
        content
            .frame(height: 45)
            .keyboardType(parent.keyboard)
            .onReceive(parent.inputStr.publisher.collect()) 
                var result = String($0)
                
                if parent.maxLength > 0 && result.count > parent.maxLength 
                    result = String(result.prefix(parent.maxLength))
                
                
                parent.inputStr = result
            
    


private struct User:Identifiable 
    let id = UUID()
    var name:String?
    var phone:String
    var password:String


struct LoginScreen_Previews: PreviewProvider 
    static var previews: some View 
        LoginScreen()
            .previewDevice("iPhone 8")
    

【问题讨论】:

【参考方案1】:

你只是犯了一个小错误。

在customloginField中,需要定义inputStr:String作为绑定变量

@Binding var inputStr:String

在使用时,你必须按照以下方式使用它

CustomLoginField(inputStr:self.$phone, keyboard: .numberPad, placeholder:"PHONE", formatedPhoneNumber:true, maxLength: 10)
CustomLoginField(inputStr:self.$password, keyboard: .numberPad, placeholder: "PASSWORD", isSecure: true, maxLength: 4)

在电话和密码前注意到“$”?

检查下面的 SO 线程以查看 @state 与 @binding SwiftUI @State vs Binding

【讨论】:

以上是关于SwiftUI - 更新@State 不会改变视图的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中使用 @State 属性包装器未更新视图

无法更新/修改 SwiftUI 视图的 @state var

SwiftUI 闭包更新 @State init

SwiftUI,为啥部分视图没有刷新? @State 变量未更新

SwiftUI:在数组中更新值后视图没有改变

SwiftUI:如何在函数计算 @State 值时同时更新视图?