TheProGrammar
TheProGrammar

Reputation: 57

SwiftUI - How to change/access @Published var value via toggle from View?

I'm making a simple password generation app. The idea was simple.

There are 2 toggles in the View that are bound to ObservableObject class two @Published bool vars. The class should return a different complexity of generated password to a new View dependently on published vars true/false status after clicking generate button.

Toggles indeed change published var status to true/false (when I print it on toggle) and the destination view does show the password for false/false combination but for some reason, after clicking generate, they always stay false unless I manually change their value to true. Can toggles change permanently the value of @Published var values somehow?

I can't seem to find a suitable workaround. Any solutions how to make this work?

MainView

enter image description here

import SwiftUI

struct MainView: View {

@ObservedObject var manager = PasswordManager()

var body: some View {
    NavigationView() {
        VStack {
            ZStack {
                Toggle(isOn: $manager.includeNumbers) {
                    Text("Include numbers")
                        .italic()
                }
            }
            ZStack {
                Toggle(isOn: $manager.includeCharacters) {
                    Text("Include special characters")
                        .italic()
                }
            }
            NavigationLink(destination: PasswordView(), label: {
                Text("Generate")
                })
        }
        .padding(80)
    }
}

PasswordManager

import Foundation

class PasswordManager: ObservableObject {

    @Published var includeNumbers = false
    @Published var includeCharacters = false

    let letters = ["A", "B", "C", "D", "E"]
    let numbers = ["1", "2", "3", "4", "5"]
    let specialCharacters = ["!", "@", "#", "$", "%"]

    var password: String = ""

    func generatePassword() -> String {
    
        password = ""
    
        if includeNumbers == false && includeCharacters == false {
            for _ in 1...5 {
                password += letters.randomElement()!
            }
        }
        else if includeNumbers && includeCharacters {
            for _ in 1...3 {
                password += letters.randomElement()!
                password += numbers.randomElement()!
                password += specialCharacters.randomElement()!
            }
        }
        return password
    }
}

View that shows password

enter image description here

import SwiftUI

struct PasswordView: View {

    @ObservedObject var manager = PasswordManager()

    var body: some View {
        Text(manager.generatePassword())
    }
}

Upvotes: 0

Views: 1991

Answers (1)

David Pasztor
David Pasztor

Reputation: 54785

The problem is caused by the fact that your PasswordView creates its own PasswordManager. Instead, you need to inject it from the parent view.

You should never initialise an @ObservedObject inside the View itself, since whenever the @ObservedObject's objectWillChange emits a value, it will reload the view and hence create a new object. You either need to inject the @ObservedObject or declare it as @StateObject if you are targeting iOS 14.

PasswordView needs to have PasswordManager injected from MainView, since they need to use the same instance to have shared state. In MainView, you can use @StateObject if targeting iOS 14, otherwise you should inject PasswordManager even there.

import SwiftUI

struct PasswordView: View {

    @ObservedObject private var manager: PasswordManager

    init(manager: PasswordManager) {
        self.manager = manager
    }

    var body: some View {
        Text(manager.generatePassword())
    }
}

struct MainView: View {

    @StateObject private var manager = PasswordManager()

    var body: some View {
        NavigationView() {
            VStack {
                ZStack {
                    Toggle(isOn: $manager.includeNumbers) {
                        Text("Include numbers")
                            .italic()
                    }
                }
                ZStack {
                    Toggle(isOn: $manager.includeCharacters) {
                        Text("Include special characters")
                            .italic()
                    }
                }
                NavigationLink(destination: PasswordView(manager: manager), label: {
                    Text("Generate")
                })
            }
            .padding(80)
        }
    }
}

Upvotes: 1

Related Questions