SwiftiSwift
SwiftiSwift

Reputation: 8707

Tap Action not working when Color is clear SwiftUI

my tapAction is not recognizing a tap when my foregroundColor is clear. When i remove the color it works fine.

That's my code:

ZStack {
    RoundedRectangle(cornerRadius: 0)
        .foregroundColor(Color.clear)
        .frame(width: showMenu ? UIScreen.main.bounds.width : 0)
        .tapAction {
            self.showMenu.toggle()
        }
    
    RoundedRectangle(cornerRadius: 5)
        .foregroundColor(Color.green)
        .shadow(radius: 5, y: 2)
        .padding(.trailing, 50)
        .frame(width: showMenu ? UIScreen.main.bounds.width : 0)
}
.edgesIgnoringSafeArea(.top)

Upvotes: 51

Views: 15827

Answers (4)

rob mayoff
rob mayoff

Reputation: 385590

UPDATE

51N4’s answer is better and you should do that.

ORIGINAL

I have also discovered that a shape filled with Color.clear does not generate a tappable area.

Here are two workarounds:

  1. Use Color.black.opacity(0.0001) (even on 10-bits-per-channel displays). This generates a color that is so transparent that it should have no effect on your appearance, and generates a tappable area that fills its frame. I don't know if SwiftUI is smart enough to skip rendering the color, so I don't know if it has any performance impact.

  2. Use a GeometryReader to get the frame size, and then use the contentShape to generate the tappable area:

     GeometryReader { proxy in
         Color.clear.contentShape(Path(CGRect(origin: .zero, size: proxy.size)))
     }
    

Upvotes: 29

Giuseppe Mazzilli
Giuseppe Mazzilli

Reputation: 490

In my case a View that didn't trigger onTapGesture:

struct MainView: View {
  var action: () -> Void

  var body: some View {
    NotTappableView()
      .contentShape(Rectangle())
      .onTapGesture(
        action()
      )
  }
}

I solved this way:

struct MainView: View {
  var action: () -> Void

  var body: some View {
    NotTappableView()
      .overlay(
        Color.clear
          .contentShape(Rectangle())
          .onTapGesture {
             action()
          }
      )
  }
}

This made whole untappable view now tappable.

Upvotes: 2

protspace
protspace

Reputation: 2147

Here is the component

struct InvisibleButton: View {

    let action: (() -> Void)?

    var body: some View {
        Color.clear
        .contentShape(Rectangle())
        .onTapGesture {
            action?()
        }
    }
}

usage: Put your view and InbisibleButton in ZStack

ZStack {
     **yourView()**
     InvisibleButton {
        print("Invisible button tapped")
     }
}

you also can make a modifier to simplify usage:

struct InvisibleButtonModifier: ViewModifier {

    let action: (() -> Void)?

    func body(content: Content) -> some View {
        ZStack {
            content
            InvisibleButton(action: action)
        }

    }
}


     **yourView()**
     .modifier(InvisibleButtonModifier {
        print("Invisible button tapped")
      })

However, if your SwiftUI View has a UIKit view as a subview under, you will have to set Color.gray.opacity(0.0001) in order to UIView's touches be ignored

Upvotes: 15

51N4
51N4

Reputation: 844

The accurate way is to use .contentShape(Rectangle()) on the view. Described in this tutorial:

control-the-tappable-area-of-a-view by Paul Hudson @twostraws

VStack {
    Image("Some Image").resizable().frame(width: 50, height: 50)
    Spacer().frame(height: 50)
    Text("Some Text")
}
.contentShape(Rectangle())
.onTapGesture {
    print("Do Something")
}

how-to-control-the-tappable-area-of-a-view-using-contentshape stackoverflow

Upvotes: 82

Related Questions