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

如何在视图控制器外以编程方式创建UIProgressView?

这个问题我之前拆分网络逻辑和UI代码时也踩过坑!核心点在于UI组件的创建和更新必须由拥有视图层级的对象(比如视图控制器)来负责,NetworkHelper作为业务逻辑类,本身没有view属性,自然不能直接往上面加ProgressView。给你三个实用的解决方案,按需选择:

方案一:用闭包回调(最简洁的方式)

让NetworkHelper通过闭包把进度变化传递给视图控制器,由VC来负责创建和更新ProgressView:

// NetworkHelper类定义
class NetworkHelper {
    // 定义进度回调闭包类型
    typealias ProgressHandler = (Float) -> Void
    var progressHandler: ProgressHandler?
    
    func startDownload(with url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
            // 下载完成后的逻辑处理
        }
        
        task.resume()
        
        // 监听下载进度
        task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil)
    }
    
    // 监听进度变化
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard keyPath == "fractionCompleted", let progress = object as? Progress else { return }
        let progressValue = Float(progress.fractionCompleted)
        
        // 回到主线程传递进度(UI操作必须在主线程)
        DispatchQueue.main.async {
            self.progressHandler?(progressValue)
        }
    }
    
    // 销毁时移除观察者,避免内存泄漏
    deinit {
        task.progress.removeObserver(self, forKeyPath: "fractionCompleted")
    }
}

// 在你的视图控制器中使用
class DownloadVC: UIViewController {
    private let progressView = UIProgressView(progressViewStyle: .default)
    private let networkHelper = NetworkHelper()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 配置并添加ProgressView到视图层级
        progressView.frame = CGRect(x: 20, y: 100, width: view.bounds.width - 40, height: 20)
        view.addSubview(progressView)
        
        // 设置闭包接收进度更新
        networkHelper.progressHandler = { [weak self] progress in
            self?.progressView.setProgress(progress, animated: true)
        }
        
        // 启动下载
        guard let url = URL(string: "your_download_url") else { return }
        networkHelper.startDownload(with: url)
    }
}

方案二:代理模式(适合复杂交互场景)

定义一个代理协议,让视图控制器遵守协议,NetworkHelper通过代理方法传递进度:

// 定义进度代理协议
protocol NetworkProgressDelegate: AnyObject {
    func networkHelper(_ helper: NetworkHelper, didUpdateProgress progress: Float)
}

class NetworkHelper {
    weak var delegate: NetworkProgressDelegate?
    
    func startDownload(with url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
            // 下载完成逻辑
        }
        
        task.resume()
        task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil)
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard keyPath == "fractionCompleted", let progress = object as? Progress else { return }
        let progressValue = Float(progress.fractionCompleted)
        
        DispatchQueue.main.async {
            self.delegate?.networkHelper(self, didUpdateProgress: progressValue)
        }
    }
    
    deinit {
        task.progress.removeObserver(self, forKeyPath: "fractionCompleted")
    }
}

// 视图控制器遵守代理协议
class DownloadVC: UIViewController, NetworkProgressDelegate {
    private let progressView = UIProgressView(progressViewStyle: .default)
    private let networkHelper = NetworkHelper()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 配置ProgressView
        progressView.frame = CGRect(x: 20, y: 100, width: view.bounds.width - 40, height: 20)
        view.addSubview(progressView)
        
        // 设置代理
        networkHelper.delegate = self
        
        // 启动下载
        guard let url = URL(string: "your_download_url") else { return }
        networkHelper.startDownload(with: url)
    }
    
    // 实现代理方法更新进度
    func networkHelper(_ helper: NetworkHelper, didUpdateProgress progress: Float) {
        progressView.setProgress(progress, animated: true)
    }
}

方案三:NotificationCenter(多组件需监听进度时使用)

通过发送通知的方式传递进度,适合多个视图或组件需要监听同一份网络进度的场景:

// 定义通知名称
extension Notification.Name {
    static let networkProgressUpdated = Notification.Name("NetworkProgressUpdated")
}

class NetworkHelper {
    func startDownload(with url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
            // 下载完成逻辑
        }
        
        task.resume()
        task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil)
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard keyPath == "fractionCompleted", let progress = object as? Progress else { return }
        let progressValue = Float(progress.fractionCompleted)
        
        DispatchQueue.main.async {
            // 发送通知携带进度值
            NotificationCenter.default.post(
                name: .networkProgressUpdated,
                object: nil,
                userInfo: ["progress": progressValue]
            )
        }
    }
    
    deinit {
        task.progress.removeObserver(self, forKeyPath: "fractionCompleted")
    }
}

// 视图控制器中监听通知
class DownloadVC: UIViewController {
    private let progressView = UIProgressView(progressViewStyle: .default)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 配置ProgressView
        progressView.frame = CGRect(x: 20, y: 100, width: view.bounds.width - 40, height: 20)
        view.addSubview(progressView)
        
        // 监听进度通知
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleProgressUpdate(_:)),
            name: .networkProgressUpdated,
            object: nil
        )
        
        // 启动下载
        guard let url = URL(string: "your_download_url") else { return }
        let networkHelper = NetworkHelper()
        networkHelper.startDownload(with: url)
    }
    
    @objc private func handleProgressUpdate(_ notification: Notification) {
        guard let progress = notification.userInfo?["progress"] as? Float else { return }
        progressView.setProgress(progress, animated: true)
    }
    
    // 销毁时移除通知观察者
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

总的来说,闭包和代理是更推荐的方式,耦合度更低、代码可读性更好。不管用哪种方案,一定要记住所有UI操作必须放在主线程执行,也就是用DispatchQueue.main.async包裹,否则会出现UI不更新甚至崩溃的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:05:52