Unexpected Additional Appearance (onAppear, task, onDisappear) Calls Made When TabView or NavigationView/NavigationStack Removed From View Tree

Originator:joshua.shroyer
Number:rdar://FB11721387 Date Originated:October 26th, 2022
Status:Open Resolved:
Product:SwiftUI Product Version:iOS 16.1 and under
Classification:Incorrect/Unexpected Behavior Reproducible:Always
 
# Appearance Issues
When a TabView or NavigationView/NavigationStack is removed from the view tree, there can be unexpected additional calls to appearance
modifiers like `onAppear`, `task`, and `onDisappear` for some of the views.

I reproduced this issue on the current iOS and Xcode versions - iOS 16.1 and Xcode 14.1.

## TabView Appearance Issues
For TabViews, you must select at least one other tab before changing state to another case that doesn't include the TabView.
When removed from the view tree, all already-viewed, non-currently-viewed tab views will run through all appearance calls again, even
when the state binding that controls the selected tab does not change.

### Reproducible Steps
1. Have an application where the root of the app has some conditional for logged-in and logged-out states, and the logged-in view has a TabView as the root with at least two tabs
2. Run the app and "Log In"
3. Select another non-root tab view
4. Log out to see the Login view again
5. Observe the already-viewed, non-currently-selected tab views run through their appearance calls again

## NavigationView/NavigationStack Appearance Issues
For NavigationView and NavigationStack (potentially NavigationSplitView as well, but I didn't test this), you must push
at least one other view onto the stack before changing state to another case that doesn't include the Navigation type.
When removed from the view tree, the root view will run through all appearance calls again.

### Reproducible Steps
1. Have an application where the root of the app has some conditional for logged-in and logged-out states, and the logged-in view has a NavigationView/NavigationStack as the root and can push a view onto the stack to log out
2. Run the app and "Log In"
3. Push a view onto the stack
4. Log out to see the Login view again
5. Observe the Navigation Root view (Not the Navigation View itself) runs through the appearance calls again

## Typical Example
Assume you have an app that has a Login view, and once logged in, you have a tabbed or some other typical navigational structure
as the root of your authenticated app flow. Following any of the above situations will result in additional appearance
callbacks for the affected views when logging out of the application.

### Sample Project Pseudocode
```swift
    struct ContentView: View {
        @StateObject
        private var viewModel = ViewModel()
        
        var body: some View {
            switch viewModel.state {
                case .loading:
                    SplashView()
                case .loggedOut:
                    LoginView()
                case .loggedIn:
                    Authenticated() // Some TabView or other NavigationView/NavigationStack Root
            }
        }
    }
```

Comments

Associated Developer Forum Post

Associated Developer Forum post - https://developer.apple.com/forums/thread/718738

By joshua.shroyer at Nov. 4, 2022, 5:44 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!