在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 } }
关键修改说明
- 添加
.tag(index):给每行绑定唯一标识,让List的selection绑定能正确识别选中的行。 - 拆分手势区域:把行的双击手势移到文本区域,避免和图标的手势互相干扰。
- 手动触发选中:通过查找底层的NSTableView并调用选中方法,绕开手势拦截导致的List默认选中失效问题。
- NSView扩展:方便快速找到List对应的NSTableView实例,简化手动选中的代码。
内容的提问来源于stack exchange,提问作者mica
相关产品推荐
相关产品推荐

