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

SwiftUI中ContextMenu内模态视图布局异常问题

SwiftUI ContextMenu 自定义视图布局压缩问题解决

问题背景

在SwiftUI中通过contextMenu展示自定义模态视图ModalSheet,已为视图指定固定尺寸305×293,但实际显示时控件被严重压缩,无法保持预设布局。推测是contextMenu的容器特性强制子视图应用fixedSize,导致布局异常。

ContextMenu 调用代码

.contextMenu {
    if let shownItem {
      ModalSheet(taskModel: shownItem)
    }
}

ModalSheet 实现代码

VStack(alignment: .trailing) {
    
    Text(taskModel.title + taskModel.title + taskModel.title + taskModel.title + taskModel.title)
        .lineLimit(3)
        .font(.system(size: 11, weight: .regular))
        .frame(maxWidth: .infinity, alignment: .center)
    
    LabelContent(label: "Release") {
        TextEditor(text: $release)
            .frame(width: 205, height: 24)
            .cornerRadius(6)
            .colorMultiply(.gray)
            .lineLimit(1)
            .cornerBorder(6, color: .gray.opacity(0.15))
    }
    Spacer()
    LabelContent(label: "Author") {
        TextEditor(text: $release)
            .frame(width: 205, height: 24)
            .cornerRadius(6)
            .colorMultiply(.gray)
            .lineLimit(1)
            .cornerBorder(6, color: .gray.opacity(0.15))
    }
    Spacer()
    LabelContent(label: "Priority") {
        Menu {
            ForEach(PriorityModel.allCases, id: \.self) { priority in
                Button("\(priority.rawValue)") {
                    self.priority = priority
                }
                Divider.appStyled()
            }
        } label: {
            Text(priority.rawValue)
                .cornerRadius(6)
                .colorMultiply(.gray)
                .lineLimit(1)
                .cornerBorder(6, color: .gray.opacity(0.15))
        }
        .frame(width: 205, height: 24)
    }
    
    Spacer()
    LabelContent(label: "Description") {
        TextEditor(text: $release)
            .frame(width: 205)
            .cornerRadius(6)
            .colorMultiply(.gray)
            .cornerBorder(6, color: .gray.opacity(0.15))
    }
}
.foregroundColor(Color(nsColor: .fromHEX(0xD0CFD1)))
.padding(14)
.background(Color(nsColor: .fromHEX(0x211D26)))
.border(Color(nsColor: .fromHEX(0x37333B)), width: 1)
.frame(width: 305, height: 293)
.onAppear {
    release = taskModel.release
    author = taskModel.author
    priority = taskModel.priority
    description = taskModel.taskDescription
}

解决方案

1. 替换为长按手势触发Sheet/Popover(推荐方案)

contextMenu设计初衷是承载轻量菜单选项,不适合复杂大尺寸视图。改用长按手势触发自定义弹窗,完全控制布局:

// 定义状态变量
@State private var isShowingModal = false

// 替换原contextMenu为长按手势
.gesture(
    LongPressGesture()
        .onEnded { _ in
            isShowingModal = true
        }
)
.sheet(isPresented: $isShowingModal) {
    ModalSheet(taskModel: shownItem)
        .presentationDetents([.height(293)]) // 固定弹窗高度
        .presentationDragIndicator(.hidden) // 可选:隐藏拖拽指示器
        .frame(width: 305) // 固定宽度
}

2. 覆盖ContextMenu的fixedSize约束(应急方案)

若必须使用contextMenu,通过fixedSize(horizontal: false, vertical: false)强制取消自动压缩,配合ScrollView避免内容截断:

// 修改ModalSheet的外层容器配置
VStack(alignment: .trailing) {
    // ... 原有内容 ...
}
.foregroundColor(Color(nsColor: .fromHEX(0xD0CFD1)))
.padding(14)
.background(Color(nsColor: .fromHEX(0x211D26)))
.border(Color(nsColor: .fromHEX(0x37333B)), width: 1)
.frame(width: 305, height: 293)
.fixedSize(horizontal: false, vertical: false) // 禁用自动fixedSize约束
.scrollable() // 或嵌套ScrollView(showsIndicators: false) { ... }
.onAppear {
    // ... 原有逻辑 ...
}

3. 使用自定义ContextMenu样式(iOS 16+)

iOS 16及以上支持修改ContextMenu样式,切换为menu模式可缓解布局约束问题:

.contextMenu {
    if let shownItem {
        ModalSheet(taskModel: shownItem)
    }
}
.contextMenuStyle(.menu) // 切换为Menu样式,放松布局约束

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 12:35:31