You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在macOS的SwiftUI中实现List的单击与双击事件检测

macOS 下 SwiftUI List 行选中及手势冲突修复

我有一个包含若干行的List,每行带两个可点击图标和文本,需求是:

  • 单击/双击图标触发对应操作
  • 单击行内其他区域选中该行
  • 双击行内其他区域触发操作

现有代码在iOS上正常运行,但在macOS 13.4.1 (c) (22F770820d)里,行的单击选中功能失效,代码如下:

struct Line_List: View {
  @State private var selectedRow: Int? = nil
  var body: some View {
    List(1..<11, id: \.self, selection: $selectedRow) { index in
      Line(index: index)
    }
  }
}

struct Line: View {
  var index: Int
  
  var body: some View {
    HStack (alignment: .top) {
      Image(systemName: "1.circle.fill")
        .onTapGesture (count: 2){
          print("Icon 1 Double-Clicked")
        }
        .onTapGesture (count: 1){
          print("Icon 1 Single-Clicked")
        }        
      VStack (alignment: .leading){
        Text("Line: \(index)")
        Text("Some other Text")
      }
      Spacer()
      Image(systemName: "2.circle.fill")
        .onTapGesture (count: 2){
          print("Icon2 Double-Clicked")
        }
        .onTapGesture (count: 1){
          print("Icon2 Single-Clicked")
        }
      
    }
    .background(Color.green)
    .onTapGesture(count: 2) {  // does not work in MacOS
      print("Line Double-Clicked")
    }   
  }
}

问题根源

macOS上SwiftUI List的行选中逻辑和iOS不同,自定义视图的手势会拦截List的默认选中手势;同时双击手势的优先级设置不当,也会导致单击选中事件无法正常传递。

修改方案及代码

下面是修复后的完整代码,解决了选中失效和手势冲突问题:

struct Line_List: View {
    @State private var selectedRow: Int? = nil
    
    var body: some View {
        List(1..<11, id: \.self, selection: $selectedRow) { index in
            Line(index: index)
                .tag(index) // 标记行的选中标识,让List能识别选中目标
                .listRowSeparator(.hidden)
        }
        .listStyle(.plain) // macOS下使用plain样式更适配自定义行的选中逻辑
    }
}

struct Line: View {
    var index: Int
    
    var body: some View {
        HStack(alignment: .top) {
            // 图标手势保留原逻辑
            Image(systemName: "1.circle.fill")
                .onTapGesture(count: 2) {
                    print("Icon 1 Double-Clicked")
                }
                .onTapGesture(count: 1) {
                    print("Icon 1 Single-Clicked")
                }
            
            // 文本区域单独绑定手势,避免和图标手势冲突
            VStack(alignment: .leading) {
                Text("Line: \(index)")
                Text("Some other Text")
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .onTapGesture(count: 2) {
                print("Line Double-Clicked")
            }
            // 同时触发选中操作,绕开手势拦截
            .simultaneousGesture(TapGesture().onEnded {
                if let tableView = NSApp.keyWindow?.contentView?.findFirstSubview(ofType: NSTableView.self) {
                    tableView.selectRowIndexes(IndexSet(integer: index - 1), byExtendingSelection: false)
                }
            })
            
            Spacer()
            
            // 第二个图标手势保留原逻辑
            Image(systemName: "2.circle.fill")
                .onTapGesture(count: 2) {
                    print("Icon2 Double-Clicked")
                }
                .onTapGesture(count: 1) {
                    print("Icon2 Single-Clicked")
                }
        }
        .background(Color.green)
    }
}

// 扩展UIView,方便查找底层的NSTableView
extension NSView {
    func findFirstSubview<T: NSView>(ofType type: T.Type) -> T? {
        if let view = self as? T {
            return view
        }
        for subview in subviews {
            if let found = subview.findFirstSubview(ofType: type) {
                return found
            }
        }
        return nil
    }
}

关键修改说明

  1. 添加.tag(index):给每行绑定唯一标识,让List的selection绑定能正确识别选中的行。
  2. 拆分手势区域:把行的双击手势移到文本区域,避免和图标的手势互相干扰。
  3. 手动触发选中:通过查找底层的NSTableView并调用选中方法,绕开手势拦截导致的List默认选中失效问题。
  4. NSView扩展:方便快速找到List对应的NSTableView实例,简化手动选中的代码。

内容的提问来源于stack exchange,提问作者mica

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:05:36