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

Swift中如何用Core Data存储视频?含离线下载分步教程(零基础)

Hey there! No worries if you're new to Core Data—let's break this down into simple, actionable steps to store video metadata (and track offline downloads) using Core Data, plus implement the actual video download logic. I'll walk you through everything from setup to playing offline videos.

Step-by-Step Guide: Core Data + Video Offline Storage in Swift

1. Set Up Core Data in Your Project

  • New Project: When creating your iOS project in Xcode, check the "Use Core Data" box. Xcode automatically generates a Core Data stack (including a PersistentContainer) in your AppDelegate (or SceneDelegate for older iOS versions) and creates a .xcdatamodeld model file.
  • Existing Project: Add a new Core Data model file (File > New > File > Core Data > Data Model), then create a PersistentContainer in a dedicated manager class (we'll build this next) to handle Core Data operations.

2. Create Your Video Entity

Open the .xcdatamodeld file and follow these steps:

  • Click the "+" button in the bottom-left to add an Entity named Video.
  • Add these attributes to the Video entity (adjust types as needed):
    • id: String (mark as Unique in the Constraints section to avoid duplicate records)
    • title: String (optional, for displaying video names)
    • remoteURL: String (stores the online video URL)
    • localFilePath: String (optional, will hold the path to the downloaded offline video)
    • isDownloaded: Bool (default value: false, tracks if the video is available offline)
    • downloadProgress: Double (default value: 0.0, tracks download percentage)
  • Save the model file.

3. Generate NSManagedObject Subclass

To work with typed code instead of raw KVC calls:

  • Select the Video entity in the model file.
  • Go to Editor > Create NSManagedObject Subclass and follow the prompts. This generates a Video.swift file with a strongly typed class for your entity.

4. Build a Core Data Manager (Singleton)

Create a singleton class to handle all Core Data operations—this keeps your code organized and ensures you're using a single Core Data context throughout your app.

import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()
    
    private init() {}
    
    // Load your Core Data model (replace "YourModelName" with your actual model filename)
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "YourModelName")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Core Data load failed: \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    // Access the main context for UI-related operations
    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    // Save changes to Core Data
    func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                print("Core Data save error: \(nserror), \(nserror.userInfo)")
            }
        }
    }
    
    // Fetch all stored videos
    func fetchVideos() -> [Video] {
        let fetchRequest: NSFetchRequest<Video> = Video.fetchRequest()
        do {
            return try context.fetch(fetchRequest)
        } catch {
            print("Fetch error: \(error)")
            return []
        }
    }
    
    // Add or update a video record (upsert)
    func upsertVideo(id: String, title: String, remoteURL: String, localFilePath: String? = nil, isDownloaded: Bool = false, downloadProgress: Double = 0.0) {
        // Check if the video already exists
        let fetchRequest: NSFetchRequest<Video> = Video.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %@", id)
        
        do {
            let existingVideos = try context.fetch(fetchRequest)
            let video: Video
            
            if let existingVideo = existingVideos.first {
                video = existingVideo // Update existing record
            } else {
                video = Video(context: context) // Create new record
                video.id = id
            }
            
            video.title = title
            video.remoteURL = remoteURL
            video.localFilePath = localFilePath
            video.isDownloaded = isDownloaded
            video.downloadProgress = downloadProgress
            
            saveContext()
        } catch {
            print("Upsert error: \(error)")
        }
    }
    
    // Delete a video (and its local file if it exists)
    func deleteVideo(_ video: Video) {
        // Delete local file first if it exists
        if let localFilePath = video.localFilePath {
            let fileURL = URL(fileURLWithPath: localFilePath)
            do {
                try FileManager.default.removeItem(at: fileURL)
            } catch {
                print("Failed to delete local video file: \(error)")
            }
        }
        
        context.delete(video)
        saveContext()
    }
}

5. Implement Video Download Logic

Create a VideoDownloadManager to handle downloading videos, tracking progress, and updating Core Data when downloads complete.

import Foundation

class VideoDownloadManager {
    static let shared = VideoDownloadManager()
    private var downloadTasks: [String: URLSessionDownloadTask] = [:] // Map video IDs to active tasks
    private let session = URLSession.shared
    
    // Start downloading a video
    func startDownload(for video: Video, progressHandler: @escaping (Double) -> Void, completionHandler: @escaping (Error?) -> Void) {
        guard let remoteURL = URL(string: video.remoteURL), !video.isDownloaded else {
            completionHandler(nil)
            return
        }
        
        // Cancel existing task for this video if it exists
        downloadTasks[video.id!]?.cancel()
        
        let task = session.downloadTask(with: remoteURL) { [weak self] tempURL, response, error in
            guard let self = self else { return }
            self.downloadTasks.removeValue(forKey: video.id!)
            
            if let error = error {
                completionHandler(error)
                return
            }
            
            guard let tempURL = tempURL, let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                completionHandler(NSError(domain: "DownloadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid download response"]))
                return
            }
            
            // Move the temporary download file to the app's Documents directory
            let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
            let fileExtension = remoteURL.pathExtension
            let fileName = "\(video.id!).\(fileExtension)"
            let destinationURL = documentsDir.appendingPathComponent(fileName)
            
            do {
                // Delete existing file if it exists
                if FileManager.default.fileExists(atPath: destinationURL.path) {
                    try FileManager.default.removeItem(at: destinationURL)
                }
                try FileManager.default.moveItem(at: tempURL, to: destinationURL)
                
                // Update Core Data to mark the video as downloaded
                CoreDataManager.shared.upsertVideo(
                    id: video.id!,
                    title: video.title!,
                    remoteURL: video.remoteURL,
                    localFilePath: destinationURL.path,
                    isDownloaded: true,
                    downloadProgress: 1.0
                )
                completionHandler(nil)
            } catch {
                completionHandler(error)
            }
        }
        
        // Track download progress
        task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil)
        downloadTasks[video.id!] = task
        task.resume()
    }
    
    // Observe progress updates
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "fractionCompleted", let progress = object as? Progress {
            // Find which video this progress belongs to
            for (videoId, task) in downloadTasks {
                if task.progress === progress {
                    if let video = CoreDataManager.shared.fetchVideos().first(where: { $0.id == videoId }) {
                        let progressValue = progress.fractionCompleted
                        CoreDataManager.shared.upsertVideo(
                            id: videoId,
                            title: video.title!,
                            remoteURL: video.remoteURL,
                            downloadProgress: progressValue
                        )
                        // Pass progress to the UI (use Combine/delegate if needed)
                        // progressHandler(progressValue)
                    }
                    break
                }
            }
        }
    }
    
    // Cancel a download
    func cancelDownload(for videoId: String) {
        downloadTasks[videoId]?.cancel()
        downloadTasks.removeValue(forKey: videoId)
    }
}

6. Play Offline Videos

Once a video is downloaded, fetch it from Core Data and play it using AVPlayer:

import AVKit

func playOfflineVideo(_ video: Video) {
    guard video.isDownloaded, let localFilePath = video.localFilePath else {
        // Handle case where video isn't downloaded
        print("Video not available offline")
        return
    }
    
    let fileURL = URL(fileURLWithPath: localFilePath)
    let player = AVPlayer(url: fileURL)
    let playerVC = AVPlayerViewController()
    playerVC.player = player
    
    // Present the player (adjust based on your UI setup)
    if let topVC = UIApplication.shared.windows.first?.rootViewController {
        topVC.present(playerVC, animated: true) {
            player.play()
        }
    }
}

7. Key Error Handling Tips

  • Wrap all Core Data operations in do-catch blocks to handle save/fetch errors.
  • Check network connectivity before starting downloads (use NWPathMonitor or similar).
  • Validate file paths and existence before attempting to play offline videos.
  • Handle download task cancellations and failures gracefully in your UI.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:22:43