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
)
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)
)
}
I built a custom Button.
My problem is, that it can be only clicked on the Text Elements not on the whole area of the Button.
What's going wrong, how can I solve this?
struct MyButton: View {
var body: some View
{ Button( action:{ print("pressed") })
{ HStack {
VStack(alignment: .leading){
Text("Tomorrow").font(.caption)
Text("23.5.22 KW 23")
}
Spacer()
}
}
.padding([.horizontal],4.0)
.padding([.vertical],2.0)
.background(RoundedRectangle(cornerRadius: 5).fill(Color.red))
.buttonStyle(PlainButtonStyle())
.padding([.horizontal],8.0)
}
}
By default, only the parts of the view that actually render something are tappable. You need to add a .contentShape(Rectangle()) modifier to your Button to make the entire area interactive.
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 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)
}
}
}
}
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.