SwiftUI Navigation Bar Colour - swift

I am trying to build a master/detail type sample application and I'm struggling to get the NavigationBar UI to work correctly in my detail view. The code for the view I am working on is as follows:
struct JobDetailView : View {
var jobDetails: JobDetails
var body: some View {
Form {
Section(header: Text("General")) {
HStack {
Text("Job Name")
Spacer()
Text(jobDetails.jobName)
.multilineTextAlignment(.trailing)
}
HStack {
Text("Hourly Rate")
Spacer()
Text(TextFormatters().currencyString(amount: jobDetails.hourlyRateBasic!))
.multilineTextAlignment(.trailing)
}
}
} .padding(.top)
.navigationBarTitle(Text(jobDetails.jobName))
}
}
The reason for the .padding(.top) is to stop the Form overlapping the navigation bar when scrolling upwards.
The white background on the navigation bar portion my issue (first image), I should expect it to be in keeping with the overall style of the UI, what I expect to happen is shown in the second image.
I have tried to add foreground/background colours and Views to change this colour, but to no avail. I'm also reticent of forcing a colour for the NagivationBar, as this will require further configuration for use with dark mode.
Current view when running application.
Expected view.

There's no available (SwiftUI) API for doing that (yet) (beta 5).
But we could use UINavigationBar.appearance(), as in:
UINavigationBar.appearance().backgroundColor = .clear
Full Code
import SwiftUI
struct JobDetailView: View {
init() {
UINavigationBar.appearance().backgroundColor = .clear
}
var body: some View {
NavigationView {
Form {
Section(header: Text("General")) {
HStack {
Text("Job Name")
Spacer()
Text("Scientist")
.multilineTextAlignment(.trailing)
}
HStack {
Text("Hourly Rate")
Spacer()
Text("$ 1.00")
.multilineTextAlignment(.trailing)
}
}
}
.navigationBarTitle("Scientist")
.navigationBarHidden(false)
}
}
}
#if DEBUG
struct JobDetailView_Previews: PreviewProvider {
static var previews: some View {
JobDetailView()
}
}
#endif
Result
Dark Mode Result

You don't need to change anything for your issue now (since it is the default behavior of SwiftUI). But about the title of your question:
From iOS 16
You can set any color to the background color of any toolbar background color (including the navigation bar) for the inline state with a simple native modifier:
.toolbarBackground(.yellow, in: .navigationBar)

Related

Add Padding to TabView Page Indicator

I have a view that is a TabView with the style PageTabViewStyle(indexDisplayMode: .always which looks great for my use case however the page indicator is bumping right up to the safe area near the bottom of the screen and it looks bad. I'd like to move the page indicator up to some n value. here's a sample of my view to reproducing it. If this view were built on any device without a Home Button, it will ride on top of the home indicator line.
var body: some View {
ZStack {
TabView(selection: $homeVM.selectedPageIndex) {
// Any number of views here.
}
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never))
}.edgesIgnoringSafeArea(.all)
}
I attempted to add the padding to the ZStack, which does work but then my TabView is cut off on the bottom, which means my cells disappear prematurely.
Here's an image for what I'm trying to fix. Notice the page indicator sits on the home bar indicator. I need the indicators pushed up, without pushing up the background ScrollView
Update #1
This view is being presented by a base view that I use to handle my navigation stack. The view is as follows. The important thing to note here is the .ignoresSafeArea() that I have on this view. I did that because it's a containing view for my eventual TabView to be presented from. Interestingly if I remove this modifier the indicators move up to a more manageable position, but then my form becomes clipped at the top and bottom of the device when scrolling, and that's not ideal.
struct BaseLaunchView: View {
#StateObject var baseNavVM = BaseLaunchViewModel()
#State var shouldLogin = false
#State var shouldRegister = false
var body: some View {
VStack {
switch baseNavVM.loggedIn {
case true:
HomeView()
default:
NavigationView {
VStack{
Spacer()
VStack(spacing: 30) {
VStack {
Text("Stello")
.font(Fonts.title)
Text("Life Groups")
.font(Fonts.body)
}
StelloDivider()
Text("Connect with like minded people, to fellowship and find your home.")
.font(Fonts.subheading)
.multilineTextAlignment(.center)
}
Spacer()
NavigationLink(destination: RegisterOptionsView(isLoggingIn: true), isActive: $shouldLogin) {
Button(action: {
shouldLogin.toggle()
}, label: {
Text("Login")
.font(Fonts.button)
}).buttonStyle(StelloFillButtonStyle())
}
NavigationLink(destination: RegisterOptionsView(isLoggingIn: false), isActive: $shouldRegister) {
Button(action: {
shouldRegister.toggle()
}, label: {
Text("Register")
.font(Fonts.button)
}).buttonStyle(StelloHollowButtonStyle())
}
}
}.accentColor(.black)
}
}
.padding()
.environmentObject(baseNavVM)
.ignoresSafeArea()
}
}

Inserting non-list elements in a SwiftUI view

I am working on a SwiftUI page that consists of a table view with some rows but I would also like to have some non-cell elements in there. I am currently having some issues with this and I have tried various different avenues. I basically just need some elements in there that aren't wrapped inside a cell while still maintaining the tableview's grayish background. In my example below, I am trying to get an image right under the table rows.
Below is my code:
import SwiftUI
struct ContentView: View {
private var color = Color(red: 32/255, green: 35/255, blue: 0/255)
var body: some View {
NavigationView {
Form {
Section() {
HStack {
Text("AA")
.foregroundColor(color)
}
HStack {
Text("B")
.foregroundColor(color)
}
HStack {
Text("C")
.foregroundColor(color)
}
HStack {
Text("D")
.foregroundColor(color)
}
HStack {
Text("E")
.foregroundColor(color)
}
HStack {
Text("F")
.foregroundColor(color)
}
HStack {
Text("G")
.foregroundColor(color)
}
}
}.navigationBarTitle(Text("Page"))
HStack {
Image(systemName: "fallLeaves")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here are all of the scenarios I have tried that have been unsuccessful:
Scenario 1: Adding a HStack with the image outside of the List. (What the current code shows) This does not get the image to show as the table view takes the whole view.
Scenario 2: Adding the HStack with the image within the list block. This wraps the image around the cell like so
Scenario 3: Wrapping the list and the HStack contains the image inside a VStack. This is no good as it's basically like a split-screen with two different views. The table gets shrunk and has it's own scroll bar. Notice the scroll bar in the image below
The ideal solution would look like this but I'm not sure what to try as I can't get it to look like this where it's all one continuous view and the image isn't in a cell
i'll let you work out the padding and the rounding.
struct ContentView: View {
private var color = Color(red: 32/255, green: 35/255, blue: 0/255)
var body: some View {
NavigationView {
Form {
Section(content: {
HStack {
Text("AA")
.foregroundColor(color)
}
HStack {
Text("B")
.foregroundColor(color)
}
}, footer: {
Image("fallLeaves")
})
}.navigationBarTitle(Text("Page"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can also use place your component (event a page view) inside the cell.
What can be done;
Use a Section to have a good style when you initiate another cell (because we'll make the following one transparent)
Create a new Section
Place a / any view
Add .listRowBackground(Color.clear) to the new view; to see the background transparently.
Add .listRowSeparator(.hidden)to the new view; to remove the cell line below.
After these, you'll have a placed view with additional padding. If this is important, you can play with padding to catch 0 padding distance. Also, by changing the list style (for this second group) with these kinds of styles, .listStyle(.grouped), you can get catch zero padding/spacing.
The footer solution can work for images, but when you need some texts, you'll see that it'll scale down the sizes. So instead of footer, my solution is to use a cell view and show it as a non-cell item;
Here is my change, which works (at least I think)
import SwiftUI
struct ContentView: View {
private var color = Color(red: 32/255, green: 35/255, blue: 255/255)
// Just changed color to test my changes better.
// private var color = Color(red: 32/255, green: 35/255, blue: 0/255)
var body: some View {
NavigationView {
Form {
Section() {
Text("AA")
Text("B")
Text("C")
Text("D")
Text("E")
Text("F")
Text("G")
}
//foreground color won't colorize the cell's bottom lines
.foregroundColor(color)
Section{
HStack{
// Use Spacer or another horizontal alignment
// I just tried with another image, you can place yours with full width and remove the Spacer() 's
Spacer()
Image(systemName: "checkmark.seal.fill")
.resizable()
.scaledToFit()
.foregroundColor(color)
.frame(width: 100)
Spacer()
}
.listRowBackground(Color.clear)
// Not required for single items; but would be usefull, if you'll add more views or sections
.listRowSeparator(.hidden)
}
}.navigationBarTitle(Text("Page"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is why your methods won't work;
Scenario 1: Using VStack and HStack will split the screen into different scrollable. This is by design, In SwiftUI List element overrides Stacks and ScrollableView
(Reference)
Scenario 2: Same as Scenario 1
Scenario 3: Actually, this is the same as Scenario 1
You might try 3 things;
My solution, I don't see a major problem
Footer solution, which can work, but for some views, you will have problems
Determine a background color in ZStack and develop a custom button object which looks like a cell view (cons: non-native way and long time consuming work around)

How can I change my SwiftUI background without losing the navigation bar UI?

I'm trying to change my View background colour to a specific color, however, whenever I add it using the basic Zstack way, it loses the navigation bar UI. (See pictures)
EDITED CODE
This method is not working for me:
var body: some View {
ZStack{
Color("Background")
.edgesIgnoringSafeArea(.vertical)
VStack {
ScrollView {
ZStack {
VStack {
HStack{
VStack (alignment: .leading) {
Text("")
}
}
}
}
}
Text("")
}
}
}
Current UI with simple ZStack:
Desired UI:
How do I change my background color in SwiftUI without losing the navigation bar UI?
At this period of SwiftUI evolution it is possible only as workaround via UIKit
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1):
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack {
ForEach(0..<50) {
Text("Item \($0)")
}
}
}
Text("Test").navigationTitle("Test")
.background(UINavigationConfiguration { nc in
nc.topViewController?.view.backgroundColor = .yellow
})
}
}
}
Note: the UINavigationConfiguration is taken from next my answer https://stackoverflow.com/a/65404368/12299030

BackButton without text SwiftUI

Actually, this is the question.
I have a screen like this, I would like to change the BackButton.
My View screen:
enter image description here
I want instead of blue backbutton with standart array + text BACK, just to be black array WITHOUT text.
In other words, essentially remove the text from the backbutton and change the color to black. How not to achieve this?
You can use the appropriate modifiers inside the View you wish to be black and without text. You need to specify no button and supply your own image as well as the return functionality.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Hello, World!")
}
}
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Hello")
Text("Second view")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.presentation.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.left")
}
.foregroundColor(.black))
}
}
Edited: Inserted the appropriate image.

Custom Button in SwiftUI List

SwiftUI Custom Button in List
I'm trying to create a custom button in a SwiftUI List. I want it to have a blue background with white text, and importantly, to remain blue and go to 50% opacity when pressed, not the default grey.
I tried using a custom ButtonStyle, but when I do so, the tappable area of the button is reduced to just the label itself. If I tap any other part of the cell, the colour doesn't change. If I remove the ButtonStyle, tapping anywhere on the cell works
How can I fix this so that I get my custom colours, including the colour when tapped, but the whole cell is still tappable?
import SwiftUI
struct BlueButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundColor(configuration.isPressed ? Color.white.opacity(0.5) : Color.white)
.listRowBackground(configuration.isPressed ? Color.blue.opacity(0.5) : Color.blue)
}
}
struct ExampleView: View {
var body: some View {
NavigationView {
List {
Section {
Text("Info")
}
Section {
Button(action: {print("pressed")})
{
HStack {
Spacer()
Text("Save")
Spacer()
}
}.buttonStyle(BlueButtonStyle())
}
}
.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle(Text("Title"))
}
}
}
struct ExampleView_Previews: PreviewProvider {
static var previews: some View {
ExampleView()
}
}
In standard variant List intercepts and handles content area of tap detection, in your custom style it is defined, by default, by opaque area, which is only text in your case, so corrected style is
Update for: Xcode 13.3 / iOS 15.4
It looks like Apple broken something, because listRowBackground now works only inside List itself, no subview, which is senseless from generic concept of SwiftUI.
Updated solution with same behavior as on demo
Original for: Xcode 11.4 / iOS 13.4
struct BlueButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.headline)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.contentShape(Rectangle())
.foregroundColor(configuration.isPressed ? Color.white.opacity(0.5) : Color.white)
.listRowBackground(configuration.isPressed ? Color.blue.opacity(0.5) : Color.blue)
}
}
and usage, just
Button(action: {print("pressed")})
{
Text("Save")
}.buttonStyle(BlueButtonStyle())
and even
Button("Save") { print("pressed") }
.buttonStyle(BlueButtonStyle())