只在文本部分点击 SwiftUI 按钮

我的按钮的背景区域没有检测到用户交互。与该按钮交互的唯一方式是点击按钮的文本/标签区域。如何使整个按钮可点击?

struct ScheduleEditorButtonSwiftUIView: View {


@Binding  var buttonTagForAction : ScheduleButtonType
@Binding  var buttonTitle        : String
@Binding  var buttonBackgroundColor : Color




let buttonCornerRadius = CGFloat(12)


var body: some View {


Button(buttonTitle) {
buttonActionForTag(self.buttonTagForAction)
}.frame(minWidth: (UIScreen.main.bounds.size.width / 2) - 25, maxWidth: .infinity, minHeight: 44)


.buttonStyle(DefaultButtonStyle())
.lineLimit(2)
.multilineTextAlignment(.center)
.font(Font.subheadline.weight(.bold))
.foregroundColor(Color.white)


.border(Color("AppHighlightedColour"), width: 2)
.background(buttonBackgroundColor).opacity(0.8)


.tag(self.buttonTagForAction)
.padding([.leading,.trailing], 5)
.cornerRadius(buttonCornerRadius)


}
}
28154 次浏览

This fixes the issue on my end:

var body: some View {
GeometryReader { geometry in
Button(action: {
// Action
}) {
Text("Button Title")
.frame(
minWidth: (geometry.size.width / 2) - 25,
maxWidth: .infinity, minHeight: 44
)
.font(Font.subheadline.weight(.bold))
.background(Color.yellow).opacity(0.8)
.foregroundColor(Color.white)
.cornerRadius(12)


}
.lineLimit(2)
.multilineTextAlignment(.center)
.padding([.leading,.trailing], 5)
}
}

button

Is there a reason why you are using UIScreen instead of GeometryReader?

I think this is a better solution, add the .frame values to the Text() and the button will cover the whole area 😉

Button(action: {
//code
}) {
Text("Click Me")
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .center)
.foregroundColor(Color.white)
.background(Color.accentColor)
.cornerRadius(7)
}

A bit late to the answer, but I found two ways to do this —

Option 1: Using Geometry Reader

Button(action: {
}) {
GeometryReader { geometryProxy in
Text("Button Title")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.foregroundColor(Color.white)
.frame(width: geometryProxy.size.width - 20 * 2) // horizontal margin
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.cornerRadius(6)
}
}

Option 2: Using HStack with Spacers

HStack {
Spacer(minLength: 20) // horizontal margin
Button(action: {


}) {
Text("Hello World")
.font(Font.custom("SFProDisplay-Semibold", size: 19))
.frame(maxWidth:.infinity)
.padding([.top, .bottom], 10) // vertical padding
.background(Color.yellow)
.foregroundColor(Color.white)
.cornerRadius(6)
}
Spacer(minLength: 20)
}.frame(maxWidth:.infinity)

My thought process here is that although option 1 is more succinct, I would choose option 2 since it's less coupled to its parent's size (through GeometryReader) and more in line of how I think SwiftUI is meant to use HStack, VStack, etc.

The proper solution is to use the .contentShape() API.

Button(action: action) {
HStack {
Spacer()
Text("My button")
Spacer()
}
}
.contentShape(Rectangle())

You can change the provided shape to match the shape of your button; if your button is a RoundedRectangle, you can provide that instead.

You can define content Shape for hit testing by adding modifier: contentShape(_:eoFill:)

And important thing is you have to apply inside the content of Button.

Button(action: {}) {
Text("Select file")
.frame(width: 300)
.padding(100.0)
.foregroundColor(Color.black)
.contentShape(Rectangle()) // Add this line
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())

Another

Button(action: {}) {
VStack {
Text("Select file")
.frame(width: 100)
Text("Select file")
.frame(width: 200)
}
.contentShape(Rectangle()) // Add this inside Button.
}
.background(Color.green)
.cornerRadius(4)
.buttonStyle(PlainButtonStyle())

Short Answer

Make sure the Text (or button content) spans the length of the touch area, AND use .contentShape(Rectangle()).

Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}

Long Answer

There are two parts:

  1. The content (ex. Text) of the Button needs to be stretched
  2. The content needs to be considered for hit testing

To stretch the content (ex. Text):

// Solution 1 for stretching content
HStack {
Text("Hello")
Spacer()
}


// Solution 2 for stretching content
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)


// Alternatively, you could specify a specific frame for the button.

To consider content for hit testing use .contentShape(Rectangle()):

// Solution 1
Button(action:{}) {
HStack {
Text("Hello")
Spacer()
}
.contentShape(Rectangle())
}


// Solution 2
Button(action:{}) {
Text("Hello")
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}

You might be doing this:

Button { /*to do something on button click*/}
label: { Text("button text").foregroundColor(Color.white)}
.frame(width: 45, height: 45, alignment: .center)
.background(Color.black)

Solution:

Button(action: {/*to do something on button click*/ })
{
HStack {
Spacer()
Text("Buttton Text")
Spacer() } }
.frame(width: 45, height: 45, alignment: .center)
.foregroundColor(Color.white)
.background(Color.black).contentShape(Rectangle())

I was working with buttons and texts that need user interaction when I faced this same issue. After looking and testing many answers (including some from this post) I ended up making it works in the following way:

For buttons:

/* WITH IMAGE */
Button {
print("TAppeD")
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
/* WITH TEXT */
Button {
print("TAppeD")
} label: {
Text("My button")
.frame(height: 80)
}
 

For Texts:

Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
print("TAppeD")
}

In the case of the texts, I only need the .contentShape(Rectangle()) modifier when the Text doesn't have a .background in order to make the entire Text frame responsive to tap gesture, while with buttons I use my Text or Image view with a frame and neither a .background nor a .contentShape is needed.

Image of the following code in preview (I'm not allowed to include pictures yet )

import SwiftUI


struct ContentView: View {
@State var tapped: Bool = true
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 19)
.frame(width: 40, height: 40)
.foregroundColor(tapped ? .red : .green)
Spacer()
HStack (spacing: 0) {
Text("PP")
.frame(width: 40, height: 40)
.contentShape(Rectangle())
.onTapGesture {
tapped.toggle()
}
                

Button {
print("TAppeD")
tapped.toggle()
} label: {
Image(systemName: "plus")
.frame(width: 40, height: 40)
}
.background(Color.red)
                

Button {
print("TAppeD")
tapped.toggle()
} label: {
Text("My button")
.frame(height: 80)
}
.background(Color.yellow)
}
            

Spacer()
}
}
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

this way makes the button area expand properly but if the color is .clear, it dosen't work🤷‍♂️

                Button(action: {
doSomething()
}, label: {
ZStack {
Color(.white)
Text("some texts")
}
})

When I used HStack then it worked for button whole width that's fine, But I was facing issue with whole button height tap not working at corners and I fixed it in below code:

Button(action:{
print("Tapped Button")
}) {
VStack {
//Vertical whole area covered
Text("")
Spacer()
HStack {
//Horizontal whole area covered
Text("")
Spacer()
}
}
}