Swift:如何更新另一UIViewController中的进度视图?上传场景求优化方案
嘿,针对你这个嵌入在UINavigationController里的UITableViewController上传文件、需要展示进度的场景,我给你梳理几个实用方案,绝对比单例靠谱多了~
先聊聊更友好的进度展示方案
其实自定义弹窗不是唯一选择,甚至未必是最优解,给你几个更贴合iOS用户习惯的选项:
- 导航栏内嵌进度条:直接在
UINavigationBar里加一个UIProgressView,可以替换titleView或者放在导航栏底部,上传时实时更新进度。这种方式不会遮挡表格内容,用户操作更顺畅,上传完成后可以自动隐藏,体验很自然。 - 底部悬浮进度条:类似系统App Store下载的样式,在屏幕底部放一个窄条进度条,上传时滑入,完成后自动滑出。既不干扰主界面操作,又能让用户随时看到进度。
- 轻量化弹窗(如果一定要用):别用全屏的自定义
UIViewController,可以用UIAlertController嵌入UIProgressView,或者做一个半透明的小弹窗(模态样式设为.overCurrentContext),避免完全遮挡表格,减少用户的压迫感。
再解决控制器间连续数据传递的问题(替代Singleton)
单例确实坑不少——全局状态容易冲突(比如同时传多个上传进度)、内存泄漏风险(单例强引用弹窗导致无法释放)、耦合性太高。给你几个更合适的方案:
方案1:闭包(Closure)——最简洁的方式
在你的自定义弹窗控制器里定义一个进度更新的闭包,当表格控制器弹出弹窗时,把进度更新的逻辑传给它,上传过程中直接调用闭包更新UI。
示例代码:
自定义弹窗控制器
class ProgressPopupVC: UIViewController { private let progressView = UIProgressView(progressViewStyle: .default) // 定义进度更新闭包 var onProgressUpdate: ((Float) -> Void)? override func viewDidLoad() { super.viewDidLoad() // 配置弹窗UI view.backgroundColor = UIColor.black.withAlphaComponent(0.5) progressView.frame = CGRect(x: 20, y: view.center.y, width: view.frame.width - 40, height: 8) view.addSubview(progressView) } // 对外暴露更新进度的方法(可选,也可以直接调用闭包) func updateProgress(_ progress: Float) { DispatchQueue.main.async { self.progressView.progress = progress } } }
UITableViewController里的调用
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // 初始化弹窗并设置模态样式 let popupVC = ProgressPopupVC() popupVC.modalPresentationStyle = .overCurrentContext // 初始化上传任务 let uploadTask = URLSession.shared.uploadTask(with: yourRequest, fromFile: yourFileURL) // 监听上传进度 uploadTask.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil) // 绑定闭包:上传进度变化时通知弹窗 popupVC.onProgressUpdate = { [weak popupVC] progress in popupVC?.updateProgress(progress) } // 弹窗弹出后启动上传 present(popupVC, animated: true) { uploadTask.resume() } } // KVO回调处理进度更新 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "fractionCompleted", let progress = object as? Progress else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // 调用闭包更新弹窗进度 (presentedViewController as? ProgressPopupVC)?.onProgressUpdate?(Float(progress.fractionCompleted)) }
方案2:代理模式(Delegate)——更规范的iOS传统写法
如果你的弹窗有更多交互(比如取消上传),代理模式会比闭包更清晰:
- 定义一个进度更新的协议:
protocol UploadProgressDelegate: AnyObject { func updateUploadProgress(_ progress: Float) }
- 在弹窗控制器里声明代理属性:
weak var delegate: UploadProgressDelegate? - 在
UITableViewController里遵守协议,实现updateUploadProgress方法,上传时把弹窗的delegate设为self,进度变化时调用delegate?.updateUploadProgress(...)
方案3:Combine框架(iOS 13+)——响应式编程更优雅
如果你的项目支持iOS 13及以上,用Combine的PassthroughSubject发送进度事件,弹窗订阅事件更新UI:
- 在表格控制器里定义
let progressSubject = PassthroughSubject<Float, Never>() - 弹窗初始化时订阅这个
subject:progressSubject.sink { [weak self] progress in self?.progressView.progress = progress }.store(in: &cancellables) - 上传进度变化时发送事件:
progressSubject.send(Float(progress.fractionCompleted))
为什么不推荐Singleton?
单例是全局共享的状态,如果你同时发起多个上传任务,进度会互相覆盖;而且单例如果持有弹窗的强引用,即使你dismiss了弹窗,它也不会被释放,容易造成内存泄漏。这种和特定任务绑定的临时数据传递,用上面的几种方案都比单例更合适。
内容的提问来源于stack exchange,提问作者johnDoe
相关产品推荐
相关产品推荐

