返回导航控制器后重载视图控制器,下载状态消失问题
Hey Noah, let's tackle that download state disappearing issue you're facing! The core problem here is that when you pop back and reload your view controller, the original instance (and its references to the download task/UI state) gets destroyed. But since the background URLSession keeps the task alive, we just need to decouple the download management from the view controller itself.
Here's a step-by-step fix:
1. Move Download Logic to a Singleton Manager
Create a shared singleton class to handle all download tasks, URLSession setup, and state tracking. This way, the download context survives even when your view controller is recreated.
class DownloadManager: NSObject, URLSessionDownloadDelegate { static let shared = DownloadManager() private var session: URLSession! private var activeTasks: [URL: URLSessionDownloadTask] = [:] // Closures to notify listeners of progress/completion var onProgressUpdate: ((URL, Double) -> Void)? var onDownloadComplete: ((URL, URL?, Error?) -> Void)? private override init() { super.init() // Use a background session to keep tasks running even if the app is in background let config = URLSessionConfiguration.background(withIdentifier: "com.yourapp.background.downloads") session = URLSession(configuration: config, delegate: self, delegateQueue: nil) } // Start or resume a download task func startDownload(for url: URL) { guard activeTasks[url] == nil else { // Task already exists, maybe just resume it if paused activeTasks[url]?.resume() return } let task = session.downloadTask(with: url) activeTasks[url] = task task.resume() } // Get current progress for a specific download URL func getCurrentProgress(for url: URL) -> Double? { guard let task = activeTasks[url], let httpResponse = task.response as? HTTPURLResponse, httpResponse.expectedContentLength > 0 else { return nil } return Double(task.countOfBytesReceived) / Double(httpResponse.expectedContentLength) } // MARK: - URLSessionDownloadDelegate func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { guard let url = downloadTask.originalRequest?.url else { return } let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) // Notify listeners on main thread for UI updates DispatchQueue.main.async { self.onProgressUpdate?(url, progress) } } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { guard let url = downloadTask.originalRequest?.url else { return } activeTasks.removeValue(forKey: url) DispatchQueue.main.async { self.onDownloadComplete?(url, location, nil) } } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let url = task.originalRequest?.url else { return } activeTasks.removeValue(forKey: url) DispatchQueue.main.async { self.onDownloadComplete?(url, nil, error) } } }
2. Update Your View Controller to Use the Singleton
Now, in your view controller, instead of managing the download task directly, we'll hook into the singleton to track state and update the UI whenever the view appears:
class YourDownloadVC: UIViewController { @IBOutlet weak var progressBar: UIProgressView! @IBOutlet weak var statusLabel: UILabel! // Replace with your actual download URL let targetDownloadURL = URL(string: "https://example.com/your-large-file.zip")! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Restore existing download state when the view loads if let progress = DownloadManager.shared.getCurrentProgress(for: targetDownloadURL) { updateUI(with: progress) } else { resetUI() } // Set up listeners for progress updates DownloadManager.shared.onProgressUpdate = { [weak self] url, progress in guard url == self?.targetDownloadURL else { return } self?.updateUI(with: progress) } // Set up listener for download completion/failure DownloadManager.shared.onDownloadComplete = { [weak self] url, location, error in guard url == self?.targetDownloadURL else { return } if let error = error { self?.statusLabel.text = "Failed: \(error.localizedDescription)" } else { self?.statusLabel.text = "Download Complete!" self?.progressBar.progress = 1.0 } } } @IBAction func startDownloadTapped(_ sender: UIButton) { DownloadManager.shared.startDownload(for: targetDownloadURL) statusLabel.text = "Downloading..." } private func updateUI(with progress: Double) { progressBar.progress = Float(progress) statusLabel.text = String(format: "Downloading: %.1f%%", progress * 100) } private func resetUI() { progressBar.progress = 0.0 statusLabel.text = "Ready to Download" } }
Key Fixes Explained:
- Singleton Persistence: The
DownloadManagersingleton lives for the app's entire lifecycle, so it retains all active download tasks even when your view controller is destroyed and recreated. - State Restoration: Every time the view appears, we check the singleton for existing progress and update the UI immediately.
- Thread Safety: All UI updates are dispatched to the main thread to avoid crashes.
- Weak Self: We use
[weak self]in closures to prevent memory leaks between the singleton and view controller.
This setup ensures that background downloads keep running, and your UI always reflects the current state—even after navigating back and forth!
内容的提问来源于stack exchange,提问作者Noah

