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

嵌入UIHostingController的SwiftUI视图无法关闭问题求助

解决UIHostingController托管的SwiftUI视图上传完成后自动关闭问题

修正后的代码

UploadViewController

required init?(coder: NSCoder) {
    let uploadView = UploadView()
    super.init(coder: coder, rootView: uploadView)
    // 将UIKit的关闭逻辑通过闭包传递给SwiftUI视图
    uploadView.dismissAction = { [weak self] in
        self?.dismiss(animated: true, completion: nil)
    }
}
// 无需自定义dismiss方法,直接使用UIViewController自带的实现即可

UploadView

struct UploadView: View {
    // 移除@State,用普通可选闭包接收来自宿主控制器的关闭逻辑
    var dismissAction: (() -> Void)?
    
    // 保留原有状态变量
    @State private var selectedImage: Image = Image(systemName: "photo")
    @State private var showImagePicker = false
    @State private var uid: String = ""
    @State private var uname: String = ""
    
    var body: some View {
        VStack {
            Button(action: {
                guard let uiImage = selectedImage.asUIImage() else { return }
                let imageData = uiImage.jpegData(compressionQuality: 0.1) ?? Data()
                
                // 读取UserDefaults逻辑保持不变
                if let data9 = UserDefaults.standard.data(forKey: "current") {
                    do {
                        let decoder = JSONDecoder()
                        let current = try decoder.decode(Current.self, from: data9)
                        uid = current.id
                        uname = current.uname
                        print(current.fid as Any)
                    } catch {
                        print("Unable to Decode User (\(error))")
                    }
                }
                
                // 构建上传请求(需自行补充url和remoteName的定义)
                guard let url = URL(string: "你的上传接口地址") else { return }
                var urlRequest = URLRequest(url: url)
                urlRequest.addDataField(named: remoteName, data: imageData, mimeType: "image/jpeg")
                
                URLSession.shared.dataTask(with: urlRequest) { data, response, error in
                    guard let data = data else {
                        print("Error in Data")
                        return
                    }
                    let responseStr = String(data: data, encoding: .utf8) ?? ""
                    print("上传结果:\(responseStr)")
                    
                    // 回到主线程执行UI操作(关闭视图)
                    DispatchQueue.main.async {
                        // 安全调用闭包,避免强制解包崩溃
                        dismissAction?()
                    }
                }.resume()
                
            }, label: {
                Text("Upload Image")
                    .font(.callout)
                    .padding(8)
                    .foregroundColor(.white)
                    .background(.black)
                    .overlay(
                        RoundedRectangle(cornerRadius: 6)
                            .stroke(Color.white, lineWidth: 3)
                    )
            })
        }
        .sheet(isPresented: $showImagePicker) {
            ImagePicker(image: $selectedImage)
        }
    }
}

问题详解

1. 原代码无法自动关闭的原因

  • @State误用:@State是SwiftUI用于管理内部视图状态的属性包装器,外部对它的赋值会被SwiftUI的状态系统覆盖,导致传递的闭包实际未生效。
  • 线程错误:URLSession的回调在后台线程执行,而dismiss是UI操作,必须在主线程执行,直接调用会导致操作无效。
  • 错误触发方式:在回调中创建Button仅生成视图对象,不会触发点击事件,自然无法触发关闭逻辑。

2. 为何不能直接在SwiftUI视图中调用dismiss方法

SwiftUI视图是声明式的,本身不持有UIKit视图控制器实例(即你的UploadViewController)。dismiss是UIViewController的方法,只有持有控制器实例的代码才能调用。因此必须通过闭包将宿主控制器的dismiss能力传递给SwiftUI视图,让视图在需要时触发闭包完成关闭。

按钮触发方案可行,是因为按钮的action闭包在主线程执行,且正确调用了传递过来的dismiss闭包,符合UI操作的线程要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 13:45:30