Кнопка списка SwiftUI с индикатором раскрытия

#ios #swift #swiftui #swiftui-list

#iOS #swift #swiftui #swiftui-список

Вопрос:

У меня есть представление SwiftUI, которое состоит из списка с некоторыми элементами. Некоторые из них являются ссылками на другие экраны (поэтому я использую NavigationLink для этого), а другие — действия, которые я хочу выполнить на текущем экране (например, кнопка для отображения таблицы действий).

Я ищу способ отображения a Button в SwiftUI List с индикатором раскрытия (шеврон на правом знаке, который показан для 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()
                ])
            }
        }
    }
}
 

Текущее поведение:

Текущее поведение

Требуемое поведение:

Требуемое поведение

Ответ №1:

В моем ответе используется фреймворк SwiftUI-Introspect, используемый для:

Проанализируйте базовые компоненты UIKit от SwiftUI

В этом случае она используется для отмены выбора строки после 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)
        }
    }
}
 

Комментарии:

1. Вот как я реализовал внутри раздела в представлении списка . Я думаю, это похоже на то, что предложил @George выше. HStack { Button { showPopover.toggle() } label: { NavigationLink(destination: {}, label: { Text("Version Number") Spacer() Text("1.1.0") }) .

Ответ №2:

Возможный подход заключается в создании строки с пользовательским шевроном, как в демонстрации ниже (протестировано с Xcode 12.1 / iOS 14.1)

ДЕМОНСТРАЦИЯ

 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"), buttons: [
                    .default(Text("Do Something")) {  },
                    .cancel()
                ])
            }
        }
    }
}
 

Ответ №3:

Я использовал ZStack, чтобы поместить a NavigationLink с пустой меткой за фактическим содержимым метки. Таким образом, вы получите правильный символ шеврона. Для isActive привязки вы можете просто использовать .constant(false) привязку, которая всегда будет возвращать false и не может быть изменена.

В результате появится строка, которая выглядит точно так же, как навигационная ссылка, но ведет себя как кнопка. К сожалению, это также включает в себя недостаток, заключающийся в том, что пользователь должен щелкнуть по содержимому ярлыка (тексту), чтобы активировать кнопку, и не может просто щелкнуть по пустому месту ячейки.

Скриншот

 struct ExampleView: View {
    @State private var showingActionSheet = false
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Navigation Link", destination: Text("xx"))
                Button {
                    self.showingActionSheet = true
                } label: {
                    // Put a NavigationLink behind the actual label for the chevron
                    ZStack(alignment: .leading) {
                        // NavigationLink that can never be activated
                        NavigationLink(
                            isActive: .constant(false),
                            destination: { EmptyView() },
                            label: { EmptyView() }
                        )
                        // Actual label content
                        Text("Action Sheet")
                    }
                }
                // Prevent the blue button tint
                .buttonStyle(.plain)
            }
            .listStyle(GroupedListStyle())
            .actionSheet(isPresented: $showingActionSheet) {
                ActionSheet(title: Text("Title"), buttons: [
                    .default(Text("Do Something")) {  },
                    .cancel()
                ])
            }
        }
    }
}
 

Ответ №4:

 struct NavigationButton: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        ZStack(alignment: .leading) {
            NavigationLink(
                isActive: .constant(false),
                destination: { EmptyView() },
                label: { EmptyView() }
            )
            configuration.label
        }
    }
}
 

Пример:

 NavigationView {
            List {
                
                Button(action: {
                    openURL(URL(string: "https://www.apple.com")!)
                }) {
                    Text("Visit Apple")
                }
                .buttonStyle(NavigationButton())
                
            }
}