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
相关产品推荐
相关产品推荐

