Issues with UserDefault boolean

Hi,

I have a view that should do the following:

  1. Set up and confirm a passcode upon account creation.
  2. Verify passcode if signing in.
  3. Reset passcode if prompted.

With the code below, functions 1 and 2 are working. However, I'm having an issue with function 3. I am able to declare the reset UserDefault on a previous view so that the proper logic occurs, which is to read in the input, and then confirm it. However, it is not working as intended. In this code here:

else if reset {
     UserDefaults.standard.set(passcode, forKey: "new-passcode")
    UserDefaults.standard.set(false, forKey: "reset-passcode")
                                
     passcode = ""
    }

I store the new passcode, set reset to false, and clear the passcode so it can be entered again to confirm. The code does not run as intended however. The title does not change and I'm unsure if it is actually storing the passcode. And, when re-entering, it does not change the view as it should by setting view = .someView. I'm assuming there is just flaw in my logic but I'm not sure how to resolve this. Below is the full code. Please let me know if any further clarification is needed.

struct EnterPasscode: View {
    @State var title = ""
    @State var passcode = ""
    @State var message = ""
    @State var buttonState = false
    @Binding var view: Views
    
    var body: some View {
        Group {
            Spacer()
                .frame(height: 50)
            
            Text(title)
                .font(.system(size: 36))
                .multilineTextAlignment(.center)
                .frame(height: 50)
            
            Spacer()
            
            if passcode != "" {
                Text("\(passcode)")
                    .font(.system(size: 24))
                    .frame(height: 25)
            } else {
                Spacer()
                    .frame(height: 25)
            }
            
            
            Spacer()
                .frame(height: 50)
            
            PasscodeKeypad(passcode: $passcode)
            
            Spacer()
            
            if message != "" {
                Text(message)
                    .frame(height: 25)
                    .foregroundStyle(Color.red)
            } else {
                Spacer()
                    .frame(height: 25)
            }
            
            Spacer()
            
            WideButton(text: "Continue", buttonFunction: .functional, openView: .enterPasscode, view: .constant(.enterPasscode), buttonState: $buttonState)
                .onChange(of: buttonState) { oldState, newState in
                    if buttonState {
                        let passcodeSet = UserDefaults.standard.bool(forKey: "passcode-set")
                        let storedPasscode = UserDefaults.standard.string(forKey: "passcode")
                        let reset = UserDefaults.standard.bool(forKey: "passcode-reset")
                        let newPasscode = UserDefaults.standard.string(forKey: "new-passcode")
                        
                        print(reset)
                        
                        if passcode.count == 4 {
                            if storedPasscode == nil {
                                if newPasscode == nil {
                                    UserDefaults.standard.set(passcode, forKey: "new-passcode")
                                    
                                    passcode = ""
                                } else if passcode == newPasscode {
                                    UserDefaults.standard.set(passcode, forKey: "passcode")
                                    UserDefaults.standard.set(true, forKey: "passcode-set")
                                    
                                    view = .someView
                                }
                            } else if reset {
                                UserDefaults.standard.set(passcode, forKey: "new-passcode")
                                UserDefaults.standard.set(false, forKey: "reset-passcode")
                                
                                passcode = ""
                            } else if newPasscode != nil {
                                if passcode == newPasscode {
                                    UserDefaults.standard.set(passcode, forKey: "passcode")
                                    
                                    view = .someView
                                }
                            }
                        }
                        
                        checkPasscodeStatus()
                        
                        buttonState = false
                    }
                }
                    
            
            Spacer()
            
            if !UserDefaults.standard.bool(forKey: "passcode-reset") && UserDefaults.standard.bool(forKey: "passcode-set") {
                Button(action: {
                    view = .verifyPhone
                }) {
                    Text("Forgot passcode?")
                        .foregroundStyle(.black)
                }
            }
            
            Spacer()
                .frame(height: 25)
            
        }
        .onAppear() {
           checkPasscodeStatus()
        }
        .frame(width: UIScreen.main.bounds.width - 50)
    }
    
    func checkPasscodeStatus() {
        let passcodeSet = UserDefaults.standard.bool(forKey: "passcode-set")
        let storedPasscode = UserDefaults.standard.string(forKey: "passcode")
        let reset = UserDefaults.standard.bool(forKey: "passcode-reset")
        
        if reset {
            title = "Enter new passcode"
        } else if passcodeSet {
            title = "Enter Passcode"
        } else if storedPasscode != nil && storedPasscode != "" {
            title = "Confirm Passcode"
        } else {
            title = "Select a 4 Digit Passcode"
        }
    }
}

struct WideButton: View {
    @State var text: String
    @State var buttonFunction: ButtonType
    @State var openView: Views?
    @Binding var view: Views
    @Binding var buttonState: Bool
    
    var body: some View {
        Button(action: {
            buttonPressed()
        }, label: {
            ZStack {
                RoundedRectangle(cornerRadius: 5)
                    .fill(Color.black)
                    .frame(width: UIScreen.main.bounds.width - 50, height: 50)
                Text(text)
                    .foregroundColor(.white)
            }
        })
    }
    
    func buttonPressed() {
        switch buttonFunction {
            case .openView: view = openView!
            case .functional: buttonState = true
        }
    }
}

enum ButtonType {
    case openView
    case functional
}

Never store passwords or pass anything in user defaults because it is not secured. If it's a homework assignment, then it's ok.

As @MobileTen stated, never store passwords in UserDefaults. You need to use the Keychain.

Also, I'm not sure why you need to store something called passcode-reset and passcode-set. The very fact that a password exists means it has been set, and if you're resetting a passcode, then that should be done as part of the journey for the user, not something that needs storing anywhere. In other words, if the user wants to reset their password, ask them for a new one, and save the new password to the keychain. There's no need to store in UDs that you're resetting the password.

So, here's how to use the keychain:

let userAccount: String = "AuthenticatedUser"
let passwordKey: String = "SomeSortOfStringIdentifyingThisIsAPasswordForYourApp" // Choose a relevant value and never change it, as the password is stored against this value

func getKeychainPasscode() -> String {
	return KeychainService.loadPassword(service: passwordKey, account: userAccount) ?? ""
}

// getKeychainPasscode() will tell you if a password is set. If it returns "" then it hasn't been set.

func updatePasscode(_ value: String) {
	KeychainService.updatePassword(service: passwordKey, account: userAccount, data: value)
}


// Arguments for the keychain queries
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)


public class KeychainService: NSObject {
	class func updatePassword(service: String, account: String, data: String) {
		if let dataFromString: Data = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
			// Instantiate a new default keychain query
			let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue])

			let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue : dataFromString] as CFDictionary)

			if(status == errSecItemNotFound) {
				// No existing passcode, so just save the new one
				savePassword(service: service, account: account, data: data)

			} else {
				// Passcode exists, so delete it and save the new one
				removePassword(service: service, account: account)
				savePassword(service: service, account: account, data: data)
			}
		}
	}


	class func removePassword(service: String, account: String) {
		// Instantiate a new default keychain query
		let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue])

		// Delete any existing items
		let status = SecItemDelete(keychainQuery as CFDictionary)
		if(status != errSecSuccess) {
			if let err = SecCopyErrorMessageString(status, nil) {
				print("Remove failed: \(err)")
			}
		}
	}


	class func savePassword(service: String, account: String, data: String) {
		if let dataFromString = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
			// Instantiate a new default keychain query
			let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])

			// Add the new keychain item
			let status = SecItemAdd(keychainQuery as CFDictionary, nil)

			// Always check the status
			if (status != errSecSuccess) {
				if let err = SecCopyErrorMessageString(status, nil) {
					print("Write failed: \(err)")
				}
			}
		}
	}


	class func loadPassword(service: String, account: String) -> String? {
		// Instantiate a new default keychain query
		// Tell the query to return a result
		// Limit our results to one item
		let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

		var dataTypeRef: AnyObject?

		// Search for the keychain items
		let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
		var contentsOfKeychain: String?

		if(status == errSecSuccess) {
			if let retrievedData = dataTypeRef as? Data {
				contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8)
			}
		}

		return contentsOfKeychain
	}
}

It is much safer to store passwords and other sensitive information in the keychain. If you want to update your UI in response to changes in UserDefaults it is better to use AppStorage.

Issues with UserDefault boolean
 
 
Q