I am applying a background modifier to a SwiftUI view that darkens and makes inactive the view until either:
1 the user selects options from the pop-up menu OR 2 taps on the dark-overlay to cancel modifying their profile-picture.
Currently this is working except the overlay does not darken the navigation tool-bar, only the main-view...
I tried placing the background before the tool-bar declaration - did not work, also tried applying it to each individual tool-bar item but it would only darken the individual button.
How can I apply this overlay ALSO to the toolbar?
Code and image below...
struct EditProfileMenu: View {
#State private var openProfilePictureAction: Bool = false
// ^ presents HalfActionSheet
var body: some View {
NavigationView {
ZStack {
VStack {
Text("CHANGE PROFILE PICTURE")
.onTapGesture { self.openProfilePictureAction = true }
}
.toolbar(content: {
// TOOL BAR BUTTONS SAVE AND CANCEL
})
VStack {
Spacer()
HalfActionSheet() .offset(y: self.openProfilePictureAction ? 0 : UIScreen.main.bounds.height)
}
// HOW CAN I APPLY THIS TO THE TOOLBAR?
.background(self.openProfilePictureAction ? Color.black.opacity(0.6) : Color.clear)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
self.openProfilePictureAction = false
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
}
Related
For some reason, the .onTapGesture event won't fire for the background of the ScrollView, but it does fire correctly when applied to the background of a nested VStack view:
var body: some View {
ScrollView {
VStack {...}
.background(
Rectangle()
.fill(.red)
.onTapGesture {
print("!!!CLICKED!!!")
}
)
}
.background(
Rectangle()
.fill(.green)
.onTapGesture {
print("!!!CLICKED!!!")
}
)
}
So when I click the red area, I'm able to see "!!!CLICKED!!!" printed, but when I click the green area, nothing is printed:
ViewLayout image
To trigger the action on the background of the ScrollView, just move the .onTapGesture() modifier to the bottom of the ScrollView itself, instead of the .background().
Here below, you will see a sample code that works (I added a couple of variables to notice the effect directly on the screen):
#State private var clicked = "Nothing" // For testing purposes only
#State private var alternate = true // For testing purposes only
var body: some View {
ScrollView {
VStack {
Text("One")
Text("Two")
Text("Three")
Text(clicked) // For testing purposes: see what was clicked on the screen
.padding()
Text(alternate ? "Clicked" : "... and again") // See the effect after each click
}
.background(
Rectangle()
.fill(.red)
.onTapGesture {
clicked = "Clicked red"
alternate.toggle()
print("!!!CLICKED RED!!!")
}
)
}
// Here is where the modifier should be to trigger the action on the background of the ScrollView
.onTapGesture {
clicked = "Clicked green"
alternate.toggle()
print("!!!CLICKED GREEN!!!")
}
.background(
Rectangle()
.fill(.green)
)
}
so I'd like to lay a Color at the top of a ZStack over another view. The reason I don't want to use overlay is the Color is going to have a tap gesture attached to it. A minimal reproducible example is below. Essentially, I want the Color.secondary to be confined to the same area as the HStack (without explicitly setting frame size. Here's the code:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
Spacer()
Button(action: {
print("tapped button")
}, label: {
Text("button")
})
}.background(Color.red)
Color.secondary
.onTapGesture {
print("clicked color")
}
}
}
}
So I'd like the view to just be a white screen, with an HStack that looks slightly darker red.
Below is a picture of my UI. The view is greyed out during onboarding, and the user will essentially just tap the grey area to go to the next step in the onboarding. If I attach a tap gesture to the Color view and then just hide the color view according to state changes, this isn't a problem. After the onboarding is completed, the greyed area won't be there and the buttons underneath need to be interactable. When using overlays, AFTER onboarding, I don't want tapping anywhere on the view to change the app state.
https://i.stack.imgur.com/lphHg.png
Given your further description, I believe you have the onTapGesture(perform:) in the wrong place, outside of the overlay rather than inside.
In the code below, the onTapGesture(perform:) can only be tapped on the gray overlay when it is showing. However when it is attached after the overlay, it can be tapped on when not tapping on something like the button.
Working code:
struct ContentView: View {
#AppStorage("is_onboarding") var isOnboarding = true
var body: some View {
HStack {
Spacer()
Button {
print("tapped button")
} label: {
Text("button")
}
}
.background(Color.red)
.overlay {
if isOnboarding {
Color.secondary
.onTapGesture {
print("clicked color")
isOnboarding = false
}
}
}
}
}
If on iOS 14+ not iOS 15+, use the following overlay instead:
.overlay(
isOnboarding ?
Color.secondary
.onTapGesture {
print("clicked color")
isOnboarding = false
}
: nil
)
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()
}
}
I have an Issue where my text disappears behind the navigation bar. The navigation bar is made visible once the user clicks on Settings (or any other menu button). It shows up and the content is visible but then when done loading the new view the text disappears behind the bar. Any solutions?
I change the status of the navigation bar being visible with .onAppear and .onDisappear of views that are root level.
Code something like this:
struct ContentView: View {
#State public var navBarHidden = true
var body: some View {
NavigationView{
VStack{
ZStack(alignment: .center){
WhiteImage().onAppear{self.navBarHidden = true} //Here only seen as white background
BottomButtons().onDisappear{self.navBarHidden = false}
ProfileInvoke().navigationBarTitle("").navigationBarHidden(self.navBarHidden)
}
}
}
}
}
//The buttons are done with such a construct
struct MenuButton: View {
var buttonText: String
var buttonCallView: AnyView
var body: some View {
NavigationLink(destination: self.buttonCallView) {
Text(self.buttonText)
}.padding()
}
}
//Population of a button
MenuButton(buttonText: "My Favourites", buttonCallView: AnyView(MyFavouritesView().navigationBarTitle(Text("My Favourites"), displayMode: .inline)))
// The settings view where the title disappears
struct SettingsView: View {
var body: some View {
HStack(alignment: .top){
VStack(alignment: .leading){
Text("General").bold()
Divider()
Spacer()
}.padding()
Spacer()
}
}
}
I have the feeling that it has something to do with the .onAppear and .onDisappear where I set the status of the navigation bar being hidden or not. Ain't sure tho.
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)