I have a view with list of toggles with languages that should change current app language after the restart. I understand how to create this feature with buttons, but it doesn't work the same way with toggles. I've tried to use on change, but it's not allowing to turn off the toggle after the second tap. How to do that properly?
struct Languages: View {
@State private var currentLanguage = true
@State private var currentLanguageEnglish = true
@State private var currentLanguageRussian = false
@State private var showingAlert = false
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
@State var currentSysLanguage = UserDefaults.standard.string(forKey: "language")
var body: some View {
VStack(alignment: .leading) {
DoubleTextView(topText: LocalizedStringKey("languages"), buttomText: "", topTextSize: 24, buttomTextSize: 0)
// Works just right, wrong design.
Button("English", action: {
currentSysLanguage = "en"
UserDefaults.standard.set(currentSysLanguage, forKey: "language")
showingAlert.toggle()
})
.alert("Restart your app", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
Button("French", action: {
currentSysLanguage = "fr"
UserDefaults.standard.set(currentSysLanguage, forKey: "language")
showingAlert.toggle()
})
.alert("Restart your app", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
// Does not work, this is how it should be.
ZStack(alignment: .top) {
Toggle(isOn: $currentLanguageEnglish) {
Text("English")
.font(.custom("Manrope-Bold", size: 16))
.foregroundColor(.white)
}
.padding(.horizontal)
.tint(Color("active"))
}
.padding(.vertical, 30.0)
.background(Rectangle()
.fill(Color("navigation"))
.frame(height: 50)
.cornerRadius(8))
ZStack(alignment: .top) {
Toggle(isOn: $currentLanguageRussian) {
Text("French")
.font(.custom("Manrope-Bold", size: 16))
.foregroundColor(.white)
}
.padding(.horizontal)
.tint(Color("active"))
}
.background(Rectangle()
.fill(Color("navigation"))
.frame(height: 50)
.cornerRadius(8))
Spacer()
}
.background(
Image("background2")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
)
.onChange(of: currentLanguageEnglish, perform: { newValue in
currentLanguageRussian = true
})
.onChange(of: currentLanguageRussian, perform: { newValue in
currentLanguageEnglish = true
})
.padding(.top, 40)
.overlay {
HStack {
Spacer()
Button {
self.mode.wrappedValue.dismiss()
} label: {
HStack {
Image(systemName: "arrow.left")
.foregroundColor(.white)
.font(.system(size: 24))
Spacer()
}
}
}
.padding(.horizontal, 3.0)
.frame(maxHeight: .infinity, alignment: .top)
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.padding(.horizontal, 10.0)
}
}
CodePudding user response:
There are many ways you can accomplish something similar, one is to make the toggle bind to a custom binding object:
Toggle(isOn: .init(get: {
currentSysLanguage == "en"
}, set: { isOn in
currentSysLanguage = isOn ? "en" : defaultLanguage
})) {
Text("English")
}
Here the getter makes the toggle listen to currentSysLanguage == "en"
. It will be on/off based on if the statement evaluates true. Whereas the setter will be triggered when you manually toggle it, and set currentSysLanguage
between "en"
and defaultLanguage
.
Note I have added the defaultLanguage
to represent the state when all toggles are off.
You can then create as many toggles as needed with the same code by changing the language code it is comparing to.
Near the end, you would add a .onChange
modifier to listen to currentSysLanguage
to send the changes to UserDefaults
:
.onChange(of: currentSysLanguage, perform: { newValue in
UserDefaults.standard.set(newValue, forKey: "language")
})