使用照片框架的视频选择器

Posted

技术标签:

【中文标题】使用照片框架的视频选择器【英文标题】:Video picker using Photos framework 【发布时间】:2016-11-02 15:31:07 【问题描述】:

我正在尝试使用照片框架提取用户库中的所有视频并将它们显示在集合视图中。基本上实现了我自己的视频选择器。

我正在使用 PHCachingImageManager 开始缓存每个视频的图像缩略图。当调用“cellForItemAt indexPath”时,当我调用 PHCachingImageManager.requestImage(...) 并尝试在闭包中设置我的单元格的图像时,我得到“在展开可选值时意外发现 nil”。

viewDidLoad() 
    // Register Cell classes
    self.collectionView!.register(PhotosCell.self, forCellWithReuseIdentifier: reuseIdentifier)

    let videosAlbum = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumVideos, options: nil)

    print(videosAlbum.count) // prints as 1 bc videosAlbum contains 1 collection
    self.videos = PHAsset.fetchAssets(in: videosAlbum.object(at: 0), options: nil)
    print(self.videos.count) // prints the # of videos in the Videos smart album

    self.imageManager.startCachingImages(for: self.videos.objects(at: [0, videos.count-1]), targetSize: CGSize(width: 150, height: 150), contentMode: .aspectFit, options: nil)


override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PhotosCell
    let asset = videos.object(at: indexPath.item)

    imageManager.requestImage(for: asset, targetSize: CGSize(width: 150, height: 150), contentMode: .aspectFill, options: nil, resultHandler: 
        image, _ in
                cell.thumbnailImage = image //this is the line throwing the error
    )
    return cell

这可能是我实现 PhotosCell 类的方式的问题吗?还是我使用 'collectionView.dequeueReusableCell(...) 的方式有问题?

class PhotosCell: UICollectionViewCell 

@IBOutlet weak var photoThumbnail: UIImageView!

var thumbnailImage: UIImage! 
    didSet 
        photoThumbnail.image = thumbnailImage
    


我环顾四周,以为我看到了传递给缓存管理器的闭包放在后台线程上的位置,但这不应该影响在错误行上设置的可选 UIImageView.image,对吧? GitHub 代码是 here 以获得更多上下文。

【问题讨论】:

【参考方案1】:

由于您的自我回答在您不确定原因的情况下解决了问题,因此这里有一个更完整的解释...

当您在情节提要中设置原型单元格时,它会自动将该单元格定义注册到集合视图中,因此您无需调用registerCellClassregisterNib

事实上,如果您确实调用registerCellClassdequeueReusableCell 将尝试使用您的单元类的默认初始化程序创建新的单元实例(init(frame:) 最有可能)。这意味着它不会从情节提要加载您的单元格类,因此您在那里设置的任何IBOutlet 连接都不会被连接——因此您的单元格的photoThumbnail 出口为零,从而导致您找到了陷阱。

所以你的自我回答是正确的——只是不要打电话给registerCellClass,让故事板为你处理。 (在幕后,Xcode 将您的故事板编译成多个 nib,nib 加载代码调用 registerNib:forCellWithReuseIdentifier: 并将您设置为原型单元的那个。)


在这里,让我们解决您的代码中可能存在的另一个问题。您的 requestImage 完成处理程序直到 collectionView(_:cellForItemAt:) 返回后才会执行。这意味着在滚动期间或之后,您设置图像的单元格可能已被重用——因此它现在代表的 indexPath 与您为其请求图像的那个不同,并且您现在在其上设置了错误的图像。

如果您查看 Apple 的 Using Photos Framework 示例代码,他们会做一些额外的工作来确保单元格上图像的异步设置正常工作。在他们的AssetGridViewController:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    let asset = fetchResult.object(at: indexPath.item)

    // Dequeue a GridViewCell.
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: GridViewCell.self), for: indexPath) as? GridViewCell
        else  fatalError("unexpected cell in collection view") 

    // Request an image for the asset from the PHCachingImageManager.
    cell.representedAssetIdentifier = asset.localIdentifier
    imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler:  image, _ in
        // The cell may have been recycled by the time this handler gets called;
        // set the cell's thumbnail image only if it's still showing the same asset.
        if cell.representedAssetIdentifier == asset.localIdentifier 
            cell.thumbnailImage = image
        
    )
    return cell

【讨论】:

所以'registerCellClass'仅在集合视图中设置单元格时使用?表视图中包含的样板代码中不包含此行是否有原因?这似乎很奇怪,它包含在集合视图中,但不包含在表格中。感谢您提供有关示例项目的课程。我已经研究了一段时间,并取出了可能归因于该问题的“assetIdentifer”想法。示例单元格使用设置器而不是 [cell].[outlet].image = [UIImage] 是否有原因? 按顺序回答您的后续问题...registerCellClass 主要用于以编程方式设置集合视图(而不是使用情节提要或 nib)。 集合视图比表格视图更新,因此它们获得了一些反映 Apple 在 ios 2 和 iOS 6 之间吸取的经验教训的设计模式。(以及底层硬件性能的变化。) 并且可能编写该示例代码的人认为单元格抽象出它如何呈现其内容会很好。如果单元格有一个图像设置器而不是直接公开其图像视图,它可以在将来对该图像执行其他操作,而无需调用需要更改的单元格的代码。【参考方案2】:

由于某种原因删除了这些行:

// Register Cell classes
self.collectionView!.register(PhotosCell.self, forCellWithReuseIdentifier: reuseIdentifier)

在 ViewDidLoad() 方法中,它可以很好地显示缩略图。之前,单元格没有被初始化(我假设),并且在尝试设置单元格的 UIImageView.image 时抛出错误。

不确定这是否会影响 UICollectionView 的任何其他功能...

【讨论】:

好吧,self.collectionView!.register(PhotosCell.self, forCellWithReuseIdentifier: reuseIdentifier) 是什么意思?这意味着,“不要从情节提要中的原型单元格中获取此单元格。”但是您确实想从情节提要中的原型单元格中获取此单元格。很明显,这是错误的说法。

以上是关于使用照片框架的视频选择器的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift/Objective-c 中按媒体类型自定义图像选择器

OC照片选择器MJPhotoBrowser

iOS 开发之照片框架详解

iOS 开发之照片框架详解

【基础笔记】Android Studio拍照、选择相册(第三方框架)

Windows Server2008怎么使用照片查看器