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

SwiftUI Menu下拉菜单弹出位置修改(顶部改底部)咨询

问题:SwiftUI中Sheet内Menu组件的弹出方向修改

我正在学习Swift、SwiftUI及相关组件,目前在Menu组件的按钮弹出样式上遇到了问题。我的应用顶部栏有一个可触发Sheet的添加按钮,当Sheet中的Menu按钮被点击时,菜单选项会从顶部弹出,但Menu下方明明有可用空间,想咨询是否可以将菜单选项的弹出位置从顶部改为底部。

应用UI截图:
应用UI截图

我的代码如下:

Sheet展示的视图

struct EinkaufAddView: View {
    
    @State var artikelTextField: String = ""
    @State var anzahlTextField: String = ""
    @State var kategorieNamen: String = "Kategorie"
    
    var body: some View {
        NavigationStack {
            
            VStack {
                HStack {
                    VStack {
                        Section("Produkt") {
                            TextField("Hinzufügen...", text: $artikelTextField)
                                .frame(width: 200, height: 50)
                                .padding(.horizontal)
                                .background(Color.gray.opacity(0.3).cornerRadius(10))
                                .padding(.horizontal)
                        }
                    }
                    
                    VStack {
                        Section("Anzahl") {
                            TextField("0", text: $anzahlTextField)
                                .keyboardType(.numberPad)
                                .frame(height: 50)
                                .frame(maxWidth: .infinity)
                                .padding(.horizontal)
                                .background(Color.gray.opacity(0.3).cornerRadius(10))
                                .padding(.horizontal)
                        }
                    }
                }
                .padding(.bottom, 20)
                
                Section("Kategorie") {
                    Menu(kategorieNamen) {
                        Button("Obst") {
                            kategorieNamen = "Obst"
                        }
                        Button("Milchprodukte") {
                            kategorieNamen = "Obst"
                        }
                        Button("Fleisch") {
                            kategorieNamen = "Obst"
                        }
                        Button("Getränke") {
                            kategorieNamen = "Obst"
                        }
                    }
                    .frame(height: 50)
                    .frame(maxWidth: .infinity)
                    .padding(.horizontal)
                    .background(Color.gray.opacity(0.3).cornerRadius(10))
                    .padding(.horizontal)
                }
            }
            .padding(.top, 30)
            
            Spacer()
            
            Button(action: {
                
            }, label: {
                Text("Produkt hinzufügen")
                    .foregroundStyle(.white)
                    .font(.title2)
                    .bold()
                    .frame(height: 50)
                    .frame(maxWidth: .infinity)
                    .background(Color.blue.cornerRadius(25))
            })
            .padding()
            
            .navigationTitle("Artikel hinzufügen")
        }
    }
}

#Preview {
    EinkaufAddView()
}

展示Sheet的视图

struct Einkaufsliste: View {
    
    @State var showSheet: Bool = false
    @StateObject var vm = EinkaufViewModel()
    let kategorien: [Kategorie] = [
        Kategorie(title: "Obst", titleColor: Color.yellow, anzahl: 5),
        Kategorie(title: "Fleisch", titleColor: Color.purple, anzahl: 2),
        Kategorie(title: "Milchprodukte", titleColor: Color.blue, anzahl: 10),
        Kategorie(title: "Getränke", titleColor: Color.green, anzahl: 8)
    ]
    
    var body: some View {
        NavigationStack {
            ScrollView {
                HStack {
                    VStack {
                        ForEach(kategorien) { item in
                            NavigationLink {
                                EinkaufslisteDetailView(vm: vm, kategorieName: item.title)
                            } label: {
                                EinkaufsListView(kategorieNamen: item.title, anzahlKategorien: item.anzahl)
                            }
                            .buttonStyle(.plain)
                            .foregroundStyle(item.titleColor)
                        }
                    }
                }
            }
            .navigationBarItems(
                trailing: Button(action: { showSheet.toggle() }, label: {
                    Image(systemName: "plus.circle")
                        .resizable()
                        .frame(width: 30, height: 30)
                })
                .sheet(isPresented: $showSheet) {
                    EinkaufAddView()
                        .presentationDetents([.fraction(0.8)])
                }
            )
            .navigationTitle("Einkaufsliste 🍎")
        }
    }
}

#Preview {
    Einkaufsliste()
}

struct EinkaufsListView: View {
    
    let kategorieNamen: String
    let anzahlKategorien: Int
    
    var body: some View {
        ZStack() {
            RoundedRectangle(cornerRadius: 15)
                .stroke()
            
            HStack {
                Image(systemName: "apple.logo")
                    .padding(.horizontal)
                
                Spacer()
                
                Text(kategorieNamen)
                
                Spacer()
                
                ZStack {
                    RoundedRectangle(cornerRadius: 10)
                        .stroke()
                        .frame(width: 40, height: 40)
                    Text("\(anzahlKategorien)")
                }
                
                Image(systemName: "arrow.right")
                    .padding()
            }
        }
        .font(.title2)
        .fontWeight(.bold)
        .frame(width: 350, height: 60)
        .background(Color.gray.opacity(0.5).cornerRadius(15))
        .padding(.top)
    }
}

注:其余代码比较混乱,可忽略。我已经在网上查找相关资料但未找到有用信息,曾听说该设置无法修改,但仍想确认是否有误。


解答

结论

默认的Menu组件确实无法直接修改弹出方向,它的弹出位置由系统自动根据容器空间计算,在Sheet这类受限容器中,系统可能优先选择向上弹出。不过可以通过两种方式实现需求:

方案1:自定义弹出菜单

完全自定义一个类似Menu的组件,通过popover控制弹出方向,这是最可靠的解决方案:

struct CustomMenuView: View {
    @Binding var selectedCategory: String
    @State private var isMenuOpen = false
    
    let categories = ["Obst", "Milchprodukte", "Fleisch", "Getränke"]
    
    var body: some View {
        Button(action: { isMenuOpen.toggle() }) {
            Text(selectedCategory)
                .frame(height: 50)
                .frame(maxWidth: .infinity)
                .padding(.horizontal)
                .background(Color.gray.opacity(0.3).cornerRadius(10))
        }
        .popover(isPresented: $isMenuOpen, attachmentAnchor: .point(.bottom), arrowEdge: .top) {
            VStack(spacing: 0) {
                ForEach(categories, id: \.self) { category in
                    Button(action: {
                        selectedCategory = category
                        isMenuOpen = false
                    }) {
                        Text(category)
                            .frame(maxWidth: .infinity, minHeight: 44)
                            .padding(.horizontal)
                    }
                }
            }
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
        }
    }
}

在EinkaufAddView中替换原有Menu:

Section("Kategorie") {
    CustomMenuView(selectedCategory: $kategorieNamen)
        .padding(.horizontal)
}

方案2:尝试修改MenuStyle(效果有限)

虽然无法直接指定方向,但可以尝试使用BorderlessButtonMenuStyle,不过这种方式仍受系统布局限制,不一定能完全满足需求:

Menu(kategorieNamen) {
    // 菜单按钮内容
}
.menuStyle(BorderlessButtonMenuStyle())

补充说明

系统默认的Menu基于UIKit菜单实现,SwiftUI并未暴露修改弹出方向的API,因此自定义组件是实现需求的最优解。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:47:04