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

SwiftUI ForEach内按钮弹出sheet仅首个正常加载自定义视图

问题根源

你的代码有三个核心错误,导致非首个按钮点击后弹窗空白:

  • 多sheet修饰符冲突:你在ForEach循环内给每个按钮都挂载了.sheet(isPresented:),且所有sheet都绑定同一个showingModal状态。SwiftUI在同个视图层级下,只会识别第一个注册到视图树的sheet修饰符,后续按钮绑定的sheet根本不会被系统加载,这是最核心的原因。
  • 状态存储位置错误:你把actionName定义在body计算属性内部,这是视图构造时的临时局部变量,不受SwiftUI状态系统托管。每次视图重绘时这个变量都会被重置为空字符串,根本无法稳定保存当前点击的按钮标识。
  • 闭包捕获值不匹配:每个sheet的内容闭包在ForEach循环执行时,就已经固定捕获了当前循环项的key值。点击非首个按钮时,弹出的始终是第一个注册的sheet,它捕获的key是第一个按钮的标识,和你临时赋值的actionName不相等,自然进不了if key == actionName的分支,显示空白。
最优实现方案

不要在ForEach循环内挂载sheet,整个视图层级只保留一个sheet放在父视图上,单独用状态变量存储当前点击的按钮标识即可,从根源避免多sheet冲突。

struct CustomActionsView: View {
    let dataObject: CustomModel
    @State private var showingModal = false
    // 用状态变量持久化存储当前点击的操作名
    @State private var selectedActionName = ""

    var body: some View {
        VStack {
            ForEach(dataObject.actions.sorted(by: >), id: \.key) { key, value in
                let imageName = (key == "Cancel" ? "xmark.circle" : value)
                CapsuleActionButtonWithImage(
                    actionNameText: key,
                    imageName: imageName,
                    action: {
                        // 先赋值当前点击的操作,再触发弹窗
                        selectedActionName = key
                        showingModal = true
                        NSLog("Clicked action is - \(key)")
                    }
                )
                Spacer()
            }
        }
        // 仅在父视图挂载一个sheet,不存在冲突问题
        .sheet(isPresented: $showingModal) {
            let messageViewDetails = Helper.getMessageDetails(
                dataObject: dataObject,
                clickedActionName: selectedActionName
            )
            GeneralAlertMessageView(
                imageName: messageViewDetails.messageViewimageName,
                mainMessageText: messageViewDetails.messageText,
                messageViewButtonStyle: .okButton,
                showingModal: $showingModal
            )
        }
    }
}

如果要更符合SwiftUI标准设计,也可以用sheet(item:)的写法,把选中的操作定义为可选值,不需要手动维护showingModal布尔状态,赋值为nil时自动关闭弹窗:

struct CustomActionsView: View {
    let dataObject: CustomModel
    // 可选值为nil时弹窗隐藏,赋值后自动弹出
    @State private var selectedAction: String?

    var body: some View {
        VStack {
            ForEach(dataObject.actions.sorted(by: >), id: \.key) { key, value in
                let imageName = (key == "Cancel" ? "xmark.circle" : value)
                CapsuleActionButtonWithImage(
                    actionNameText: key,
                    imageName: imageName,
                    action: {
                        selectedAction = key
                        NSLog("Clicked action is - \(key)")
                    }
                )
                Spacer()
            }
        }
        .sheet(item: $selectedAction) { actionName in
            let messageViewDetails = Helper.getMessageDetails(
                dataObject: dataObject,
                clickedActionName: actionName
            )
            GeneralAlertMessageView(
                imageName: messageViewDetails.messageViewimageName,
                mainMessageText: messageViewDetails.messageText,
                messageViewButtonStyle: .okButton,
                showingModal: Binding(
                    get: { selectedAction != nil },
                    set: { if !$0 { selectedAction = nil } }
                )
            )
        }
    }
}

// 让String遵循Identifiable即可使用sheet(item:)接口,若要更规范也可以单独定义操作枚举
extension String: Identifiable {
    public var id: String { self }
}

两种写法都能保证所有按钮点击后正常加载自定义提醒视图,不会再出现弹窗空白的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:54:29