iOS 15 SwiftUI TabView tab bar appearance doesn't update between views

Viewed 4539

iOS 15 sets the TabView's appearance depending on the loaded view's scroll position. However, this doesn't seem to update between views switched in the tab bar. How can I fix this so that the appearance updates properly?

  1. Opening a tabbed view without scrolling content ("no-scrolling view") uses a transparent background for the tab bar.

  2. After navigating to a tabbed view with scrolling content ("scrolling view"), a translucent background is used.

  3. However, when coming back to the "no-scrolling view", the translucent background still remains instead of being replaced with a transparent background.

I did notice that the appearance updates properly when I open the Control Center or App Switcher and come back.

no-scrolling view transparent scrolling view translucent no-scrolling view translucent

Reproduction:

enum Tab {
    case scroll
    case noScroll
}

struct ContentView: View {
    @State var selection: Tab = .noScroll
    
    var body: some View {
        TabView(selection: $selection) {     
            Text("Should have a transparent tab bar")
                .tabItem{ Label("No-scroll", systemImage: "xmark.circle") }
                .tag(Tab.noScroll)
            
            ScrollView {
                VStack(spacing: 10) {
                    ForEach(0..<100) {_ in
                        Text("Should have a translucent tab bar")
                    }
                }
            }
            .tabItem { Label("Scroll", systemImage: "circle") }
            .tag(Tab.scroll)
        }
    }
}

4 Answers
.onAppear {
    if #available(iOS 15.0, *) {
        let appearance = UITabBarAppearance()
        UITabBar.appearance().scrollEdgeAppearance = appearance
    }
}

it's pretty much the same logic as @cole 's answer but here is my solution:

.introspectTabBarController(customize: { controller in

            let appearance = controller.tabBar.standardAppearance
            appearance.configureWithDefaultBackground()
            appearance.backgroundColor = .red
            if #available(iOS 15.0, *) {
                controller.tabBar.scrollEdgeAppearance = appearance
            } else {
                controller.tabBar.standardAppearance = appearance
            }
        })

to use introspectTabBarController SwiftUI-Introspect

I had a similar issue here, solved by using this for iOS 15

@available(iOS 15.0, *)
@NSCopying open var scrollEdgeAppearance: UITabBarAppearance?

Describes the appearance attributes for the tabBar to use when an observable scroll view is scrolled to the bottom. If not set, standardAppearance will be used instead.

//@available(iOS 15.0, *)
//@NSCopying open var scrollEdgeAppearance: UITabBarAppearance?

let appearance: UITabBarAppearance = UITabBarAppearance()
init() {
    UITabBar.appearance().scrollEdgeAppearance = appearance
}

After update to XCode 13 & iOS 15 I also faced some TabView issues with bar background color and items text & icons color for different states. The way I fixed it:

if #available(iOS 15, *) {
   let tabBarAppearance = UITabBarAppearance()
   tabBarAppearance.backgroundColor = backgroundColor
   tabBarAppearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: selectedItemTextColor]
   tabBarAppearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: unselectedItemTextColor]
   tabBar.standardAppearance = tabBarAppearance
   tabBar.scrollEdgeAppearance = tabBarAppearance
} else {
   UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor: selectedItemTextColor], for: .selected)
   UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor: unselectedItemTextColor], for: .normal)
   tabBar.barTintColor = backgroundColor
 }
Related