带有披露指示器的 SwiftUI 列表按钮

问题描述

我有一个包含一些项目的列表的 SwiftUI 视图。其中一些是指向其他屏幕的链接(因此我使用 NavigationLink 来执行此操作),而其他一些是我想在当前屏幕上执行的操作(例如显示操作表的按钮)。

我正在寻找一种方法,让 SwiftUI Button 中的 List 与披露指示符一起显示(右侧标志处的 V 形符号,显示NavigationLink)。

这可能吗?

例如

struct ExampleView: View {
    @State private var showingActionSheet = false
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Navigation Link",destination: Text("xx"))
                Button("Action Sheet") {
                    self.showingActionSheet = true
                }
                .foregroundColor(.black)
            }
            .listStyle(GroupedListStyle())
            .actionSheet(isPresented: $showingActionSheet) {
                ActionSheet(title: Text("Title"),buttons: [
                    .default(Text("Do Something")) {  },.cancel()
                ])
            }
        }
    }
}

当前行为:

Current Behaviour

想要的行为:

Wanted Behaviour

解决方法

我的回答使用了 SwiftUI-Introspect 框架,用于:

从 SwiftUI 内省底层 UIKit 组件

在这种情况下,它用于在按下 NavigationLink 后取消选择行。

我认为带有普通强调色但没有 NavigationLink 的按钮对用户来说更直观,但如果这正是您所需要的,它就是这里。以下答案应该适合您:

import Introspect
import SwiftUI


struct ExampleView: View {
    
    @State private var showingActionSheet = false
    @State private var tableView: UITableView?
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Navigation Link",destination: Text("xx"))
                
                NavigationLink(
                    destination: EmptyView(),isActive: Binding<Bool>(
                        get: { false },set: { _ in
                            showingActionSheet = true
                            
                            DispatchQueue.main.async {
                                deselectRows()
                            }
                        }
                    )
                ) {
                    Text("Action Sheet")
                }
            }
            .introspectTableView { tableView = $0 }
            .listStyle(GroupedListStyle())
            .actionSheet(isPresented: $showingActionSheet) {
                ActionSheet(title: Text("Title"),buttons: [
                    .default(Text("Do Something")) {  },.cancel()
                ])
            }
        }
    }
    
    private func deselectRows() {
        if let tableView = tableView,let selectedRow = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: selectedRow,animated: true)
        }
    }
}
,

可能的方法是使用自定义人字形制作行,如下面的演示(使用 Xcode 12.1 / iOS 14.1 测试)

demo

struct ExampleView: View {
    @State private var showingActionSheet = false
    
    var body: some View {
        NavigationView {
            List {
                HStack {
                    Text("Navigation Link")

                    // need to hide navigation link to use same chevrons
                    // because default one is different
                    NavigationLink(destination: Text("xx")) { EmptyView() }
                    Image(systemName: "chevron.right")
                        .foregroundColor(Color.gray)
                }
                HStack {
                    Button("Action Sheet") {
                        self.showingActionSheet = true
                    }
                    .foregroundColor(.black)
                    Spacer()
                    Image(systemName: "chevron.right")
                        .foregroundColor(Color.gray)
                }
            }
            .listStyle(GroupedListStyle())
            .actionSheet(isPresented: $showingActionSheet) {
                ActionSheet(title: Text("Title"),.cancel()
                ])
            }
        }
    }
}