4

You can find many (UIKit) solutions to set the text color of the status bar for a SwiftUI view. Unfortunately, in my experience, these solutions do not seem to work satisfactorily for TabViews at runtime.

Found Solutions:

SwiftUI: Set Status Bar Color For a Specific View

How can I change the status bar text color per view in SwiftUI?

Using these solutions, you will quickly find that the text color is not always set correctly when trying to switch between tabs.

Unfortunately, Apple currently doesn't seem to have a direct solution for SwiftUI to change the UIStatusBarStyle for each view like it was possible to do with UIKit.

I just can't find a stable way to change the status bar text color for each tab at runtime.

enter image description here

Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49

2 Answers2

2

I read the other Solutions but did not try them. But the following works, and I also think it lets the other Solutions work.

  1. Go to your Info.plist and set UIViewControllerBasedStatusBarAppearance to NO
  2. From here on, I would assume that the solutions mentioned by you, would also work.

If you don't mind having a deprecation warning in your Code this would also be an option:

TabView {
    Text("1")
        .tabItem { Text("1") }
        .onAppear {
            UIApplication.shared.setStatusBarStyle(.lightContent, animated: false)
        }

    Text("2")
        .tabItem { Text("2") }
        .onAppear {
            UIApplication.shared.setStatusBarStyle(.darkContent, animated: true)
        }
}

But setStatusBarStyle is deprecated and therefore it might not be the best option.

KonDeichmann
  • 878
  • 1
  • 8
  • 21
2

I could help myself now. I found a solution that takes an approach that works with TabViews as well based on: https://github.com/xavierdonnellon/swiftui-statusbarstyle

My full Code Example where the states of the previous status bar styles are saved and restored upon disposal (it is important to keep an eye on the status bar style hierarchy during runtime):

ExampleApp.swift

import SwiftUI

@main
struct ExampleApp: App {
    
    init() {
        //navigation bar
        let coloredNavAppearance = UINavigationBarAppearance()
        coloredNavAppearance.configureWithTransparentBackground()
        coloredNavAppearance.backgroundColor = .clear
        coloredNavAppearance.backgroundEffect = nil
        coloredNavAppearance.backgroundImage = UIImage()
        coloredNavAppearance.shadowImage = UIImage()
        coloredNavAppearance.shadowColor = .clear
        coloredNavAppearance.titleTextAttributes = [
            .foregroundColor: UIColor.black
        ]
        coloredNavAppearance.largeTitleTextAttributes = [
            .foregroundColor: UIColor.black
        ]
        
        UINavigationBar.appearance().standardAppearance = coloredNavAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = coloredNavAppearance
        UINavigationBar.appearance().compactAppearance = coloredNavAppearance
        
    }
    
    var body: some Scene {
        WindowGroup {
            RootView(content: {
                ContentView()
            })
        }
    }
}

UIApplication+StatusBarStyle.swift

extension UIApplication {
    static var hostingController: HostingController<AnyView>? = nil
    
    static var statusBarStyleHierarchy: [UIStatusBarStyle] = []
    static var statusBarStyle: UIStatusBarStyle = .darkContent
    
    ///Sets the App to start at rootView
    func setHostingController(rootView: AnyView) {
        let hostingController = HostingController(rootView: AnyView(rootView))
        windows.first?.rootViewController = hostingController
        UIApplication.hostingController = hostingController
    }
    
    static func setStatusBarStyle(_ style: UIStatusBarStyle) {
        statusBarStyle = style
        hostingController?.setNeedsStatusBarAppearanceUpdate()
    }
}

class HostingController<Content: View>: UIHostingController<Content> {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return UIApplication.statusBarStyle
    }
}

///By wrapping views in a RootView, they will become the app's main / primary view. This will enable setting the statusBarStyle.
struct RootView<Content: View> : View {
    var content: Content
    
    init(@ViewBuilder content: () -> (Content)) {
        self.content = content()
    }
    
    var body:some View {
        EmptyView()
            .onAppear {
                UIApplication.shared.setHostingController(rootView: AnyView(content))
            }
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    
    @State var tabRoute = TabRoute.green
    
    enum TabRoute: String, Identifiable {
        
        var id: String {
            self.rawValue
        }
        
        case green, black, blue, purple
        
        static var all: [TabRoute] {
            [.green, .black, .blue, .purple]
        }
        
        var bgColor: Color {
            switch self {
            case .green:
                return .green
                
            case .black:
                return .black
                
            case .blue:
                return .blue
                
            case .purple:
                return .purple
                
            }
        }
        
        var statusBarStyle: UIStatusBarStyle {
            switch self {
            case .black:
                return .lightContent
                
            default:
                return .darkContent
                
            }
        }
    }
    
    
    var body: some View {
        
        TabView(selection: $tabRoute) {
            
            ForEach(TabRoute.all) { route in
                NavigationView {
                    ViewExample(isPresented: false, title: route.rawValue, bgColor: route.bgColor, statusBarStyle: route.statusBarStyle)
                        .navigationTitle(route.rawValue)
                        .navigationBarTitleDisplayMode(.inline)
                }
                .navigationViewStyle(StackNavigationViewStyle())
                .tabItem {
                    Text(route.rawValue)
                }
                .tag(TabRoute(rawValue: route.rawValue))
            }
            
        }
        .edgesIgnoringSafeArea(.all)
    }
    
}

struct ViewExample: View {
    
    @Environment(\.presentationMode) var presentationMode

    @State private var sheetIsPresented = false
    
    @State  var isPresented: Bool
    
    var title: String
    
    var bgColor: Color
    
    @State var statusBarStyle: UIStatusBarStyle
    
    var body: some View {
        ZStack {
            bgColor
        }
        .onAppear {
            UIApplication.statusBarStyleHierarchy.append(statusBarStyle)
            UIApplication.setStatusBarStyle(statusBarStyle)
        }
        .onDisappear {
            guard UIApplication.statusBarStyleHierarchy.count > 1 else { return }
            let style = UIApplication.statusBarStyleHierarchy[UIApplication.statusBarStyleHierarchy.count - 1]
            UIApplication.statusBarStyleHierarchy.removeLast()
            UIApplication.setStatusBarStyle(style)
        }
        .edgesIgnoringSafeArea(.all)
    }
}
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
  • Thanks for sharing this! It's a great drop-in solution. Works perfectly. I still hope for a native SwiftUI solution in iOS 17 – codingFriend1 May 17 '23 at 13:49