我正在尝试将上下文菜单添加到 SwiftUI 中列表 -> 部分 -> ScrollView(水平)内的按钮。但是,当我按住按钮时,上下文菜单将应用于整个部分,而不仅仅是按钮。这是相关代码:
为什么上下文菜单应用于整个部分而不是仅应用于按钮?如何使上下文菜单仅出现在按钮上?
struct HistoryView: View {
@StateObject private var viewModel = HistoryViewModel()
@State private var selectedFilter: String = "all"
@State private var oldSelectedFilter: String = "all"
@State private var selectedItem: HistoryItem?
@State private var selectedHistoryEdit: HistoryItem?
@State private var showingDetailSheet: Bool = false
@State private var showingFilterSheet: Bool = false
@Environment(\.colorScheme) var colorScheme
@State private var shouldRefreshParentView: Bool = false
@State private var showingTemplateAddSheet = false
@State private var selectedTemplateItem: TemplateItem?
@AppStorage("templatesLimit") var templatesLimit: Int = 0
var body: some View {
VStack {
List {
Section {
filterSection
summarySection
}
if viewModel.isLoadingTemplates, viewModel.isLoading {
Section(header: Text("")
.frame(maxWidth: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 15) {
// Show a few skeletons while loading
ForEach(0..<5, id: \.self) { _ in
SkeletonTemplateView()
}
}
.padding(.leading, 0) // Убираем левый отступ у HStack
.padding(.trailing) // Можно настроить правый отступ при необходимости
}
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets()) // Убираем стандартные отступы у строки
}
} else {
templateSection
}
}
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("История")
.sheet(item: $selectedItem) { item in
HistoryDetailView(item: item, categoryDictionary: categoryDictionary, accountDictionary: accountDictionary, shouldRefreshParentView: $shouldRefreshParentView, viewModel: viewModel)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
.sheet(item: $selectedHistoryEdit) { item in
NewView(historyItem: item, shouldRefreshParentView: $shouldRefreshParentView) // Ensure item is defined and passed properly
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
.onAppear {
Task {
await viewModel.loadData()
}
}
}
.refreshable {
Task {
await viewModel.refreshData()
}
}
.onChange(of: shouldRefreshParentView) { newValue in
if newValue {
Task {
await viewModel.refreshData()
}
shouldRefreshParentView = false
}
}
}
private var templateSection: some View {
Section(header: Text("Шаблоны")
.font(.callout)
.frame(maxWidth: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
) {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 15) {
if templatesLimit > 0 {
Button(action: {
showingTemplateAddSheet = true
}) {
VStack {
Image(systemName: "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.padding()
.background(colorScheme == .dark ? Color.gray.opacity(0.2) : Color.white)
.cornerRadius(10)
Text("Создать")
.foregroundColor(.primary)
.font(.footnote)
.multilineTextAlignment(.center)
.padding(.top, 5)
.frame(width: 80)
}
.frame(width: 80, height: 100)
}
.sheet(isPresented: $showingTemplateAddSheet) {
NewTemplateView(templateItem: nil, shouldRefreshParentView: $shouldRefreshParentView, defaultType: filterTypeView)
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
.contextMenu {
Button("HELLO"){
}
}
}
ForEach(filteredTemplates) { item in
Button(action: {
selectedTemplateItem = item
}) {
VStack {
Image(systemName: "\(item.iconName)")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.padding()
.background(colorScheme == .dark ? Color.gray.opacity(0.2) : Color.white)
.cornerRadius(10)
Text(item.name)
.foregroundColor(.primary)
.font(.footnote)
.multilineTextAlignment(.center)
.padding(.top, 5)
.frame(width: 80)
}
.frame(width: 80, height: 100)
}
}
// Индикатор загрузки данных в конце списка
if !viewModel.templateItems.isEmpty {
if viewModel.isLoadingTemplatesMore {
ForEach(0..<5, id: \.self) { _ in
SkeletonTemplateView()
}
} else if !viewModel.isAllTemplatesDataLoaded {
SkeletonTemplateView()
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
Task {
await viewModel.loadMoreTemplates()
}
}
}
.padding()
}
}
}
.padding(.leading, 0)
.padding(.trailing)
}
.listRowBackground(Color.clear) // Прозрачный фон для секции
.listRowInsets(EdgeInsets()) // Убираем стандартные отступы у строки
.sheet(item: $selectedTemplateItem) { item in
NewView(historyItem: nil, shouldRefreshParentView: $shouldRefreshParentView, templateItem: item) // Ensure item is defined and passed properly
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil)
}
}
}
这似乎是由于列表的布局行为造成的。除了在这种情况下不使用列表之外,我不知道任何解决方案。
这里有一些可重现的代码,您可以取消注释列表以查看差异。
import SwiftUI
struct ButtonContextMenuTest: View {
var body: some View {
// List {
Section {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 15) {
ForEach(0..<8) { index in
Button(action: {
//action here...
}) {
VStack(spacing: 10) {
Image(systemName: "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.cornerRadius(10)
Text("Button")
.foregroundColor(.primary)
.font(.footnote)
.multilineTextAlignment(.center)
.padding(.top, 5)
.frame(width: 80)
}
.frame(width: 80, height: 100)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12))
}
.contextMenu {
Button("HELLO"){
}
}
}
}
.padding()
}
}
}
// }
}
#Preview {
ButtonContextMenuTest()
.preferredColorScheme(.dark)
}
当不在列表中时,contextMenu 的行为符合预期: