SwiftUI在.safeAreaInset内添加Menu出现异常布局问题求助
问题背景
在safeAreaInset中嵌入Menu组件时,键盘弹出后Menu按钮会出现轻微上移,点击Menu还会触发异常布局,该问题在iOS15和iOS16版本中均存在。
初始测试代码
import SwiftUI struct TestMenuInSafeAreaInset: View { @State private var message = "" var body: some View { VStack { Rectangle() .fill(Color.blue) } .safeAreaInset(edge: .bottom) { HStack { TextField("输入消息", text: $message) .padding() .background(Color.brown) .cornerRadius(12) .foregroundColor(.white) Menu { Button { } label: { Text("确认") } Button { } label: { Text("取消") } } label: { Image(systemName: "square.and.arrow.up.fill") .tint(.white) .padding() .background(Color.brown) .cornerRadius(50) } } .padding() } } } struct TestMenuInSafeAreaInset_Previews: PreviewProvider { static var previews: some View { TestMenuInSafeAreaInset() } }
问题现象
- 正常状态:

- 键盘首次弹出时,右侧Menu按钮上移:

无效尝试
给Menu添加固定尺寸后问题仍复现(测试环境:iPhone14 Pro模拟器、iOS16):
struct TestMenuInSafeAreaInset: View { @State private var message = "" var body: some View { VStack { TextField("输入用户名", text: $message) .padding() .background(Color.gray.opacity(0.3)) .cornerRadius(12) .padding() Spacer() } .safeAreaInset(edge: .bottom) { HStack { Spacer() .frame(height: 70) Menu { Button { } label: { Text("确认") } Button { } label: { Text("取消") } } label: { Image(systemName: "square.and.arrow.up.fill") .padding() .tint(.white) .background(Color.brown) .cornerRadius(50) } .frame(width: 50, height: 50) } .padding(.horizontal, 20) .background(Color.blue) } } }
此时现象:
可行解决方案
该问题属于SwiftUI中Menu与safeAreaInset、键盘交互的布局bug,以下是三种有效的解决方式:
方案1:用UIKit Menu替代SwiftUI Menu
通过UIViewRepresentable封装UIKit的UIMenu,规避SwiftUI的布局冲突:
import SwiftUI struct UIKitMenuButton: UIViewRepresentable { let menuActions: [UIAction] func makeUIView(context: Context) -> UIButton { let button = UIButton(type: .system) button.setImage(UIImage(systemName: "square.and.arrow.up.fill"), for: .normal) button.tintColor = .white button.backgroundColor = .brown button.layer.cornerRadius = 25 button.frame = CGRect(x: 0, y: 0, width: 50, height: 50) let menu = UIMenu(children: menuActions) button.showsMenuAsPrimaryAction = true button.menu = menu return button } func updateUIView(_ uiView: UIButton, context: Context) {} } // 使用示例 struct TestMenuInSafeAreaInset: View { @State private var message = "" var body: some View { VStack { TextField("输入消息", text: $message) .padding() .background(Color.gray.opacity(0.3)) .cornerRadius(12) .padding() Spacer() } .safeAreaInset(edge: .bottom) { HStack(spacing: 16) { TextField("输入消息", text: $message) .padding() .background(Color.brown) .cornerRadius(12) .foregroundColor(.white) UIKitMenuButton(menuActions: [ UIAction(title: "确认") { _ in // 执行确认逻辑 }, UIAction(title: "取消") { _ in // 执行取消逻辑 } ]) } .padding() } } }
方案2:改用Toolbar替代SafeAreaInset
将底部操作栏移至toolbar的.bottomBar位置,该方式与键盘的交互逻辑更稳定:
import SwiftUI struct TestMenuInSafeAreaInset: View { @State private var message = "" var body: some View { VStack { TextField("输入消息", text: $message) .padding() .background(Color.gray.opacity(0.3)) .cornerRadius(12) .padding() Spacer() } .toolbar { ToolbarItem(placement: .bottomBar) { HStack(spacing: 16) { TextField("输入消息", text: $message) .padding() .background(Color.brown) .cornerRadius(12) .foregroundColor(.white) Menu { Button { // 确认逻辑 } label: { Text("确认") } Button { // 取消逻辑 } label: { Text("取消") } } label: { Image(systemName: "square.and.arrow.up.fill") .tint(.white) .padding() .background(Color.brown) .cornerRadius(50) } } .padding() } } } }
方案3:强制锁定Menu布局
通过设置布局优先级和固定尺寸,强制Menu保持位置:
import SwiftUI struct TestMenuInSafeAreaInset: View { @State private var message = "" var body: some View { VStack { TextField("输入消息", text: $message) .padding() .background(Color.gray.opacity(0.3)) .cornerRadius(12) .padding() Spacer() } .safeAreaInset(edge: .bottom) { HStack(spacing: 16) { // 让TextField优先占用可用空间 TextField("输入消息", text: $message) .padding() .background(Color.brown) .cornerRadius(12) .foregroundColor(.white) .layoutPriority(1) Menu { Button { // 确认逻辑 } label: { Text("确认") } Button { // 取消逻辑 } label: { Text("取消") } } label: { Image(systemName: "square.and.arrow.up.fill") .tint(.white) .padding() .background(Color.brown) .cornerRadius(50) // 固定图标尺寸 .frame(width: 50, height: 50) .fixedSize() } // 固定Menu容器尺寸 .frame(width: 50, height: 50) .fixedSize() } .padding() // 给底部栏添加背景,避免与键盘交互时的布局闪烁 .background(Color(UIColor.systemBackground)) } } }
内容的提问来源于stack exchange,提问作者ZYiOS
相关产品推荐
相关产品推荐

