SwiftUI无法在HStack的Spacer中点击

127

我有一个列表视图(List view),每一行都包含一个HStack,其中包含一些文本视图和一个图像,就像这样:

HStack{
    Text(group.name)
    Spacer()
    if (groupModel.required) { Text("Required").color(Color.gray) }
    Image("ic_collapse").renderingMode(.template).rotationEffect(Angle(degrees: 90)).foregroundColor(Color.gray)
}.tapAction { self.groupSelected(self.group) }

除了在文本和图像之间的空白部分(Spacer()所在的位置)轻触时未注册轻触操作外,这似乎很有效。只有在我轻触文本或图像时才会发生轻触操作。

是否有其他人遇到过此问题/知道解决方法?


诚实的问题:你为什么期望有人在“spacer”中输入?它本质上是空格。也许你的UI期望你在UIKit中输入一些东西?如果是这样,请详细说明。 - user7014451
11
每一行都只是一些文本,以一个“大于号”结尾,看起来像这样 Object One > - 我希望用户能够在任何一行上点击(注意,这里的空格没有格式化,只要想象一下文本和>之间有一个空格)。 - Quinn
15
@dfd 我认为让用户能够在表格单元格的任何位置进行点击是相当标准的行为,这也是 UIKit 表格视图上具有 didSelectRowAt 方法的原因。 - Quinn
1
当然,我同意。但是也许可以尝试其他东西来代替“Spacer”。也许将整个内容转换为“Button”?在SwiftUI中,“Spacer”只是用于间距。 - user7014451
1
简直不敢相信我要说这句话...但是没错,这是一个老但好的例子!当我建议使用按钮时,我想到了这个链接:https://alejandromp.com/blog/2019/06/09/playing-with-swiftui-buttons/ - user7014451
13个回答

285

我最近学到还有这个:

HStack {
  ...
}
.contentShape(Rectangle())
.onTapGesture { ... }

对我来说运作良好。


1
当应用于我的“Button”内容时,这个方法完美地工作了。在这种情况下不需要使用“onTapGesture”。 - Kris McGinnes
在 HStack 中使用间距器。 - LondonGuy
10
Hacking With Swift提供了更多解释:https://www.hackingwithswift.com/quick-start/swiftui/how-to-control-the-tappable-area-of-a-view-using-contentshape。 - David L
6
这个解决方案的必要性让我对SwiftUI感到失望。不管怎样,这是一个很好的解决方案! - C. Skjerdal
吃了亏才知道修饰符的顺序很重要,确保contentShapeonTapGesture之前。 - robotsquidward
显示剩余2条评论

25

最好使用 按钮 以提高可访问性。

Button(action: { self.groupSelected(self.group) }) {
    HStack {
        Text(group.name)
        Spacer()
        if (groupModel.required) { Text("Required").color(Color.gray) }
        Image("ic_collapse").renderingMode(.template).rotationEffect(Angle(degrees: 90)).foregroundColor(Color.gray)
    }
}.foregroundColor(.primary)

如果您不希望按钮将重点颜色应用于Text(group.name),您必须像我在示例中所做的那样设置foregroundColor


这会在轻触时更改背景颜色,尽管您可能可以更改它:https://dev59.com/sFMI5IYBdhLWcg3wUZyd@jim-marquardt的ZStack解决方案对我很有效。 - choxi
navigationLink 的行为不同。 - iGhost

17
我认为出于可访问性的原因,最好的方法是将 HStack 包装在 Button 标签中,并为了解决无法点击 Spacer 的问题,您可以向 HStack 添加 .contentShape(Rectangle())
因此,根据您的代码,应该是这样的:
Button {
    self.groupSelected(self.group)
} label: {
    HStack {
        Text(group.name)
        Spacer()
        if (groupModel.required) {
            Text("Required").color(Color.gray)
        }
        Image("ic_collapse")
            .renderingMode(.template)
            .rotationEffect(Angle(degrees: 90))
            .foregroundColor(Color.gray)
    }
    .contentShape(Rectangle())
}

17

在每个视图上都像魔法一样工作:

extension View {
    func onTapGestureForced(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        self
            .contentShape(Rectangle())
            .onTapGesture(count:count, perform:action)
    }
}

1
这太棒了。 - DaWiseguy

9

根据Jim的回答制作的简单扩展

extension Spacer {
    /// https://dev59.com/olMI5IYBdhLWcg3wDXDi#57416760
    public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        ZStack {
            Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
            self
        }
    }
}

现在这个可以工作了。

Spacer().onTapGesture {
    // do something
}

你能详细描述一下这段代码在做什么吗?每次用户点击时,它是在创建一个新的 ZStack 吗?它会清理这部分内存吗?顺便说一句,这个代码运行得很好 :)。 - Joseph Astrahan
@JosephAstrahan 不,它只是在当前视图后面创建一个不透明的视图,以注册用户的点击。 - Casper Zandbergen
在点击完成后,它是否会销毁(释放内存)视图? - Joseph Astrahan
那么它创建的是一次性视图吗?我想我的主要问题是不会让用户反复点击创建该视图。我之所以问这个问题,是因为它在on TapGesture函数内部,这意味着它会一遍又一遍地创建该视图。 - Joseph Astrahan
这是一个干净的答案,特别适用于跟踪虚拟点击。 - DaWiseguy

9

我已经解决了这个问题,方法是将Spacer包装在ZStack中,并添加一个非常低不透明度的纯色:

ZStack {
    Color.black.opacity(0.001)
    Spacer()
}

4

我只是为 HStack 添加了背景颜色(除了 clear 颜色)。

HStack {
        Text("1")
        Spacer()
        Text("1")
}.background(Color.white)
.onTapGesture(count: 1, perform: {

})

2
虽然被接受的答案允许模仿按钮功能,但从视觉上来看并不理想。除非您只需要一个接受手指点击事件的区域,否则不要用 .onTapGestureUITapGestureRecognizer 替换 Button。这些解决方案被认为是 hacky 的,不是良好的编程实践。
为了解决您的问题,您需要实现 BorderlessButtonStyle ⚠️
示例:
创建一个通用的单元格,例如 SettingsNavigationCellSettingsNavigationCell
struct SettingsNavigationCell: View {
  
  var title: String
  var imageName: String
  let callback: (() -> Void)?

  var body: some View {
    
    Button(action: {
      callback?()
    }, label: {

      HStack {
        Image(systemName: imageName)
          .font(.headline)
          .frame(width: 20)
        
        Text(title)
          .font(.body)
          .padding(.leading, 10)
          .foregroundColor(.black)
        
        Spacer()
        
        Image(systemName: "chevron.right")
          .font(.headline)
          .foregroundColor(.gray)
      }
    })
    .buttonStyle(BorderlessButtonStyle()) // <<< This is what you need ⚠️
  }
}

设置视图

struct SettingsView: View {
  
  var body: some View {
    
    NavigationView {
      List {
        Section(header: "Appearance".text) {
          
          SettingsNavigationCell(title: "Themes", imageName: "sparkles") {
            openThemesSettings()
          }
          
          SettingsNavigationCell(title: "Lorem Ipsum", imageName: "star.fill") {
            // Your function
          }
        }
      }
    }
  }
}

1

在所有已经说过的事情的基础上:

struct NoButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .background(Color.black.opacity(0.0001))
    }
}
extension View {
    func wrapInButton(action: @escaping () -> Void) -> some View {
        Button(action: action, label: {
            self
        })
        .buttonStyle(NoButtonStyle())
    }
}

我创建了NoButtonStyle,因为BorderlessButtonStyle的动画与.onTapGesture不同。

示例:

HStack {
    Text(title)
    Spacer()
    Text("Select Value")
    Image(systemName: "arrowtriangle.down.square.fill")
}
.wrapInButton {
    isShowingSelectionSheet = true
}

另一个选项:
extension Spacer {
    func tappable() -> some View {
        Color.blue.opacity(0.0001)
    }
}

更新:

我注意到当在堆栈中使用时,ColorSpacer 的行为并不总是相同的,因此,除非您了解这些差异,否则建议不要使用该 Spacer 扩展。(如果在 VStack 中,则 Spacer 只会沿着垂直方向推进;如果在 HStack 中,则只会沿着水平方向推出。而 Color 视图则会朝所有方向推出。)


1
我已经在此提交了反馈,建议您也这样做。
同时,一个不透明的Color应该和Spacer一样有效。不幸的是,您需要匹配背景颜色,并且这假设您没有任何东西可以在按钮后面显示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接