1

One of my tabs shows a list of items. I wish to add a gradient above the tab view to show that there are more items in the list and user can scroll. Please refer to attached screenshot. I am hiding the scroll indicator.

The way I am trying to achieve this is by finding the height of the tab bar and adding that as the bottom padding for the gradient. I found another post on SO which helped me getting the accurate height of the tab view. However even with that, the gradient isn't getting placed at the right location.

How can I move the gradient to the position highlighted in purple in the attached screenshot?

Code:

import SwiftUI
import Foundation

struct ContentView: View {
    @State var activeTab = 1
    @State var tabBarHeight: CGFloat = 0.0
    
    var body: some View {
        TabView(selection: $activeTab) {
            Group {
                Text("profile")
                    .tabItem {
                        Label("profile", systemImage: "square.text.square.fill")
                    }
                    .tag(0)
                
                Text("search")
                    .tabItem {
                        Label("search", systemImage: "magnifyingglass")
                    }
                    .background(TabBarAccessor { tabBar in
                        tabBarHeight = tabBar.bounds.height
                    })
                    .tag(1)
            }
        }
        .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
        .onAppear {
            let appearance = UITabBarAppearance()
            UITabBar.appearance().scrollEdgeAppearance = appearance
        }
        .overlay {
            LinearGradient(gradient:
                            Gradient(
                                colors: [Color.gray.opacity(0.6), Color.gray.opacity(0)]),
                           startPoint: .top, endPoint: .bottom
            )
            // .frame(height: 20.0).offset(y:tabBarHeight)
            .frame(height: 20.0).padding(.bottom, -tabBarHeight)
        }
    }
}

// Helper bridge to UIViewController to access enclosing UITabBarController
// and thus its UITabBar
// Found from - https://stackoverflow.com/a/59972635/697033
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)
            }
        }
    }
}

Screenshot:

enter image description here

tech_human
  • 6,592
  • 16
  • 65
  • 107

1 Answers1

1

You should set the alignment of the overlay to be .bottom, since you want the overlay to appear at the bottom. This will put the gradient at the very bottom of the TabView.

Now we just need to figure out how much the gradient should be shifted upwards. It turns out that UITabBar.bounds includes the safe area, but an overlay will avoid the safe area. Therefore, the amount to shift is:

amountToShift = tabBar.bounds.height - tabBar.safeAreaInsets.bottom

Then you can shift it upwards by using offset:

.frame(height: 20.0)
.offset(y: -amountToShift)

This will put the gradient just above the tab bar.

enter image description here

It looks like the gradient is not actually touching the tab bar, but if you check with the UI inspector, the frames of the tab bar and the gradient are indeed touching, right next to each other.

If you instead want the gradient to cover up the top part of the tab bar, reduce the shift by the height of the gradient:

.frame(height: 20.0)
.offset(y: -(amountToShift - 20.0))

enter image description here

Sweeper
  • 213,210
  • 22
  • 193
  • 313