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

Swift环境下视频下载、存储选型及CollectionView展示技术问询

Hey there! Let me walk you through a step-by-step guide based on my actual iOS dev experience to tackle this task—downloading videos from a URL, storing them properly, and displaying them with a CollectionView.

Step-by-Step Guide: Download, Store & Display Videos on iOS

1. Video Download with URLSession

For reliable video downloads, Apple's URLSession is the way to go. It handles background downloads, progress tracking, and error handling out of the box. Here's a practical implementation:

func startVideoDownload(from url: URL, videoTitle: String) {
    let downloadTask = URLSession.shared.downloadTask(with: url) { [weak self] tempLocation, response, error in
        guard let self = self, let tempLocation = tempLocation, error == nil else {
            // Notify user of failure, update download status in database
            print("Download failed: \(error?.localizedDescription ?? "Unknown error")")
            return
        }
        
        // Move the temp file to Documents directory (persistent storage)
        let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let destinationURL = documentsDir.appendingPathComponent("\(videoTitle).mp4")
        
        do {
            // Replace existing file if it exists
            if FileManager.default.fileExists(atPath: destinationURL.path) {
                try FileManager.default.removeItem(at: destinationURL)
            }
            try FileManager.default.moveItem(at: tempLocation, to: destinationURL)
            
            // Save video metadata to your database
            self.saveVideoMetadataToDB(title: videoTitle, localPath: destinationURL.path, originalURL: url.absoluteString)
        } catch {
            print("Failed to save video file: \(error.localizedDescription)")
        }
    }
    downloadTask.resume()
}

Key Tips for Downloads:

  • Background Downloads: Use URLSessionConfiguration.background(withIdentifier:) to let downloads continue even when the app is in the background.
  • Progress Tracking: Implement URLSessionDownloadDelegate's urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:) method to update a progress UI.
  • Breakpoint Resumption: Save resumeData from failed/canceled tasks, then use downloadTask(withResumeData:) to pick up where you left off.

2. Database Selection & Implementation

First, let's break down the options you mentioned, then pick the best fit:

  • SQLite: Too low-level—requires writing raw SQL and managing connections manually. Not recommended unless you have specific performance needs.
  • CoreData: Great for deep Apple ecosystem integration (like iCloud sync), but has a steep learning curve with managed contexts, predicates, and migration. Overkill for simple video metadata storage.
  • Realm: My top recommendation. It has a clean Swift-native API, built-in thread safety, real-time data updates, and zero boilerplate. Perfect for storing video metadata (title, local path, download status, etc.).

Realm Setup & Video Model

  1. Install Realm via Swift Package Manager or CocoaPods.
  2. Define a Realm object for your video metadata:
import RealmSwift

class DownloadedVideo: Object {
    @Persisted(primaryKey: true) var id: UUID = UUID()
    @Persisted var title: String
    @Persisted var originalURL: String
    @Persisted var localFilePath: String
    @Persisted var downloadStatus: String // e.g., "completed", "in_progress", "failed"
    @Persisted var createdAt: Date = Date()
}

Save & Fetch Videos

// Save video metadata to Realm
func saveVideoMetadataToDB(title: String, localPath: String, originalURL: String) {
    let realm = try! Realm()
    let video = DownloadedVideo()
    video.title = title
    video.localFilePath = localPath
    video.originalURL = originalURL
    video.downloadStatus = "completed"
    
    try! realm.write {
        realm.add(video)
    }
    
    // Trigger CollectionView refresh
    NotificationCenter.default.post(name: NSNotification.Name("VideoAdded"), object: nil)
}

// Fetch all downloaded videos (sorted by date added)
func fetchDownloadedVideos() -> Results<DownloadedVideo> {
    let realm = try! Realm()
    return realm.objects(DownloadedVideo.self).sorted(byKeyPath: "createdAt", ascending: false)
}

3. Display Videos with UICollectionView

Step 1: Setup CollectionView

Add a UICollectionView to your view controller, set a UICollectionViewFlowLayout (for grid display), and register a custom cell.

Step 2: Custom CollectionView Cell

Create a cell to show the video thumbnail and title:

class VideoCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var thumbnailImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    
    override func prepareForReuse() {
        super.prepareForReuse()
        thumbnailImageView.image = nil // Reset to avoid reuse artifacts
    }
}

Step 3: Generate Video Thumbnails

Extract the first frame of the video to use as a thumbnail (run this on a background thread to avoid UI freezes):

func generateThumbnail(from videoPath: String) -> UIImage? {
    let videoURL = URL(fileURLWithPath: videoPath)
    let asset = AVAsset(url: videoURL)
    let imageGenerator = AVAssetImageGenerator(asset: asset)
    imageGenerator.appliesPreferredTrackTransform = true // Preserve video orientation
    
    do {
        let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
        return UIImage(cgImage: cgImage)
    } catch {
        print("Failed to generate thumbnail: \(error.localizedDescription)")
        return UIImage(systemName: "video") // Fallback placeholder
    }
}

Step 4: CollectionView Data Source & Delegate

class VideoListViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    @IBOutlet weak var collectionView: UICollectionView!
    var videos: Results<DownloadedVideo>?
    var realmNotificationToken: NotificationToken?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.dataSource = self
        collectionView.delegate = self
        
        // Fetch videos and observe real-time updates
        videos = fetchDownloadedVideos()
        realmNotificationToken = videos?.observe { [weak self] _ in
            self?.collectionView.reloadData()
        }
        
        // Listen for new video additions
        NotificationCenter.default.addObserver(self, selector: #selector(reloadCollectionView), name: NSNotification.Name("VideoAdded"), object: nil)
    }
    
    @objc func reloadCollectionView() {
        collectionView.reloadData()
    }
    
    // MARK: - UICollectionView Data Source
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return videos?.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCollectionViewCell
        guard let video = videos?[indexPath.item] else { return cell }
        
        cell.titleLabel.text = video.title
        
        // Generate thumbnail asynchronously
        DispatchQueue.global(qos: .background).async {
            let thumbnail = self.generateThumbnail(from: video.localFilePath)
            DispatchQueue.main.async {
                cell.thumbnailImageView.image = thumbnail
            }
        }
        
        return cell
    }
    
    // MARK: - UICollectionView Delegate
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let video = videos?[indexPath.item] else { return }
        let videoURL = URL(fileURLWithPath: video.localFilePath)
        
        // Open video player
        let playerVC = AVPlayerViewController()
        playerVC.player = AVPlayer(url: videoURL)
        present(playerVC, animated: true) {
            playerVC.player?.play()
        }
    }
    
    // Grid layout configuration (2 columns)
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let spacing: CGFloat = 15
        let width = (view.frame.width - spacing * 3) / 2
        return CGSize(width: width, height: width * 0.75) // 4:3 aspect ratio
    }
}

Pro Tips for Better UX & Performance

  • Cache Thumbnails: Use NSCache to store generated thumbnails and avoid reprocessing videos every time.
  • Thread Safety: Always access Realm objects on the thread they were created on—Realm instances are thread-confined.
  • Clean Up: Add a delete feature that removes both the database entry and the local video file from the Documents directory.
  • Error States: Update the downloadStatus in Realm for failed downloads, and add a retry button in the CollectionView cell.
  • Lazy Loading: For large video libraries, implement pagination in Realm to fetch videos in batches.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:52:55