使用照片框架的视频选择器
Video picker using Photos framework
我正在尝试使用照片框架提取用户库中的所有视频,并将它们显示在集合视图中。基本上实现了我自己的视频选择器。
我正在使用 PHCachingImageManager 开始缓存每个视频的图像缩略图。当 'cellForItemAt indexPath' 被调用时,当我调用 PHCachingImageManager.requestImage(...) 并尝试在闭包中设置我的单元格图像时,我得到 'unexpectedly found nil when unwrapping an Optional value'。
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
}
这可能是我实现 PhotoCell class 的方式有问题吗?或者我使用 'collectionView.dequeueReusableCell(...)?
的方式有问题
class PhotosCell: UICollectionViewCell {
@IBOutlet weak var photoThumbnail: UIImageView!
var thumbnailImage: UIImage! {
didSet {
photoThumbnail.image = thumbnailImage
}
}
}
我环顾四周,认为我看到传递给缓存管理器的闭包放在后台线程上的位置,但这不应该影响可选的 UIImageView.image 被设置在有错误的行上,对吧?
GitHub 代码是 here 更多上下文。
出于某种原因删除以下行:
// Register Cell classes
self.collectionView!.register(PhotosCell.self, forCellWithReuseIdentifier: reuseIdentifier)
在 ViewDidLoad() 方法中,它可以很好地显示缩略图。之前,单元格没有被初始化(我假设),并且在尝试设置单元格的 UIImageView.image.
时抛出错误
虽然不确定这是否会影响 UICollectionView 的任何其他功能...
由于您的自我回答解决了问题,但您不确定原因,这里有更全面的解释...
当您在情节提要中设置原型单元格时,它会自动将该单元格定义注册到集合视图中,因此您无需调用 registerCellClass
或 registerNib
.
事实上,如果您 do 调用 registerCellClass
,dequeueReusableCell
将尝试使用您的单元格 class' 默认创建新的单元格实例初始值设定项(init(frame:)
最有可能)。这意味着它不会从情节提要中加载您的单元 class,因此您在那里设置的任何 IBOutlet
连接都不会连接 — 因此您单元的 photoThumbnail
插座为零,导致你发现的陷阱。
所以您的自我回答是正确的 — 只是不要调用 registerCellClass
,让情节提要为您处理。 (在幕后,Xcode 将您的故事板编译成多个 nib,并且 nib 加载代码调用 registerNib:forCellWithReuseIdentifier:
使用您设置为原型单元格的那个。)
既然我们在这里,让我们修复您的代码中的另一个可能的问题。您的 requestImage
完成处理程序直到 collectionView(_:cellForItemAt:)
returns 之后才会执行。这意味着在滚动期间或之后,您设置图像的单元格可能已被重复使用 — 因此它现在代表一个与您为其请求图像的单元格不同的 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
}
我正在尝试使用照片框架提取用户库中的所有视频,并将它们显示在集合视图中。基本上实现了我自己的视频选择器。
我正在使用 PHCachingImageManager 开始缓存每个视频的图像缩略图。当 'cellForItemAt indexPath' 被调用时,当我调用 PHCachingImageManager.requestImage(...) 并尝试在闭包中设置我的单元格图像时,我得到 'unexpectedly found nil when unwrapping an Optional value'。
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
}
这可能是我实现 PhotoCell class 的方式有问题吗?或者我使用 'collectionView.dequeueReusableCell(...)?
的方式有问题class PhotosCell: UICollectionViewCell {
@IBOutlet weak var photoThumbnail: UIImageView!
var thumbnailImage: UIImage! {
didSet {
photoThumbnail.image = thumbnailImage
}
}
}
我环顾四周,认为我看到传递给缓存管理器的闭包放在后台线程上的位置,但这不应该影响可选的 UIImageView.image 被设置在有错误的行上,对吧? GitHub 代码是 here 更多上下文。
出于某种原因删除以下行:
// Register Cell classes
self.collectionView!.register(PhotosCell.self, forCellWithReuseIdentifier: reuseIdentifier)
在 ViewDidLoad() 方法中,它可以很好地显示缩略图。之前,单元格没有被初始化(我假设),并且在尝试设置单元格的 UIImageView.image.
时抛出错误虽然不确定这是否会影响 UICollectionView 的任何其他功能...
由于您的自我回答解决了问题,但您不确定原因,这里有更全面的解释...
当您在情节提要中设置原型单元格时,它会自动将该单元格定义注册到集合视图中,因此您无需调用 registerCellClass
或 registerNib
.
事实上,如果您 do 调用 registerCellClass
,dequeueReusableCell
将尝试使用您的单元格 class' 默认创建新的单元格实例初始值设定项(init(frame:)
最有可能)。这意味着它不会从情节提要中加载您的单元 class,因此您在那里设置的任何 IBOutlet
连接都不会连接 — 因此您单元的 photoThumbnail
插座为零,导致你发现的陷阱。
所以您的自我回答是正确的 — 只是不要调用 registerCellClass
,让情节提要为您处理。 (在幕后,Xcode 将您的故事板编译成多个 nib,并且 nib 加载代码调用 registerNib:forCellWithReuseIdentifier:
使用您设置为原型单元格的那个。)
既然我们在这里,让我们修复您的代码中的另一个可能的问题。您的 requestImage
完成处理程序直到 collectionView(_:cellForItemAt:)
returns 之后才会执行。这意味着在滚动期间或之后,您设置图像的单元格可能已被重复使用 — 因此它现在代表一个与您为其请求图像的单元格不同的 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
}