0

I'm trying an authenticated flow in my app.

The app has a LoginScreenView that is going to appear first on the app. When logging in, we go to a TabView consisting of two Views, FirstView and SecondView. SecondView has a logout button, that returns the user to the login screen. On the FirstView we have a network call that is being performed always onAppear.

When the user logs out, we are on the SecondView, on the second tab; however both onAppear and onDisappear functions of the FirstView are being executed, even though FirstView is not on display.

This provokes in our use case to trigger the network call in the onAppear method of FirstView that will fail because we are not authenticated.

Here is my code.

This is LoginScreen:

struct LoginScreenView: View { @EnvironmentObject private var authManager: AuthManager var body: some View { Button("Login") { authManager.isLoggedIn = true } } } 

This is FirstView:

struct FirstView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") } .tabItem { Label("Hello", systemImage: "globe") } .onAppear { print("First ✅") Task { try await loadSomeStuffFromNetwork() } } .onDisappear { print("First ❌") } } } 

This is SecondView:

struct SecondView: View { @EnvironmentObject private var authManager: AuthManager var body: some View { VStack { Image(systemName: "flag.fill") .imageScale(.large) .foregroundColor(.accentColor) Text("Goodbye, world!") Button("Logout") { authManager.isLoggedIn = false } } .tabItem { Label("Goodbye", systemImage: "flag") } .onAppear { print("Second ✅") } .onDisappear { print("Second ❌") } } } 

And this is the root view of the app:

struct ContentView: View { @StateObject private var authManager = AuthManager() var body: some View { TabView { Group { if isLoggedIn { FirstView() SecondView() } else { LoginScreenView() } } } .environmentObject(authManager) } } 

It's using the AuthManager declared in here:

final class AuthManager: ObservableObject { @Published var isLoggedIn: Bool = false } 

As you can see in the code, we are trying to change the displayed views depending on whether we are logged in or not, and we use an if-else for that.

Do you think this code is OK? Do you feel that this might be a bug on the Apple side? I feel like this should work out of the spot, but I don't know if I'm missing anything.

I saw this question from 2022, but it seems it never got resolved (apart from the workaround of adding a condition of being logged in to perform the network call).

Also, saw this other one, where they propose to use a @State variable to make the view only perform one network call instead of everytime it appears (or SwiftUI decides it to appear).

Are we OK on this train of thought? Should we try to build our network call in some other way?

4
  • Don't use @State to track logged in status. Use an observable model object that you inject into the environment Commented Apr 20, 2023 at 20:40
  • Yeah, in our application we have it as an environment object. Let me update it to that. Commented Apr 20, 2023 at 21:02
  • I would add a guard to check if you are logged in to the network code. Commented Apr 20, 2023 at 22:24
  • That was one way to do it. However, we would need to add similar code in every view that is going to appear in the TabView, which would add too much code everywhere. Commented Apr 24, 2023 at 14:49

1 Answer 1

0

TabView SwiftUI has a bug and it makes each tab render many times. To fix it you can use LazyView.

struct LazyView<Content: View>: View { let build: () -> Content init(_ build: @autoclosure @escaping () -> Content) { self.build = build } var body: Content { build() } } 

Use:

func tabViewRegion() -> some View { TabView(selection: $viewModel.tabViewCurrentIndex) { LazyView(YourView1()) .tag(0) LazyView(YourView2()) .tag(1) } } 

I hope Apple will fix it soon as soon.

Sign up to request clarification or add additional context in comments.

4 Comments

It seems like a lightweight change, let me try this and see if it works as expected.
Sorry for the late response, I'm afraid this doesn't work still, it still calls the onAppear method every now and then.
I personally think u just change the value of the variable binding to the tab view. View re-render.
We thought that should work too, but it seems the Views inside the TabView are still cached in some way, even if we remove the TabView from the hierarchy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.