Reputation: 57
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
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
import SwiftUI
struct PasswordView: View {
@ObservedObject var manager = PasswordManager()
var body: some View {
Text(manager.generatePassword())
}
}
Upvotes: 0
Views: 1991
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