如何创建可返回视图并传递动作回调的SwiftUI View Modifier?
问题描述
我用SheetManager配合View Modifier实现了弹窗管理,基础弹窗(带关闭按钮)运行正常。现在要扩展功能,让复杂自定义弹窗(比如SoundProfilePopupView)里的按钮动作能回调到主视图。目前尝试过类型别名、调整闭包返回值,但要么编译报错,要么回调不触发,核心需求是把Modifier里tappedProfile的动作传递到主视图的.popup回调中。
现有代码
View Modifier扩展方法
func popup(with sheetManager: SheetManager, action: @escaping () -> Void) -> some View { self.modifier(PopupViewModifier(sheetManager: sheetManager){ }) }
PopupViewModifier结构体
struct PopupViewModifier: ViewModifier { @ObservedObject var sheetManager: SheetManager var popupAction: () -> (Void) func body(content: Content) -> some View { content .disabled(sheetManager.action.isPresented ? true : false ) .blur(radius: sheetManager.action.isPresented ? 05 : 0) .overlay(alignment: .center) { if case let .present(config) = sheetManager.action { switch config.type { case .popupAlert: PopupView(config: config) { withAnimation { sheetManager.dismiss() } } case .soundProfileList: SoundProfilePopupView(config: config) { withAnimation { sheetManager.dismiss() } } tappedProfile: { popupAction() } } } } .ignoresSafeArea() } }
主视图调用代码
@EnvironmentObject var sheetManager: SheetManager var body: some View { buttonToCallPopup( didPress: { sheetManager.present(with: Config.innit) }) { Text("Open up popup") } .popup(with: sheetManager) { print("button from popup has been pressed") } }
SheetManager实现代码
enum SheetType { case popupAlert case soundProfileList } final class SheetManager: ObservableObject { typealias Config = Action.Info @Published private(set) var action: Action = .na enum Action { struct Info { let systemName: String let title: String let content: String let type: SheetType } case na case present(info: Info) case dismiss } func present(with config: Config) { guard !action.isPresented else { return } self.action = .present(info: config) } func dismiss() { self.action = .dismiss } } extension SheetManager.Action { var isPresented: Bool { guard case .present(_) = self else {return false} return true } }
解决方案
问题核心是View Modifier扩展方法没有把传入的action传递给PopupViewModifier,导致主视图的回调根本没关联上。以下是修复和优化方案:
1. 修复基础回调传递
直接修改View Modifier扩展方法,把传入的闭包传递给PopupViewModifier的popupAction参数:
func popup(with sheetManager: SheetManager, action: @escaping () -> Void) -> some View { // 替换原有的空闭包,传入主视图的action self.modifier(PopupViewModifier(sheetManager: sheetManager, popupAction: action)) }
修复后,主视图里的.popup闭包会在tappedProfile触发时正常执行,打印预期日志。
2. (可选)扩展支持带数据的回调
如果后续需要传递弹窗内的具体数据(比如选中的音效配置),可以优化SheetManager的结构:
- 修改
SheetManager的Action.Info,增加带参数的回调存储:
final class SheetManager: ObservableObject { typealias Config = Action.Info @Published private(set) var action: Action = .na enum Action { struct Info { let systemName: String let title: String let content: String let type: SheetType // 新增:支持带参数的回调,根据弹窗类型定义 var profileTapAction: ((String) -> Void)? // 示例:传递选中的profile名称 } case na case present(info: Info) case dismiss } // 其余代码不变 }
- 修改
PopupViewModifier和弹窗视图的回调调用:
// PopupViewModifier中 case .soundProfileList: SoundProfilePopupView(config: config) { withAnimation { sheetManager.dismiss() } } tappedProfile: { selectedProfileName in // 优先调用Config里的带参回调,兼容原有无参回调 config.profileTapAction?(selectedProfileName) // 如果需要保留原有无参回调,可同时调用popupAction() }
- 主视图调用时可直接绑定带参回调:
buttonToCallPopup( didPress: { let config = SheetManager.Config( systemName: "speaker", title: "Select Sound Profile", content: "", type: .soundProfileList, profileTapAction: { name in print("Selected sound profile: \(name)") } ) sheetManager.present(with: config) }) { Text("Open Sound Profile Popup") }
内容的提问来源于stack exchange,提问作者kenada97
相关产品推荐
相关产品推荐

