Programmatically detect Tab Bar or TabView height in SwiftUI

后端 未结 2 964
温柔的废话
温柔的废话 2020-11-29 12:03

I have a SwiftUI app that will have a floating podcast player, similar to the Apple Music player that sits just above the Tab Bar and persists across all tabs and views whil

相关标签:
2条回答
  • 2020-11-29 12:27

    As bridge to UIKit is officially allowed and documented, it is possible to read needed information from there when needed.

    Here is possible approach to read tab bar height directly from UITabBar

    // Helper bridge to UIViewController to access enclosing UITabBarController
    // and thus its UITabBar
    struct TabBarAccessor: UIViewControllerRepresentable {
        var callback: (UITabBar) -> Void
        private let proxyController = ViewController()
    
        func makeUIViewController(context: UIViewControllerRepresentableContext<TabBarAccessor>) ->
                                  UIViewController {
            proxyController.callback = callback
            return proxyController
        }
    
        func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TabBarAccessor>) {
        }
    
        typealias UIViewControllerType = UIViewController
    
        private class ViewController: UIViewController {
            var callback: (UITabBar) -> Void = { _ in }
    
            override func viewWillAppear(_ animated: Bool) {
                super.viewWillAppear(animated)
                if let tabBar = self.tabBarController {
                    self.callback(tabBar.tabBar)
                }
            }
        }
    }
    
    // Demo SwiftUI view of usage
    struct TestTabBar: View {
        var body: some View {
            TabView {
                Text("First View")
                    .background(TabBarAccessor { tabBar in
                        print(">> TabBar height: \(tabBar.bounds.height)")
                        // !! use as needed, in calculations, @State, etc.
                    })
                    .tabItem { Image(systemName: "1.circle") }
                    .tag(0)
                Text("Second View")
                    .tabItem { Image(systemName: "2.circle") }
                    .tag(1)
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 12:28

    It seems, that you need to know the maximal size of player (size of space above tab bar), not height of tab bar self.

    Using GeometryReader and PreferenceKey are the handy tool for that

    import Combine
    
    struct Size: PreferenceKey {
    
        typealias Value = [CGRect]
        static var defaultValue: [CGRect] = []
        static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) {
            value.append(contentsOf: nextValue())
        }
    }
    
    struct HomeView: View {
        let txt: String
        var body: some View {
            GeometryReader { proxy in
                Text(self.txt).preference(key: Size.self, value: [proxy.frame(in: CoordinateSpace.global)])
            }
        }
    }
    
    
    struct ContentView: View {
        @State var playerFrame = CGRect.zero
        var body: some View {
    
            TabView {
                HomeView(txt: "Hello").tabItem {
                    Image(systemName: "house")
                    Text("A")
                }.border(Color.green).tag(1)
    
                HomeView(txt: "World!").tabItem {
                    Image(systemName: "house")
                    Text("B")
                }.border(Color.red).tag(2)
    
                HomeView(txt: "Bla bla").tabItem {
                    Image(systemName: "house")
                    Text("C")
                }.border(Color.blue).tag(3)
            }
            .onPreferenceChange(Size.self, perform: { (v) in
                self.playerFrame = v.last ?? .zero
                print(self.playerFrame)
            })
                .overlay(
                    Color.yellow.opacity(0.2)
                .frame(width: playerFrame.width, height: playerFrame.height)
                .position(x: playerFrame.width / 2, y: playerFrame.height / 2)
            )
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    In the example I reduce the size with .padding() on yellow transparent rectangle, to be sure no part could be hidden (out of screen)

    Even the height of tab bar could be calculated, if necessary, but I am not able to imagine for what.

    0 讨论(0)
提交回复
热议问题