42

I've built a custom slider in SwiftUI with a thumb dragger that is 20x20. Everything is working, except the tap target is quite small since it's just the 20x20 view. I'm looking for a way to increase this to make it easier for users to interact with my slider. Is there a way to do this in SwiftUI?

I tried wrapping my thumb in Color.clear.overlay and setting the frame to 60x60. This works if the color is solid, like red, but with clear the tap target seems to revert back to visible pixels of 20x20.

You can see on this gif I'm able to drag the slider even when clicking outside of the thumb.

slider with red background

However, as soon as I change the color to clear, this area no longer receives interactions.

slider with clear background

keegan3d
  • 10,357
  • 9
  • 53
  • 77
  • 1
    clear background are untouchable in swift UI, I asked a question similar to this not long ago - I'd recommend putting a button behind it and making the background colour the same as the colour behind it instead of clear – Quinn Jul 29 '19 at 17:09
  • 1
    my question for reference: https://stackoverflow.com/questions/57191013/swiftui-cant-tap-in-spacer-of-hstack – Quinn Jul 29 '19 at 17:10
  • Here I just showed grey, but I want to show multiple colors in the background of the slider so it won't be a solid color. – keegan3d Jul 29 '19 at 17:11
  • that's too bad - I think this is just a swiftUI issue, you can submit a bug report, hopefully eventually they allow you to tap on clear things but right now I'm not sure what you can do – Quinn Jul 29 '19 at 17:12
  • another option might be to add a background that isn't technically clear, but the alpha is so low that it isn't noticeable - though I'm not sure if you would be able to make it 100% unnoticeable – Quinn Jul 29 '19 at 17:14

5 Answers5

66

Add a .contentShape(Rectangle()) after the frame.

Joris Kluivers
  • 11,894
  • 2
  • 48
  • 47
  • yes exactly. So in the example above create the 20x20 circle. Then add an extra .frame, and add .contentShape(Rectangle()) after that. – Pbk Dec 19 '19 at 07:00
  • I think it's worth mentioning that if you have a frame to set the visible size you can add another frame for the tappable area and after that add the contentShape. It looks weird but it doesn't work if you have .frame.contentShape.frame, it must be .frame.frame.contentShape – José Manuel Sánchez Mar 26 '21 at 11:36
15

I'm sure there are several ways to accomplish this, but this is how i made a big button with the whole area tappable:

Button(action: { someAction() }, label: {
                Text("OK")
                .frame(minWidth: 200)
                .contentShape(Rectangle())
                .padding()
            })
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(5.0)
brynbodayle
  • 6,546
  • 2
  • 33
  • 49
Chris
  • 7,830
  • 6
  • 38
  • 72
5

I had the same problem. Except I didn't want the expanded overlay to impact the rest of the layout. If you embed everything in a ZStack and put a rectangle before your interactive object, you can control the size of the gesture. Kind of like this:

Rectangle().frame(width: screen.width, height: 300).opacity(0.001)
                    .layoutPriority(-1)

Just needed to make sure to set the opacity to next to nothing so you can't see it, and the layout priority to -1 so it doesn't impact the view.

David Buck
  • 3,752
  • 35
  • 31
  • 35
Ben E.
  • 97
  • 1
  • 4
  • Exactly what I needed! However for me I didn't need to bother with setting the layoutPriority; it didn't affect anything. – wristbands Mar 14 '22 at 22:23
1

As a follow up to the answer above, You can enlarge the clickable rect all day using this as example.

ZStack {
    Image(systemName: secured ? "eye.slash" : "eye")
        .resizable()
        .renderingMode(.template)
        .aspectRatio(contentMode: .fit)
        .frame(width: 16, height: 16)

    Rectangle()
        .frame(width: 22, height: 20)
        .opacity(0.001)
        .onTapGesture {
            self.secured.toggle()
        }
}
Klajd Deda
  • 355
  • 2
  • 7
0

Move all modifers to the label in the button

Button {
    print("Tap")
} label: {
    Text("Button Content")
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .cornerRadius(8)

}

Now the whole button will be tappable

Jevon718
  • 354
  • 4
  • 10