SwiftUI changing navigation bar background color for inline navigationBarTitleDisplayMode - swiftui

I just started coding in SwiftUI and came across a problem. I need to give different colors to the background of the navigation bar (NavigationView). The colors will change as I go from one view to the next. I need to have this working for navigationBarTitleDisplayMode being "inline".
I tried the solutions presented in:
SwiftUI update navigation bar title color
but none of these solutions work fully for what I need.
The solution in this reply to that post works for inline:
Using UIViewControllerRepresentable. Nevertheless, when we first open the view it will show the color of the previous view for one second, before changing to the new color. I would like to avoid this and have the color displayed as soon as everything appears on screen. Is there a way to do this?
This other solution will not work either: Changing UINavigation's appearance in init(), because when I set the background in init(), it will change the background of all the views in the app. Again, I need the views to have different background colors.
I tried something similar to this solution: Modifying Toolbar, but it does not allow me to change the color of the navigation bar.
The other solution I tried was this: Creating navigationBarColor function, which is based on: NAVIGATIONVIEW DYNAMIC BACKGROUND COLOR IN SWIFTUI. This solution works for navigationBarTitleDisplayMode "large", but when setting navigationBarTitleDisplayMode to "inline", it will show the background color of the navigation bar in a different color, as if it was covered by a gray/transparent layer. For example, the color it shows in "large" mode is:
Red color in large mode
But instead, it shows this color:
Red color in inline mode
Finally, I tried this solution: Subclassing UIViewController and configuring viewDidLayoutSubviews(), but it did not work for what I want it either.
The closest solutions for what I need are 1. and 4., but they still do not work 100%.
Would anybody know how to make any of these solutions work for navigationBarTitleDisplayMode inline, being able to change the background color of the navigation bar in different layouts, and showing the new color once the view is shown (without delays)?
Thank you!
By the way, I am using XCode 12.5.
Here is the sample code that I am using, taking example 4. as a model:
FirstView.swift
import SwiftUI
struct FirstView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
GeometryReader { metrics in
VStack {
Text("This is the first view")
NavigationLink(destination: SecondView(), tag: "SecondView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "SecondView"
print("Go to second view")
}) {
Text("Go to second view")
}
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
On this screen, if I use
.navigationBarTitleDisplayMode(.large)
the color will be displayed properly: Navigation bar with red color
But using
.navigationBarTitleDisplayMode(.inline)
there is a blur on it: Navigation bar with some sort of blur over red color
import SwiftUI
struct SecondView: View {
#State private var selection: String? = nil
var body: some View {
GeometryReader { metrics in
VStack {
Text("This is the second view")
NavigationLink(destination: ThirdView(), tag: "ThirdView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "ThirdView"
print("Go to third view")
}) {
Text("Go to third view")
}
}
}
.navigationBarColor(backgroundColor: Color.red, titleColor: .black)
.navigationBarTitleDisplayMode(.inline)
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
ThirdView.swift
This view displays the color properly as it is using
.navigationBarTitleDisplayMode(.large)
But if changed to
.navigationBarTitleDisplayMode(.inline)
it will show the blur on top of the color as well.
import SwiftUI
struct ThirdView: View {
var body: some View {
GeometryReader { metrics in
Text("This is the third view")
}
.navigationBarColor(backgroundColor: Color.blue, titleColor: .black)
.navigationBarTitleDisplayMode(.large)
}
}
struct ThirdView_Previews: PreviewProvider {
static var previews: some View {
ThirdView()
}
}
NavigationBarModifierView.swift
import SwiftUI
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: Color, titleColor: UIColor?) {
self.backgroundColor = UIColor(backgroundColor)
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = UIColor(backgroundColor)
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = titleColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: Color, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
NOTE TO THE MODERATORS: Please, do not delete this post. I know similar questions were asked before, but I need an answer to this in particular which was not addressed. Please read before deleting indiscriminately, I need this for work. Also, I cannot ask questions inline in each of those solutions because I do not have the minimum 50 points in stackoverflow required to write there.

I think I have what you want. It is VERY touchy... It is a hack, and not terribly robust, so take as is...
I got it to work by having your modifier return a clear NavBar, and then the solution from this answer works for you. I even added a ScrollView to ThirdView() to make sure that scrolling under didn't affect in. Also note, you lose all of the other built in effects of the bar like translucency, etc.
Edit: I went over the code. The .navigationViewStyle was in the wrong spot. It likes to be outside of the NavigaionView(), where everything else needs to be inside. Also, I removed the part of the code setting the bar color in FirstView() as it was redundant and ugly. I hadn't meant to leave that in there.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: Color, titleColor: UIColor?) {
self.backgroundColor = UIColor(backgroundColor)
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = .clear // The key is here. Change the actual bar to clear.
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = titleColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: Color, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
struct FirstView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
GeometryReader { _ in
VStack {
Text("This is the first view")
NavigationLink(destination: SecondView(), tag: "SecondView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "SecondView"
print("Go to second view")
}) {
Text("Go to second view")
}
}
.navigationTitle("First")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .red, titleColor: .black)
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondView: View {
#State private var selection: String? = nil
var body: some View {
VStack {
Text("This is the second view")
NavigationLink(destination: ThirdView(), tag: "ThirdView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "ThirdView"
print("Go to third view")
}) {
Text("Go to third view")
}
}
.navigationTitle("Second")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .blue, titleColor: .black)
}
}
struct ThirdView: View {
var body: some View {
ScrollView {
ForEach(0..<50) { _ in
Text("This is the third view")
}
}
.navigationTitle("Third")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .green, titleColor: .black)
}
}

iOS 16
Since this version of SwiftUI, there is a dedicated modifier for setting any toolbar background color (including the navigation bar):
Xcode 14 beta 5 (Not working 🤦🏻‍♂️, waiting for beta 6...)
.toolbarBackground(.red, for: .navigationBar)
Xcode 14 beta 1,2,3,4
.toolbarBackground(.red, in: .navigationBar)
It works perfectly in in inline mode and also animates between modes.

For my custom view the following code worked well.
struct HomeView: View {
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemIndigo]
//Use this if NavigationBarTitle is with displayMode = .inline
UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.systemIndigo]
UINavigationBar.appearance().backgroundColor = UIColor.clear
UINavigationBar.appearance().barTintColor = UIColor(Color(red: 32 / 255, green: 72 / 255, blue: 63 / 255))
}
var body: some View {
NavigationView {
ZStack {
...
...
...
}
.padding(.zero)
.navigationTitle("Feedbacks")
}
}
}
and result is like that:

Here is a bit hacky solution, but it works for me (as of iOS 15) both for .large and .inline display modes.
import SwiftUI
enum Kind: String, CaseIterable {
case checking
case savings
case investment
}
struct PaddedList: View {
#Binding var name: String
#Binding var kind: Kind
var body: some View {
NavigationView {
List {
TextField("Account name", text: $name)
Picker("Kind", selection: $kind) {
ForEach(Kind.allCases, id: \.self) { kind in
Text(kind.rawValue).tag(kind)
}
}
.listRowSeparatorTint(.red)
Spacer()
}
.padding(.top, 1) // note top 1 padding!
.background(.green) // the color "bleeds" through
.navigationBarTitle("Navigation Bar")
}
}
}
struct PaddedList_Previews: PreviewProvider {
static var previews: some View {
PaddedList(name: .constant(""), kind: .constant(.checking))
}
}

Related

With SwiftUI 3.0 .swipeActions in a ForEach how do you have an action go to another view while passing that view the input argument of the ForEach?

I am tying to add a .swipeAction to a ForEach list in which I want to pass the element in the list that was selected by the user to another invoked View. In other words when the user swipes on an item in the list, I want the user to be taken to a new View which has the contents of that item in the list so that it can display details from that item in that new view.
With that said, I have mocked up this simple example which I hope helps show the issue I am having.
import SwiftUI
struct ContentView: View {
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
List {
ForEach(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: { print("Hello From The First Button") },
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
NavigationLink(destination: ColorShowView(color: color),
label: {Image(systemName: "magnifyingglass")})
.tint(.yellow)
}
}
}
Spacer()
NavigationLink(destination: ColorShowView(color: Color.red),
label: { Image(systemName: "magnifyingglass" ) } ).tint(.yellow)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
var color: Color
var body: some View {
VStack {
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
}
}
}
What I find is that if I put a NavigationLink as a button on the .swipeActions it shows up correctly, but when tapped the NavigationLink does not execute and hence does not take you to a new View.
If I move that same Navigation Link down to after the .swipeActions, and invoke it with some #State it works, but it adds another row in-between each row in the list of the ForEach. In other words, the ForEach of course sees it as part of its list and adds it in with the other items in the list. Even if I add a .hidden() onto that NavigationLink, it still takes up space with a new row, it just hides the contents of the row, not the row itself.
If I move the NavigationLink outside of the ForEach, then the input argument of color from the ForEach is out of scope. It will correctly build the view and execute the link (using an action and some #State), but it can not pass the color input from the ForEach because of course it is out of scope. If you hard code a color in its place it works fine, except of course for the fact that it does not have the color from the users selection from the list.
Note I put a simple NavigationLink on the bottom of the view as well just so that I could see that it worked correctly outside of the issue with the .swipeActions, and it does work fine with a hard coded color value like Color.red.
This is of course a very made up example, but I think it does show the issue.
Has anyone used a .swipeActions to invoke a NavigationLink to a new view passing into that view the users selected item (in this case the color)? If so how do you get that to work. It feels like a chicken and the egg problem, I can not seem to both have access to the scope in which the input data (the color) is available, and a NavigationLink that does not become part of the view of the ForEach list.
Anyone have any ideas? Thanks in advance for any and all commentary, corrections, ideas, etc.
First solution: by using fullScreenCover and #State var selectedColor
#Environment(.presentationMode) var presentationMode
// ContentView.swift
// StackOverFlow
//
// Created by Mustafa T Mohammed on 12/31/21.
//
import SwiftUI
struct ContentView: View {
#State var isColorShowViewPresented = false
#State var selectedColor: Color = .yellow
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
// you can use List instead of ForEach loop it's the same
// less code :)
List(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: {
isColorShowViewPresented.toggle() // toggle isColorShowViewPresented to trigger the
// fullScreenCover
selectedColor = color
print("Hello From The First Button")
},
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
}
}
Spacer()
.fullScreenCover(isPresented: $isColorRowViewPresented) {
// if you like to implement what happen when user dismiss the presented view
print("user dissmissed ColorRowView")
} content: {
ColorShowView(color: selectedColor) // view that you want to present
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
#Environment(\.presentationMode) var presentationMode
var color: Color
var body: some View {
VStack {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
Spacer()
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
Spacer()
}
}
}
Second solution: NavigationLink and #State var selectedColor
//
// ContentView.swift
// StackOverFlow
//
// Created by Mustafa T Mohammed on 12/31/21.
//
import SwiftUI
struct ContentView: View {
#State var isColorShowViewPresented = false
#State var selectedColor: Color = .yellow
var colors : [Color] = [Color.red, Color.green,
Color.blue, Color.yellow,
Color.brown, Color.cyan, Color.pink]
var body: some View {
NavigationView {
VStack {
// you can use List instead of ForEach loop it's the same
// less code :)
List(colors, id: \.self) { color in
ColorRowView(color: color)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(action: {
isColorShowViewPresented.toggle() // toggle isColorShowViewPresented to trigger the
// NavigationLink
selectedColor = color
print("Hello From The First Button")
},
label: {Label("Hello", systemImage: "face.smiling")})
.tint(Color.orange)
}
}
Spacer()
NavigationLink("", isActive: $isColorShowViewPresented) {
ColorShowView(color: selectedColor)
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle(Text("List Of Colors"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ColorRowView: View {
var color: Color
var body: some View {
VStack {
Text("Color \(color.description)").foregroundColor(color)
}
}
}
struct ColorShowView: View {
var color: Color
var body: some View {
VStack {
Text("Color Name \(color.description)").foregroundColor(color)
Text("Color Hash Value \(color.hashValue)").foregroundColor(color)
}
}
}

SwiftUI UINavigationBar does not ignore the top safe area. How to get rid of empty space on the top?

Code I used to create an example of this navigation bar:
struct ContentView: View {
init() {
let defaultAppearance = UINavigationBarAppearance()
defaultAppearance.configureWithOpaqueBackground()
defaultAppearance.backgroundColor = .red
UINavigationBar.appearance().standardAppearance = defaultAppearance
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
}
var body: some View {
ParentView()
}
}
struct ParentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Tap here")
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.all)
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("Detail View")
}
.edgesIgnoringSafeArea(.all)
}
}
It doesn't matter where I'm putting .edgesIgnoringSafeArea(.all) it doesn't work. Is there another way to tell Navigation Bar to ignore the safe area?
If it's not the safe area problem, then I need to make the whole navigation bar to have the same height of UINavigationBarContentView displayed in UI Hierarchy:

SwiftUI Navigation Controller stuttering with two Navigationlinks per List row

I am trying to create two NavigationLinks in a repeating List. Each has a separate destination. The code all works fine until I imbed the call to the root view in a List/ForEach loop. At which point the navigation becomes very strange.
Try to click on either link and then click the back indicator at the top. It will go to one NavigationLink, and then the other. Sometimes in a different order, and sometimes it will auto-return from one of the links, and othertimes it won't open the second detail view until you return from the first detail view. It does this both in Preview, as well as if you build and run the application.
I have distilled down the code to the most basic below. If you comment the 2 lines as indicated in ContentView, you will then see correct behavior.
I am running Catalina 10.15.5, xCode 11.6, with the application target of IOS 13.6.
How can I modify the code, so that it will work with the List/ForEach loop?
import SwiftUI
struct DetailView1: View {
var body: some View {
HStack {
Text("Here is Detail View 1." )}
.foregroundColor(.green)
}
}
struct DetailView2: View {
var body: some View {
HStack {
Text( "Here is Detail View 2.") }
.foregroundColor(.red)
}
}
struct RootView: View {
var body: some View {
HStack {
VStack {
NavigationLink(destination: DetailView1())
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}.buttonStyle(PlainButtonStyle())
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
NavigationLink(destination: DetailView2())
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
// Comment the following line for correct behavior
List { ForEach(0..<3) {_ in
RootView()
// Comment the following line for correct behavior
} }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
.navigationBarTitle("Strange Behavior")
}
}
}
In your case both navigation links are activated at once user tap a row, to avoid this below is possible approach
Tested with Xcode 12 / iOS 14
The idea is to have one link which is activated programmatically and destination is selected dynamically depending on which button is clicked
struct RootView: View {
#State private var isFirst = false
#State private var isActive = false
var body: some View {
HStack {
VStack {
Button(action: {
self.isFirst = true
self.isActive = true
})
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
Button(action: {
self.isFirst = false
self.isActive = true
})
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
.background(
NavigationLink(destination: self.destination(), isActive: $isActive) { EmptyView() }
)
}
.buttonStyle(PlainButtonStyle())
}
#ViewBuilder
private func destination() -> some View {
if isFirst {
DetailView1()
} else {
DetailView2()
}
}
}

SwiftUI How To Hide The Navigation Bar While Keeping The Back Button

So I'm trying to hide the navigationBar in a Details view in SwiftUI. I've technically gotten it to work by using an init() in a different view, but the issue is that it's making the navigationBar transparent for the whole app, which I only want it in one view. The reason I haven't used an init() in the DetailsView is because I have a variable that needs an input, so I wasn't sure how to do that! Here is the code for the initializer:
init() {
let navBarAppearance = UINavigationBar.appearance()
navBarAppearance.backgroundColor = .clear
navBarAppearance.barTintColor = .clear
navBarAppearance.tintColor = .black
navBarAppearance.setBackgroundImage(UIImage(), for: .default)
navBarAppearance.shadowImage = UIImage()
}
Here's What The Content View and Details View code is like with the init() inside the detailsView:
// ContentView //
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<5) { i in
NavigationLink(destination: DetailsView(test: 1)) {
Text("DetailsView \(i)")
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Test App")
}
}
}
// DetailsView //
struct DetailsView: View {
var test: Int
var body: some View {
ScrollView {
Text("More Cool \(test)")
Text("Cool \(test)")
Text("Less Cool \(test)")
}
}
init(test: Int) {
self.test = 8
let navBarAppearance = UINavigationBar.appearance()
navBarAppearance.backgroundColor = .clear
navBarAppearance.barTintColor = .clear
navBarAppearance.tintColor = .black
navBarAppearance.setBackgroundImage(UIImage(), for: .default)
navBarAppearance.shadowImage = UIImage()
}
}
struct DetailsView_Previews: PreviewProvider {
static var previews: some View {
DetailsView(test: 8)
}
}
It's a heavily edited version of my code, but it shows the problem I have. With no variables needing to be passed in, the init() worked to remove the bar in only that view. However, with that variable input, not only does it change all the views to the "8" for the number, but it also doesn't even hide the navigationBar. I'm not sure if I'm just doing something wrong nor if this is even the right way to do it, but any help would be appreciated!
Also, on a side note, does anyone know how to hide the statusBar in iOS 14 with the NavigationView?
I think you try to use UIKit logic instead of the SwiftUI one. This is what I would do to hide the navigation bar with a back button on the top leading side of your view.
As for hiding the status bar, I would use .statusBar(hidden: true).
But it seems not to work on iOS14. It may be a bug... You can refer yourself to the Apple documentation on this topic.
struct DetailsView: View {
#Environment(\.presentationMode) var presentation
var test: Int
var body: some View {
ZStack(alignment: .topLeading) {
ScrollView {
Text("More Cool \(test)")
Text("Cool \(test)")
Text("Less Cool \(test)")
}
Button(action: { presentation.wrappedValue.dismiss() }) {
HStack {
Image(systemName: "chevron.left")
.foregroundColor(.blue)
.imageScale(.large)
Text("Back")
.font(.title3)
.foregroundColor(.blue)
}
}
.padding(.leading)
.padding(.top)
}
.navigationTitle(Text(""))
.navigationBarHidden(true)
.statusBar(hidden: true)
}
}

Change Tabbed View Bar Color SwiftUI

Does anyone know how to change the background colour of a tabbed view bottom bar?
I have set the accent colour which changed the colour of my icons when I select each tab bar item.
I have tried setting the background to a colour but it doesn't change the back, and tried setting background to an image just to be sure but that also doesn't do anything.
Wondering if I need to specifically access the bottom bar somehow and then set a property on that?
Here is a solution. You can change appearance of the UITabBar and change the TabBar.
struct TabView: View {
init() {
UITabBar.appearance().backgroundColor = UIColor.blue
}
var body: some View {
return TabbedView {
Text("This is tab 1").tag(0).tabItemLabel(Text("tab1"))
Text("This is tab 2").tag(1).tabItemLabel(Text("tab2"))
Text("This is tab 3").tag(2).tabItemLabel(Text("tab3"))
}
}
}
SwiftUI 1.0 - Using named colors
Combining barTintColor and isTranslucent
For some reason I wasn't getting the full color of my named color when I used just barTintColor or even backgroundColor. I had to include isTranslucent too.
Here is my named color:
Setting Just barTintColor
(As you can see, it is slightly faded)
Setting Just backgroundColor
(This darkens the bar a little bit)
Setting barTintColor & isTranslucent to False
This combination is what did it for me:
UITabBar.appearance().isTranslucent = false
UITabBar.appearance().barTintColor = UIColor(named: "Secondary")
In the init() add UITabBar.appearance().barTintColor = UIColor.blue
struct ContentView: View {
#State private var selection = 1
init() {
UITabBar.appearance().barTintColor = UIColor.blue
}
var body: some View {
TabView (selection:$selection){
Text("The First Tab")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
.tag(1)
Text("Another Tab")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}.tag(2)
Text("The Last Tab")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}.tag(3)
}
.accentColor(.white)
}
}
This one looks like a working solution based on the latest version of Swift and SwiftUI
struct TabBar: View {
init() {
UITabBar.appearance().barTintColor = UIcolor.black
var body: some View {
TabView {
HomeView().tabItem {
Image(systemName: "house.fill")
Text("Home")
}
MapView().tabItem {
Image(systemName: "mappin.circle.fill")
Text("Map")
}
}
.edgesIgnoringSafeArea(.top)
}
}
where HomeView() and MapView() are just some other views created earlier that will be displayed on tap.
It is important to set the colors for UITabBar before the TabView is shown. If not using a custom view with initializer, then you must make sure it is called before the TabView is loaded, for instance in the AppDelegate (when using the "UIKit App Delegate" in the project life cycle or otherwise adding it for "SwiftUI App" life cycle).
Then you can configure it with a UITabBarAppearance() object, for instance like so:
let tabBarAppeareance = UITabBarAppearance()
tabBarAppeareance.shadowColor = .gray // For line separator of the tab bar
tabBarAppeareance.backgroundColor = .black // For background color
UITabBar.appearance().standardAppearance = tabBarAppeareance
TabbedView has been deprecated, for now you can try:
struct AppTabbedView: View {
#State private var selection = 3
init() {
UITabBar.appearance().backgroundColor = UIColor.blue
}
var body: some View {
TabView (selection:$selection){
Text("The First Tab")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
.tag(1)
Text("Another Tab")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}.tag(2)
Text("The Last Tab")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}.tag(3)
}
.font(.headline)
}
}
In case if you need change background of unselected item and top line as well then you can stuck. Next is what will work for me. We will start from this one:
In first iteration I change everything except top line:
struct ContentView: View {
#ObservedObject var model: ContentViewModel
init(model: ContentViewModel) {
self.model = model
UITabBar.appearance().isTranslucent = false
UITabBar.appearance().unselectedItemTintColor = UIColor(Color.primary)
UITabBar.appearance().barTintColor = UIColor(Color("tab_background"))
}
var body: some View {
NavigationView {
TabView(selection: $model.selectedTab) {...}
}
}
}
But after that, I realize that I can't change the color of this line in the same way. So I will use #atineoSE answer. But realize that set the UITabBar.appearance().standardAppearance will totally override my previous customization. So I need to change it - here is the final code and result:
init(model: ContentViewModel) {
self.model = model
let itemAppearance = UITabBarItemAppearance()
itemAppearance.normal.iconColor = UIColor(Color.primary)
let appeareance = UITabBarAppearance()
appeareance.shadowColor = UIColor(Color("tab_separator"))
appeareance.backgroundColor = UIColor(Color("tab_background"))
appeareance.stackedLayoutAppearance = itemAppearance
appeareance.inlineLayoutAppearance = itemAppearance
appeareance.compactInlineLayoutAppearance = itemAppearance
UITabBar.appearance().standardAppearance = appeareance
}
Its Work for me in latest Versions
var body: some View {
TabView{
Text("Zain ahmed")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Label("Home", systemImage: "house.fill")
}
Text("Bookmark Tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Label("Bookmark", systemImage: "bookmark.circle.fill")
}
}
.onAppear() {
UITabBar.appearance().backgroundColor = .lightGray
}
}
init() {
UITabBar.appearance().barTintColor = .white
UITabBar.appearance().backgroundColor = .white
}
While this is great for light mode, when you switch to dark mode, the background for the tabbar stays the color you have selected. Any way to make the bar go to black when dark mode is sl