SwiftUI Button Not Clickable With Overlay - swift

I have a Button with an overlay, but it is not clickable. If I take the overlay off, it becomes clickable. How can I make it work with the overlay?
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
UIPasteboard.general.string = viewModel.promoCode?.code ?? ""
} label: {
HStack {
Text(viewModel.promoCode?.code ?? "")
.foregroundColor(Color(UIColor.uStadium.primary))
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.leading, 20)
Spacer()
Image("copyIcon")
.foregroundColor(Color(UIColor.uStadium.primary))
.padding(.trailing, 20)
}
}
.overlay (
RoundedRectangle(cornerRadius: 8)
.stroke(Color(UIColor.uStadium.primary), style: StrokeStyle(lineWidth: 2, dash: [10]))
.frame(height: 42)
.background(Color(UIColor.uStadium.primary.withAlphaComponent(0.2)))
)
.frame(height: 42)
.cornerRadius(8)

Another solution is adding .allowsHitTesting(false) modifier at the view inside of the overlay.
The reason why touch is not working is overlay modifier layers a secondary view in front of current view. So when user tap the button they were actually tapping the overlayed view.
The allowsHitTesting(false) stop a view from receiving any kind of taps, and any taps automatically continue through the view on to whatever is behind it.
Simple example:
struct ContentView: View {
var body: some View {
Button {
print("dd")
} label: {
Image(systemName: "house").resizable().frame(width: 50, height: 50)
}
.overlay (
RoundedRectangle(cornerRadius: 8).allowsHitTesting(false)
)
}
}
and touch works!

Related

SwiftUI Button Tap off Target

I have a scroll view of some buttons but for some reason the tap target is below the actual button. The background of the button shows a square behind the circular button. Why can't I select the button where the content is?
Code:
struct ContentView: View {
let buttons = ["🔮", "🔮"]
#State var selected: Bool = false
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(buttons, id: \.self) { item in
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
selected.toggle()
} label: {
Text(item)
.frame(width: 40, height: 40, alignment: .center)
.lineLimit(1)
.background(Color(UIColor.lightGray))
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(.black, lineWidth: selected ? 1 : 0)
)
}
.frame(width: 40, height: 40, alignment: .center)
.cornerRadius(20)
.contentShape(Circle())
.background(.red)
}
}
.padding(.horizontal, 10)
}
.frame(maxHeight: 40)
}
}
I was not able to replicate your problem, when clicking any of the two buttons (inside them) both buttons show as they are being clicked at the same time.
I have version 14.1 of XCode and 16.1 of iOS, maybe a different version may cause this problem?
But first you should change the id of the buttons. You are using the same id for both buttons and that is not recommended in a ForEach and could cause problems.

How to avoid Bottom View to come over the Keyboard in SwiftUI?

My View is something like this:
VStack{
ScrollView {
ForEach(0..<50) {_ in
Text("Editor")
}
VStack {
TextField("My Text Filed, text: $text)
}
.frame(minHeight: 200, maxHeight: 200)
.overlay(
RoundedRectangle(cornerRadius: 6.0)
.stroke(.gray, lineWidth: 1.0)
)
.padding()
}
Button("My Button")
.background(.green)
.padding()
}
My problem is that whenever clicking on text field my Button comes on the top of keyboard. I know this is cool feature and expected that view out side if scroll will come to top of keyboard. I want to avoid this, can any body give some solution how to avoid this.
I want my Button to always be below the ScrollView in the bottom not on the top of keyboard
Try the below code:
#State private var text = ""
var body: some View {
ZStack{
VStack{
Spacer()
Button("Button title") {
print("Button tapped!")
}
}
.ignoresSafeArea(.keyboard)
ScrollView {
ForEach(0..<50) {_ in
Text("Editor")
}
VStack {
TextField("My Text Filed", text: $text)
}
.frame(minHeight: 200, maxHeight: 200)
.overlay(
RoundedRectangle(cornerRadius: 6.0)
.stroke(.gray, lineWidth: 1.0)
)
.padding()
}
.padding(.bottom, 20)
}
}

onTapGesure not registering for background of ScrollView

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)
)
}

Spacer not pushing view to top of screen inside ZStack?

How can I position my search-bar and button to the top of the screen with 65 padding from the top AND KEEP the onTapGestureFunctionality?
I tried the modifiers .edgesIgnoringSafeArea(.top) with padding(.top, 65) applied to the VStack inside my ZStack but then my onTapGesture would not work when tapping on either the search bar or the button...
Why does the Spacer() only push the elements about 80% to the top of the screen? Could this be because its inside a navigation view?
See image below.
Thank you
struct MapScreen: View {
var body: some View {
NavigationView {
ZStack(alignment: .bottom) {
VStack {
HStack(spacing: 15) {
SearchBar()
.padding(.leading, 5)
.onTapGesture {
print("search pressed")
}
ActivateClaimButton()
.onTapGesture {
print("claim pressed")
}
}
// 2 modifiers below positions correctly but
// onTapGesture doesn't work
// .padding(.top, 65)
// .edgesIgnoringSafeArea(.top)
Spacer()
}.zIndex(2)
MapView(locations: $locations, spotArray: $spotArray).edgesIgnoringSafeArea([.top, .bottom])
BottomNavBar().zIndex(1).edgesIgnoringSafeArea(.all).offset(y: 35)
}
.navigationViewStyle(StackNavigationViewStyle())
.fullScreenCover(isPresented: $presentSearchView, content: {
SearchView()
})
}
}
}
The answer that worked for me was
.navigationBarHidden(true)

In SwiftUI, why will my buttons trigger if contained in a ZStack but not in an overlay on a VStack?

Thanks in advance for any advice you can give. I have a custom dropdown menu that I have built in SwiftUI:
struct PhoneTypeDropdown: View {
let phoneTypes:[PhoneType] = [.Cell, .Work, .Landline]
#State var isExpanded: Bool
var body : some View {
VStack (spacing: 0) {
HStack {
Text("select type").font(.footnote)
Spacer()
Image(systemName: Constants.Design.Image.IconString.ChevronDown)
.resizable()
.frame(width: Constants.Design.Measurement.DropDownIconWidth,
height: Constants.Design.Measurement.DropDownIconHeight)
}
.padding(Constants.Design.Measurement.PadMin)
.background(Constants.Design.Colors.LightGrey)
.cornerRadius(Constants.Design.Measurement.CornerRadius)
.onTapGesture {
self.isExpanded.toggle()
}
if isExpanded {
VStack (spacing: 0){
ForEach(0 ..< phoneTypes.count) { i in
Button(self.phoneTypes[i].description, action: {
print("button tapped")
self.isExpanded.toggle()
})
.buttonStyle(DropDownButtonStyle())
if i != self.phoneTypes.count - 1 {
Divider()
.padding(.vertical, 0)
.padding(.horizontal, Constants.Design.Measurement.Pad)
.foregroundColor(Constants.Design.Colors.DarkGrey)
}
}
}.background(Constants.Design.Colors.LightGrey)
.cornerRadius(Constants.Design.Measurement.CornerRadiusMin)
.padding(.top, Constants.Design.Measurement.PadMin)
}
}.frame(width: Constants.Design.Measurement.PhoneTypeFieldWidth)
.cornerRadius(Constants.Design.Measurement.CornerRadius)
}
}
When I went to utilize this in my view, I had planned on using it as an overlay like this:
VStack(spacing: 0) {
ProfileField(text: phone1,
label: Constants.Content.PrimaryPhone,
position: .Justified)
.overlay(
PhoneTypeDropdown(isExpanded: expandDropdown1)
.padding(.top, 32) //FIXME: Fix this hard-coded value.
.padding(.trailing, Constants.Design.Measurement.PadMax),
alignment: .topTrailing
)
}
However, although the above code will trigger on clicking and expand the dropdown box, tapping any of the Button objects inside the dropdown box does nothing. I then tried to implement the dropdown box using a ZStack like this:
ZStack(alignment: .topTrailing) {
ProfileField(text: phone1,
label: Constants.Content.PrimaryPhone,
position: .Justified)
PhoneTypeDropdown(isExpanded: expandDropdown1)
.padding(.top, 32) //FIXME: Fix this hard-coded value.
.padding(.trailing, Constants.Design.Measurement.PadMax)
}
The dropdown box worked beautifully, expanding and collapsing as expected. However, now, when it expands, it pushes down the rest of the form instead of laying on top of the form as desired.
My question then is this: what would cause my button action to fire correctly when using the dropdown object in a ZStack as opposed to incorporating it in an overlay on an view in a VStack?