SwiftUI 无法点击 HStack 的 Spacer

SwiftUI can't tap in Spacer of HStack

我有一个列表视图,列表的每一行都包含一个带有一些文本视图和图像的 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() 所在的部分)时,点击操作未注册。仅当我点击文本或图像时才会发生点击操作。

有没有其他人遇到过这个问题/知道解决方法?

我已经就此提交了 feedback,建议您也这样做。

与此同时,不透明的 Color 应该和 Spacer 一样有效。不幸的是,您必须匹配背景颜色,这假设您没有任何东西可以显示在按钮后面。

为什么不直接使用 Button

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

我已经能够通过将 Spacer 包裹在 ZStack 中并添加不透明度非常低的纯色来解决这个问题:

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

据我最近了解到还有:

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

很适合我。

基于吉姆回答的简单扩展

extension Spacer {
    /// 
    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
}

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

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

})

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

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

尽管接受的答案允许模仿按钮功能,但在视觉上并不令人满意。不要将 Button 替换为 .onTapGestureUITapGestureRecognizer,除非您只需要一个接受手指点击事件的区域。这样的解决方案被认为是 hacky,不是好的编程实践。

要解决您的问题,您需要实施 BorderlessButtonStyle ⚠️

示例

创建一个通用单元格,例如SettingsNavigationCell.

SettingsNavigationCell

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
          }
        }
      }
    }
  }
}

有点像所说的一切的精神:

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)
    }
}

已更新:

我注意到 Color 在放入堆栈时并不总是与 Spacer 相同,所以我建议不要使用 Spacer 扩展,除非你知道那些差异。 (间隔器在堆栈的单个方向上推动(如果在 VStack 中,它垂直推动,如果在 HStack 中,它水平推出,而 Color 视图向内推出所有方向。)

出于可访问性原因,我认为最好的方法是将 HStack 包装在 Button 标签内,为了解决 Spacer cannot be tap 的问题,您可以添加一个 .contentShape(Rectangle()) HStack.

所以根据您的代码将是:

    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())
    }