I have a published variable isLoggedIn
inside a ObservableObject class as follows:
import Combine
class UserAuth: ObservableObject{
@Published var isLoggedIn: Bool = false
}
I want to update this variable to true in a particular view (LoginView). This variable determines what view I show the user depending if the user has logged in or not:
struct ContentView: View {
@ObservedObject var userAuth = UserAuth()
var body: some View {
Group{
if(userAuth.isLoggedIn){
MainView()
}else{
AccountView()
}
}
}
}
Because userAuth.isLoggedIn is false (I haven't logged in) AccountView is displayed.
AccountView:
struct AccountView: View {
@State private var toggleSheet = false
var body: some View {
VStack {
Spacer()
Button(action: {
self.toggleSheet.toggle()
}){
Text("Toggle Modal")
.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$toggleSheet){
LoginView()
}
Spacer()
}
}
}
Whenever the user presses the button the LoginView Modal is shown:
struct LoginView: View {
var body: some View {
VStack{
Button(action: {
return self.login()
}){
Text("Log in")
.padding()
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(10)
}
}
}
func login(){
// update UserAuth().isLoggedIn to TRUE
}
}
In the LoginView there is a button, the logic I want is for the user to press the button, login()
gets called and inside that function userAuth.isLoggedIn
is set to true
. What would be the best way to implement this ?
I've tried to directly change the value and I get an error along the lines of:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive
Try embedding your code in DispatchQueue.main.async like this:
func login(){
DispatchQueue.main.async {
//update UserAuth().isLoggedIn to TRUE
}
}
One possibility would be to insert the UserAuth
object from the ContentView
into the subviews as an EnvironmentObject
.
struct ContentView: View {
@ObservedObject var userAuth = UserAuth()
var body: some View {
Group {
if userAuth.isLoggedIn {
MainView()
} else {
AccountView()
.environmentObject(userAuth)
}
}
}
}
Now userAuth
is accessible in AccountView
and all its subviews (e.g. LoginView
):
struct LoginView: View {
// Access to the environment object
@EnvironmentObject private var userAuth: UserAuth
var body: some View {
VStack {
Button(action: {
return self.login()
}) {
Text("Log in")
.padding()
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(10)
}
}
}
func login() {
// Update the value on the main thread
DispatchQueue.main.async {
self.userAuth.isLoggedIn = true
}
}
}
It may be necessary to insert the EnvironmentObject into the LoginView
manually. You can do this by accessing it in the AccountView
and inserting it in the LoginView
:
struct AccountView: View {
@State private var toggleSheet = false
// Access the value, that was inserted in `ContentView`
@EnvironmentObject private var userAuth: UserAuth
var body: some View {
VStack {
Spacer()
Button(action: {
self.toggleSheet.toggle()
}){
Text("Toggle Modal")
.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$toggleSheet){
LoginView()
// Insert UserAuth object again
.environmentObject(self.userAuth)
}
Spacer()
}
}
}
Further Reading
WWDC 2019 Video | Hacking with Swift Example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With