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:
and, auto-adjusted to TabBar height when rotated:
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:
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
Protocol Doesn't Conform to Itself
Wait Until Swift For Loop With Asynchronous Network Requests Finishes Executing
How Does String.Index Work in Swift
Overriding Superclass Property With Different Type in Swift
Multiple Sheet(Ispresented:) Doesn't Work in Swiftui
When Are Argument Labels Required in Swift
How to Compare Two Dictionaries in Swift
Swiftui Iterating Through Dictionary With Foreach
Transparent Background For Modally Presented Viewcontroller
Print Without Newline in Swift
Using a Dispatch_Once Singleton Model in Swift
Getting a "This Application Is Modifying the Autolayout Engine from a Background Thread" Error
Setting Device Orientation in Swift Ios
What's the Difference Between "As", "As!", and "As"
Swift Dictionary Get Key For Value