简体   繁体   中英

Why onAppear called again after onDisappear while switching tab in TabView in SwiftUI?

I am calling API when tab item is appeared if there is any changes. Why onAppear called after called onDisappear?

Here is the simple example:

struct ContentView: View {
    var body: some View {
        TabView {
            NavigationView {
                Text("Home")
                    .navigationTitle("Home")
                    .onAppear {
                        print("Home appeared")
                    }
                    .onDisappear {
                        print("Home disappeared")
                    }
            }
            .tabItem {
                Image(systemName: "house")
                Text("Home")
            }.tag(0)
        
            NavigationView {
                Text("Account")
                    .navigationTitle("Account")
                    .onAppear {
                        print("Account appeared")
                    }
                    .onDisappear {
                        print("Account disappeared")
                    }
            }
            .tabItem {
                Image(systemName: "gear")
                Text("Account")
            }.tag(1)
        }
    }
}    

Just run above code and we will see onAppear after onDisappear.

Home appeared
---After switch tab to Account---
Home disappeared
Account appeared
Home appeared

Is there any solution to avoid this?

It's very annoying bug, imagine this scenario: Home view onAppear method contains a timer which is fetching data repeatedly. Timer is triggered invisibly by switching to the Account view.

Workaround:

  1. Create a standalone view for every embedded NavigationView content
  2. Pass the current selection value on to standalone view as @Binding parameter

Eg:

struct ContentView: View {
    @State var selected: MenuItem = .HOME

    var body: some View {
        return TabView(selection: $selected) {
            HomeView(selectedMenuItem: $selected)
                .navigationViewStyle(StackNavigationViewStyle())
                .tabItem {
                    VStack {
                        Image(systemName: "house")
                        Text("Home")
                    }
                }
                .tag(MenuItem.HOME)
            
            AccountView(selectedMenuItem: $selected)
                .navigationViewStyle(StackNavigationViewStyle())
                .tabItem {
                    VStack {
                        Image(systemName: "gear")
                        Text("Account")
                    }
                }
                .tag(MenuItem.ACCOUNT)
        }
    }
}

enum MenuItem: Int, Codable {
    case HOME
    case ACCOUNT
}

HomeView:

struct HomeView: View {

    @Binding var selectedMenuItem: MenuItem

    var body: some View {
        return Text("Home")
            .onAppear(perform: {
                if MenuItem.HOME == selectedMenuItem {
                    print("-> HomeView")
                }
            })
    }
}

AccountView:

struct AccountView: View {

    @Binding var selectedMenuItem: MenuItem

    var body: some View {
        return Text("Account")
            .onAppear(perform: {
                if MenuItem.ACCOUNT == selectedMenuItem {
                    print("-> AccountView")
                }
            })
    }
}

I'm not sure why you are seeing that behaviour in your App. But I can explain why I was seeing it in my App.

I had a very similar setup to you and was seeing the same behaviour running an iOS13 App on iOS14 beta. In my Home screen I had a custom Tab Bar that would animate in and out when a detail screen was displayed. The code for triggering the hiding of the Tab Bar was done in the.onAppear of the Detail screen. This was triggering the Home screen to be redrawn and the.onAppear to be called. I removed the animation and found a much better set up due to this bug and the Home screen.onAppear stopped being called.

So if you have something in your Account Screen.onAppear that has a visual effect on the Home Screen then try commenting it out and seeing if it fixes the issue.

Good Luck.

To whom it may help.

Because this behaviour I only could reproduce on iOS 14+, I end up using https://github.com/NicholasBellucci/StatefulTabView (which properly only get called when showed; but don't know if it's a bug or not , but it works with version 0.1.8) and TabView on iOS 13+.

I have been trying to understand this behavior for a number of days now. If you are working with a TabView, all of your onAppears() / onDisapear() will fire immediately on app init and never again. Which actually makes since I guess?

This was my solution to fix this:

import SwiftUI

enum TabItems {
    case one, two
}

struct ContentView: View {
    
    @State private var selection: TabItems = .one
    
    var body: some View {
        TabView(selection: $selection) {
            ViewOne(isSelected: $selection)
                .tabBarItem(tab: .one, selection: $selection)
            
            ViewTwo(isSelected: $selection)
                .tabBarItem(tab: .two, selection: $selection)
        }
    }
}

struct ViewOne: View {
    
    @Binding var isSelected: TabItems
    
    var body: some View {
        Text("View One")
            .onChange(of: isSelected) { _ in
                if isSelected == .one {
                    // Do something
                }
            }
    }
}

struct ViewTwo: View {
    
    @Binding var isSelected: TabItems
    
    var body: some View {
        Text("View Two")
            .onChange(of: isSelected) { _ in
                if isSelected == .two {
                    // Do something
                }
            }
    }
}

View Modifier for custom TabView

struct TabBarItemsPreferenceKey: PreferenceKey {
    
    static var defaultValue: [TabBarItem] = []
    
    static func reduce(value: inout [TabBarItem], nextValue: () -> [TabBarItem]) {
        value += nextValue()
    }
}


struct TabBarItemViewModifer: ViewModifier {
    
    let tab: TabBarItem
    @Binding var selection: TabBarItem
    
    func body(content: Content) -> some View {
        content
            .opacity(selection == tab ? 1.0 : 0.0)
            .preference(key: TabBarItemsPreferenceKey.self, value: [tab])
    }
}


extension View {
    func tabBarItem(tab: TabBarItem, selection: Binding<TabBarItem>) -> some View {
        modifier(TabBarItemViewModifer(tab: tab, selection: selection))
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM