Programmatically Detect Tab Bar or Tabview Height in Swiftui

Programmatically detect Tab Bar or TabView height in SwiftUI

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)
}
}
}

backup

How to calculate Tab Bar height in SwiftUI?

I'm just starting to look at SwiftUI, but... I'm under the impression GeometryReader should handle this for you.

Give this a try:

struct Tab1View: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Top == 1/3 height")
.frame(width: geometry.size.width, height: geometry.size.height / 3.0, alignment: .center)
.background(Color.init(red: 0.1, green: 0.1, blue: 0.5))
.foregroundColor(Color.white)
.border(Color.yellow)
Text("Bottom == 2/3 height")
.frame(width: geometry.size.width, height: geometry.size.height * 2.0 / 3.0, alignment: .center)
.background(Color.init(red: 0.5, green: 0.1, blue: 0.1))
.foregroundColor(Color.white)
.border(Color.yellow)
}
.frame(width: geometry.size.width,
height: geometry.size.height,
alignment: .topLeading)
}
}

}
struct Tab2View: View {
var body: some View {
Color.blue
}
}

struct MyTabView: View {
var body: some View {

TabView {
//Text("The content of the first view")
Tab1View()
.tabItem {
Image(systemName: "phone.fill")
Text("First Tab")
}
//Text("The content of the second view")
Tab2View()
.tabItem {
Image(systemName: "tv.fill")
Text("Second Tab")
}
}
}
}

struct MyTabView_Previews: PreviewProvider {
static var previews: some View {
MyTabView()
}
}

Result:

Sample Image

and, auto-adjusted to TabBar height when rotated:

Sample Image


Edit

After some more investigation, it looks like ScrollView can size itself to fill available space, so we only need to set the height on the top view.

Here's a modified example:

struct MyItemView: View {
var itemDesc = "Testing"
var geoWidth: CGFloat = 100
var body: some View {
Text("\(self.itemDesc)")
.foregroundColor(Color.white)
.padding(16.0)
.frame(width: self.geoWidth, height: nil, alignment: .leading)
}
}

struct Tab1View: View {
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0.0) {
Text("Top == 1/3 height")
.frame(width: geometry.size.width, height: geometry.size.height * 1.0 / 3.0, alignment: .center)
.background(Color.init(red: 0.1, green: 0.1, blue: 0.5))
.foregroundColor(Color.white)
.border(Color.yellow)

ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 0) {
ForEach((1...20), id: \.self) {
MyItemView(itemDesc: "This is item \($0)", geoWidth: geometry.size.width)
.background(Color.init(red: 0.5, green: 0.1, blue: 0.1))
.border(Color.yellow)
}
}
}
.frame(width: geometry.size.width, height: nil, alignment: .leading)
}
}
}
}
struct Tab2View: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Top == 1/3 height")
.frame(width: geometry.size.width, height: geometry.size.height * 1.0 / 3.0, alignment: .center)
.background(Color.init(red: 0.1, green: 0.1, blue: 0.7))
.foregroundColor(Color.white)
.border(Color.yellow)
Text("Bottom == 2/3 height")
.frame(width: geometry.size.width, height: geometry.size.height * 2.0 / 3.0, alignment: .center)
.background(Color.init(red: 0.2, green: 0.6, blue: 0.1))
.foregroundColor(Color.white)
.border(Color.yellow)
}
}
}
}
struct MyTabView: View {
var body: some View {
TabView {
Tab1View()
.tabItem {
Image(systemName: "phone.fill")
Text("First Tab")
}
Tab2View()
.tabItem {
Image(systemName: "tv.fill")
Text("Second Tab")
}
}
}
}

struct MyTabView_Previews: PreviewProvider {
static var previews: some View {
MyTabView()
}
}

and, new result:

Sample Image

Sample Image

SwiftUI TabView covers content view

The contentView contains a List, and it fills all available space, moving buttons that are located below the List under the TabView. Adding Space after the contentView solves the issue.

var body: some View {
TabView {
VStack {
self.contentView
Spacer()
}
.tabItem {
Image(systemName: "chart.bar.fill")
Text("Chart")
}
Text("Screen 2")
.tabItem {
Image(systemName: "person.3.fill")
Text("Leaderboard")
}
Text("Screen 3")
.tabItem {
Image(systemName: "ellipsis")
Text("More")
}
}
}

Thank you @Asperi for pointing in right direction, and checking the children of the contentView.

SwiftUI - TabView overlaps ove views

First, I would wrap your custom tab bar as follows:

VStack {
Spacer()
CustomTabBar(animation: animation, currentTab: $currentTab)
}

Then on the TabView itself, you will likely need to pad the bottom to ensure it provides room for the custom view.

TabView(selection: $currentTab) {
// Your views here
}.padding(.bottom, <<Height of the custom view, plus spacing here>>)

calculating height of UITabBar

It is 320 x 49.

If you want to test, open Interface Builder, add a UITabBar, go into the ruler, you will see it

UITabBar is inherited from UIVIew so you can use the frame.size.height to get the height

SwiftUI - TabView Overlay with custom view

You could overlay the TabView like so:

TabView {
...
}
.overlay(
VStack {
Spacer()

//Your View
.frame(height: /*Height of the TabBar*/)
}
.edgesIgnoringSafeArea(.all)
)

Now it comes down to finding out the height of the TabBar. Since its dynamic (different iPhone screen sizes) One way would be like so:

@State private var tabBarHeight: CGFloat = .zero
...

TabView {
//NavigationView
.background(
TabBarAccessor { tabBar in tabBarHeight = tabBar.bounds.height }
)
}
.overlay(
VStack {
Spacer()

//Your View
.frame(height: tabBarHeight)
}
.edgesIgnoringSafeArea(.all)
)

An example solution

Everything together - your code from your first answer modified.

Code:

struct ContentView: View {

@State private var tabBarHeight: CGFloat = .zero
@State var isSelecting : Bool = false

var body: some View {
TabView {
NavigationView {
Text("First Nav Page")
}
.tabItem {
Image(systemName: "house")
Text("Home")

}.tag(0)
.background(
TabBarAccessor { tabBar in tabBarHeight = tabBar.bounds.height }
)

NavigationView {
Text("Second Nav Page")
}
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(1)

Text("No Nav Page")
.tabItem{
Image(systemName: "plus")
Text("Test")
}.tag(2)
}
.overlay(
VStack {
Spacer()
Rectangle()
.foregroundColor(.green)
.frame(height: tabBarHeight)
}
.edgesIgnoringSafeArea(.all)
)
}
}

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)
}
}
}
}

Note

The TabBarAccessor code I have found from: Here



Related Topics



Leave a reply



Submit