12

I'm trying to implement TabView in SwiftUI that has the same color as screen background but also has a shadow above it like in this picture: enter image description here

So far I've been able to properly display color, but I don't know how to add the shadow. This is my code:

struct ContentView: View { init() { let appearance = UITabBarAppearance() appearance.configureWithTransparentBackground() UITabBar.appearance().standardAppearance = appearance } var body: some View { TabView { Text("First View") .tabItem { Image(systemName: "square.and.arrow.down") Text("First") } Text("Second View") .tabItem { Image(systemName: "square.and.arrow.up") Text("Second") } } } } 

Does anyone know how to do this? I would appreciate your help :)

2 Answers 2

17

Short answer

I found a solution. You can create your own shadow image and add it to the UITabBar appearance like this:

// load your custom shadow image let shadowImage: UIImage = ... //you also need to set backgroundImage, without it shadowImage is ignored UITabBar.appearance().backgroundImage = UIImage() UITabBar.appearance().shadowImage = shadowImage 

More detailed answer

Setting backgroundImage

Note that by setting

UITabBar.appearance().backgroundImage = UIImage() 

you make your TabView transparent, so it is not ideal if you have content that can scroll below it. To overcome this, you can set TabView's color.

let appearance = UITabBarAppearance() appearance.configureWithTransparentBackground() appearance.backgroundColor = UIColor.systemGray6 UITabBar.appearance().standardAppearance = appearance 

Setting shadowImage

I wanted to generate shadow image programatically. For that I've created an extension of UIImage. (code taken from here)

extension UIImage { static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage { let gradientLayer = CAGradientLayer() gradientLayer.frame = bounds gradientLayer.colors = colors UIGraphicsBeginImageContext(gradientLayer.bounds.size) gradientLayer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } } 

And finally I styled my TabView like this:

let image = UIImage.gradientImageWithBounds( bounds: CGRect( x: 0, y: 0, width: UIScreen.main.scale, height: 8), colors: [ UIColor.clear.cgColor, UIColor.black.withAlphaComponent(0.1).cgColor ] ) let appearance = UITabBarAppearance() appearance.configureWithTransparentBackground() appearance.backgroundColor = UIColor.systemGray6 appearance.backgroundImage = UIImage() appearance.shadowImage = image UITabBar.appearance().standardAppearance = appearance 

Result

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

3

Your best bet in order to achieve pretty much exactly what you wish is to create a custom TabView.

In fact, in SwiftUI you could use UITabBarAppearance().shadowColor, but that won't do much apart from drawing a 2px line on top of the TabView itself.

Instead, with the below code, you could create a custom TabView and achieve the desired graphical effect.

import SwiftUI enum Tab { case borrow,ret,device,you } struct TabView: View { @Binding var tabIdx: Tab var body: some View { HStack { Group { Spacer() Button (action: { self.tabIdx = .borrow }) { VStack{ Image(systemName: "arrow.down.circle") Text("Borrow") .font(.system(size: 10)) } } .foregroundColor(self.tabIdx == .borrow ? .purple : .secondary) Spacer() Button (action: { self.tabIdx = .ret }) { VStack{ Image(systemName: "arrow.up.circle") Text("Return") .font(.system(size: 10)) } } .foregroundColor(self.tabIdx == .ret ? .purple : .secondary) Spacer() Button (action: { self.tabIdx = .device }) { VStack{ Image(systemName: "safari") Text("Device") .font(.system(size: 10)) } } .foregroundColor(self.tabIdx == .device ? .purple : .secondary) Spacer() Button (action: { self.tabIdx = .you }) { VStack{ Image(systemName: "person.circle") Text("You") .font(.system(size: 10)) } } .foregroundColor(self.tabIdx == .you ? .purple : .secondary) Spacer() } } .padding(.bottom, 30) .padding(.top, 10) .background(Color(red: 0.95, green: 0.95, blue: 0.95)) .font(.system(size: 30)) .frame(height: 80) } } struct ContentView: View { @State var tabIdx: Tab = .borrow var body: some View { NavigationView { VStack(spacing: 20) { Spacer() if tabIdx == .borrow { Text("Borrow") } else if tabIdx == .ret { Text("Return") } else if tabIdx == .device { Text("Device") } else if tabIdx == .you { Text("You") } Spacer(minLength: 0) TabView(tabIdx: self.$tabIdx) .shadow(radius: 10) } .ignoresSafeArea() } } } 

Remember that when you do this, all your tabs are specified as cases within enum Tab {}, and the TabView() contains some Button elements, which will change the tab by using the @State var tabIdx. So you can adjust your actions by modifying the self.tabIdx = <yourtab> statement.

The active color is set with this statement after each button:

.foregroundColor(self.tabIdx == .borrow ? .purple : .secondary) 

Here you can just change .purple to whatever suits you.

You can see that on the ContentView, the if-else if block catches the SubView. Here I have placed some Text() elements like Text("Borrow"), so you can replace them by calling something like BorrowView() or whatever you have.

I have added the shadow when I call the TabView from within the ContentView, but you could even add the .shadow(radius: 10) after the HStack of the TabView itself.

This would be the final output:

                 enter image description here

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.