I have a simple shape I want to use as a button but I would like to have the link/button only triggered when clicked on the shape itself but not on its frame. With the following code the link is also triggered when clicking inside the frame of the triangle:
struct Triangle: Shape {
func path(in rect: CGRect)-> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
return path
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: Text("DetailView")){
Triangle()
.fill(Color.green)
.frame(width: 200, height: 200, alignment: .center)
}.navigationBarTitle(Text("Triangle"))
}
}
}
How can I adjust the code to have only tapping the triangle but not the frame triggering the link?
You can add a contentShape
modifier to tell the system how to do hit testing for the NavigationLink:
NavigationLink(destination: Text("DetailView")){
Triangle()
.fill(Color.green)
.frame(width: 100, height: 100, alignment: .center)
}
.contentShape(Triangle())
There's a caveat here, which is SwiftUI includes some slop in the hit testing, so this will still accept some clicks/touches that are close to the borders of the shape, but still outside. I don't have a great solution for getting rid of that slop. But, you can see that the same thing happens on the basic Rectangle
that normally makes up the navigation view as well -- e.g. put a border
around it and note that you can still tap slightly outside of that border.
A secondary way you could experiment with is render the Path
on its own, attach an onTapGesture
to it and turn on/off a boolean connected to the NavigationLink
that you'd then want to move outside your hierarchy. Something like:
struct ContentView: View {
@State var navigationLinkActive = false
var body: some View {
NavigationView {
Group {
if navigationLinkActive {
NavigationLink(destination: Text("Detail"), isActive: $navigationLinkActive) {
EmptyView()
}
}
Triangle()
.fill(Color.green)
.frame(width: 100, height: 100, alignment: .center)
.onTapGesture {
navigationLinkActive = true
}
}.navigationBarTitle(Text("Triangle"))
EmptyView()
}
}
}
Note that in this solution, you still have the hit slop issue, plus you lose the touch down/up highlighting that the Button
has.