Swift - 使用PhotoKit获取照片2(列出所有相簿、并选择)
作者:hangge | 2016-06-19 09:10
(本文代码已升级至Swift3)
在前面一篇文章中:Swift - 使用PhotoKit获取照片1(获取所有照片缩略图、原图及其信息)。介绍了如何使用 Photos 框架来获取相机胶卷中的所有图片。
--- 缩略图展示页 CollectionViewController.swift ---
(注意:高亮部分表示相较于前文,修改过的地方)
--- 详情页 ImageDetailViewController.swift ---
(注意:这个完全没有改动。)
4,源码下载:
hangge_1233.zip
在前面一篇文章中:Swift - 使用PhotoKit获取照片1(获取所有照片缩略图、原图及其信息)。介绍了如何使用 Photos 框架来获取相机胶卷中的所有图片。
本文在起基础上做个功能改进,添加选择照片相簿的功能。
1,样例说明
(1)首先通过 tableView 将系统中的所有智能相簿,以及用户自定义的相簿通过表格的形式展示出来。
(2)相簿按照内部包含的图片数量进行降序排列。同时如果某个相簿内部没有任何图片,则将其过滤掉不显示。
(3)点击某个相簿,则会展示出该相簿下所有照片的缩略图。
(4)其他功能同前一篇文章一样(包括点击缩略图显示原图,以及图片信息)
2,效果图如下

3,详细代码
--- 相簿列表首页 TableViewController.swift ---
import UIKit
import Photos
//相簿列表项
class AlbumItem {
//相簿名称
var title:String?
//相簿内的资源
var fetchResult:PHFetchResult<PHAsset>
init(title:String?,fetchResult:PHFetchResult<PHAsset>){
self.title = title
self.fetchResult = fetchResult
}
}
class TableViewController: UITableViewController {
//相簿列表项集合
var items:[AlbumItem] = []
override func viewDidLoad() {
super.viewDidLoad()
//申请权限
PHPhotoLibrary.requestAuthorization({ (status) in
if status != .authorized {
return
}
// 列出所有系统的智能相册
let smartOptions = PHFetchOptions()
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum,
subtype: .albumRegular,
options: smartOptions)
self.convertCollection(collection: smartAlbums)
//列出所有用户创建的相册
let userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
self.convertCollection(collection: userCollections
as! PHFetchResult<PHAssetCollection>)
//相册按包含的照片数量排序(降序)
self.items.sort { (item1, item2) -> Bool in
return item1.fetchResult.count > item2.fetchResult.count
}
//异步加载表格数据,需要在主线程中调用reloadData() 方法
DispatchQueue.main.async{
self.tableView?.reloadData()
}
})
}
//转化处理获取到的相簿
private func convertCollection(collection:PHFetchResult<PHAssetCollection>){
for i in 0..<collection.count{
//获取出但前相簿内的图片
let resultsOptions = PHFetchOptions()
resultsOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate",
ascending: false)]
resultsOptions.predicate = NSPredicate(format: "mediaType = %d",
PHAssetMediaType.image.rawValue)
let c = collection[i]
let assetsFetchResult = PHAsset.fetchAssets(in: c , options: resultsOptions)
//没有图片的空相簿不显示
if assetsFetchResult.count > 0{
let title = titleOfAlbumForChinse(title: c.localizedTitle)
items.append(AlbumItem(title: title,
fetchResult: assetsFetchResult))
}
}
}
//由于系统返回的相册集名称为英文,我们需要转换为中文
private func titleOfAlbumForChinse(title:String?) -> String? {
if title == "Slo-mo" {
return "慢动作"
} else if title == "Recently Added" {
return "最近添加"
} else if title == "Favorites" {
return "个人收藏"
} else if title == "Recently Deleted" {
return "最近删除"
} else if title == "Videos" {
return "视频"
} else if title == "All Photos" {
return "所有照片"
} else if title == "Selfies" {
return "自拍"
} else if title == "Screenshots" {
return "屏幕快照"
} else if title == "Camera Roll" {
return "相机胶卷"
}
return title
}
//表格分区数
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//表格单元格数量
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//设置单元格内容
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "myCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath) as UITableViewCell
let item = self.items[indexPath.row]
let titleLabel = cell.contentView.viewWithTag(1) as! UILabel
titleLabel.text = item.title
let countLabel = cell.contentView.viewWithTag(2) as! UILabel
countLabel.text = "(\(item.fetchResult.count))"
return cell
}
//页面跳转
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//如果是跳转到展示相簿缩略图页面
if segue.identifier == "showPhotos"{
guard let collectionViewController = segue.destination
as? CollectionViewController,
let cell = sender as? UITableViewCell else{
return
}
guard let indexPath = self.tableView.indexPath(for: cell) else { return }
//获取选中的相簿信息
let item = self.items[indexPath.row]
//设置标题
collectionViewController.title = item.title
//传递相簿内的图片资源
collectionViewController.assetsFetchResults = item.fetchResult
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
(注意:高亮部分表示相较于前文,修改过的地方)
import UIKit
import Photos
class CollectionViewController: UICollectionViewController {
///取得的资源结果,用了存放的PHAsset
var assetsFetchResults:PHFetchResult<PHAsset>!
///缩略图大小
var assetGridThumbnailSize:CGSize!
/// 带缓存的图片管理对象
var imageManager:PHCachingImageManager!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//根据单元格的尺寸计算我们需要的缩略图大小
let scale = UIScreen.main.scale
let cellSize = (self.collectionViewLayout as! UICollectionViewFlowLayout).itemSize
assetGridThumbnailSize = CGSize(width:cellSize.width*scale ,
height:cellSize.height*scale)
}
override func viewDidLoad() {
super.viewDidLoad()
// 如果没有传入值 则获取所有资源
if assetsFetchResults == nil {
//则获取所有资源
let allPhotosOptions = PHFetchOptions()
//按照创建时间倒序排列
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate",
ascending: false)]
//只获取图片
allPhotosOptions.predicate = NSPredicate(format: "mediaType = %d",
PHAssetMediaType.image.rawValue)
assetsFetchResults = PHAsset.fetchAssets(with: PHAssetMediaType.image,
options: allPhotosOptions)
}
// 初始化和重置缓存
self.imageManager = PHCachingImageManager()
self.resetCachedAssets()
}
//重置缓存
func resetCachedAssets(){
self.imageManager.stopCachingImagesForAllAssets()
}
// CollectionView行数
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.assetsFetchResults.count
}
// 获取单元格
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// storyboard里设计的单元格
let identify:String = "DesignViewCell"
// 获取设计的单元格,不需要再动态添加界面元素
let cell = (self.collectionView?.dequeueReusableCell(
withReuseIdentifier: identify, for: indexPath))! as UICollectionViewCell
let asset = self.assetsFetchResults[indexPath.row]
//获取缩略图
self.imageManager.requestImage(for: asset, targetSize: assetGridThumbnailSize,
contentMode: PHImageContentMode.aspectFill,
options: nil) { (image, nfo) in
(cell.contentView.viewWithTag(1) as! UIImageView)
.image = image
print(image)
}
return cell
}
// 单元格点击响应
override func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
let myAsset = self.assetsFetchResults[indexPath.row]
//这里不使用segue跳转(建议用segue跳转)
let detailViewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "detail")
as! ImageDetailViewController
detailViewController.myAsset = myAsset
// navigationController跳转到detailViewController
self.navigationController!.pushViewController(detailViewController,
animated:true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
--- 详情页 ImageDetailViewController.swift ---
(注意:这个完全没有改动。)
import UIKit
import Photos
class ImageDetailViewController: UIViewController {
//选中的图片资源
var myAsset:PHAsset!
//用于显示图片信息
@IBOutlet weak var textView: UITextView!
//用于显示原图
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
//获取文件名
PHImageManager.default().requestImageData(for: myAsset, options: nil,
resultHandler: {
_, _, _, info in
self.title = (info!["PHImageFileURLKey"] as! NSURL).lastPathComponent
})
//获取图片信息
textView.text = "日期:\(myAsset.creationDate!)\n"
+ "类型:\(myAsset.mediaType.rawValue)\n"
+ "位置:\(myAsset.location)\n"
+ "时长:\(myAsset.duration)\n"
//获取原图
PHImageManager.default().requestImage(for: myAsset,
targetSize: PHImageManagerMaximumSize , contentMode: .default,
options: nil, resultHandler: {
(image, _: [AnyHashable : Any]?) in
self.imageView.image = image
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
4,源码下载:
全部评论(6)
如何把UIImage转成PHAsset?教程中是能获取到系统的相册,但是如果我需要在相册分组的最后加上自己的图片,而自己的图片是UIImage, 跟PHAsset不同,这个可以转换么?我目前想到的是将图片先导入到系统相册中,并新建一个分类,然后再从相册中导入,但感觉这样有些麻烦,能否直接将自己的图片新建分组放到最后?
站长回复:不用把UIImage转成PHAsse的,你就直接在相册分组最后面加上一行(自己的图片分组),点击分组时判断一下,如果是自定义的这个分组加载自己的UIImage图片就好了。
航哥, 我用详情页图片配合ScrollView,做成可以左右滑动,查看该分类的所有图片,如果这个分类的图片数量只有30多张,那么可以正常获取,并且正常显示出来。但是如果图片张数达到上百,那么会卡住,然后程序崩溃,有什么好的解决方法么?
站长回复:建议使用CollectionView代替ScrollView做图片展示,这样其内部cell可以复用,节约资源。具体可参考我的这篇文章:Swift - 实现图片全屏展示功能(可左右滑动切换图片)
else if title == "Screenshots" {
return "屏幕快照"
}
拿到这个图片的集合,获取最新的一张图片即是用户截屏的那个图片,这个技巧可以用来处理截屏分享,获取截屏的图片,用系统的方式实现,可靠高效
站长回复:谢谢你的补充,确实是这样的。我原来也写过一篇类似的文章,不过当时是获取的是所有图片的第一张:Swift - 监听照片库里的变化(自动获取最新添加的图片)
你好,我用真机调试,相薄没用显示出来
站长回复:我把代码升级成Swift3了,顺便还做了些修改。你再试试看,应该没问题了。
缩略图片显示,有压缩拉伸现象,是怎么处理的?
站长回复:你是不是忘记把imageView设置成等比缩放了 imageView.contentMode = .scaleAspectFit
在手机相册照片比较多的情况下,获取缩略图,不会有问题么
站长回复:不会有问题,获取到的缩略图只是引用,如果不使用是不会占用什么内存的。而且后面将获取到的缩略图显示在collectionView里,由于collectionView的特性,只会加载显示当前页的图片。如果滑动collectionView,那些不在可视区域的图片又会从内存移除。