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

如何创建可返回视图并传递动作回调的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:21:02