skip to Main Content

I am using a SwiftUI Button inside a HStack as below.

import SwiftUI

    struct ContentView: View {
    
        @State var tapCount: Int = 0
    
        var body: some View {
            VStack(spacing: 10) {
                HStack {
                    Image(systemName: "globe")
                        .frame(width: 48, height: 48)
                        .background(.green)
                    Text("Headline")
                    Spacer()
                    Text("See More")
                        .foregroundStyle(.white)
                    Button(action: {
                        tapCount += 1
                    }, label: {
                        Image(systemName: "x.circle.fill")
                            .frame(width: 48, height: 48)
                            .background(.yellow)
                            ..contentShape(Rectangle()) // here
                    })
                    .contentShape(Rectangle()) // here
                }
                .background(.pink)
                .padding(.horizontal, 16)
    
                Text("Button Tap Count: (tapCount)")
                    .font(.system(size: 25))
            }
        }
    }

And UI looks like below

enter image description here

And the button still tappable until the letter e of See More Text why is that? and I tried to use .contentShape(Rectangle()) for the image and button but non of them fixed the issue. any idea how to fix this?

2

Answers


  1. You are likely using a mouse pointer to click the button, and this is why you find this behaviour confusing. In reality, the user most likely uses their finger to tap the button, which covers an area, not just a point, and the iOS simulator simulates this. Hold the option key and you will see the area used for hit-testing. When I hover over the "e", that area clearly overlaps with the button,

    enter image description here

    In your real app, I assume "See More" is also tappable. If you add onTapGesture to that:

    Text("See More")
        .foregroundStyle(.white)
        .onTapGesture {
            print("See More!")
        }
    

    Then when clicking on the "e", iOS correctly triggers the "See More" action, instead of the button’s action. This is because the finger’s area overlaps with "See More", more than it does with the button.

    You can also reduce the tappable area with an insetted shape,

    Button(action: {
        tapCount += 1
    }, label: {
        Image(systemName: "x.circle.fill")
            .frame(width: 48, height: 48)
            .background(.yellow)
    })
    .contentShape(Rectangle().inset(by: 10)) // <----
    

    But people will likely find it much harder to tap the button this way.

    You can also write your own insetted shape instead of Rectangle,

    struct InsetRectangle: Shape {
        let insets: UIEdgeInsets
        
        nonisolated func path(in rect: CGRect) -> Path {
            Path(rect.inset(by: insets))
        }
    }
    
    // usage:
    
    .contentShape(InsetRectangle(insets: .init(top: 0, left: 10, bottom: 0, right: 0)))
    
    Login or Signup to reply.
  2. Instead of clipShape modifier, you should be using contentShape modifier on Button to define the shape of your button content. So, only that part will be tappable.
    Here is the corrected code.

    Button(action: {
        tapCount += 1
    }, label: {
        Image(systemName: "x.circle.fill")
            .frame(width: 48, height: 48)
            .background(.yellow)
    })
    .contentShape(Rectangle())
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search