当前位置: > > > Swift - 使用PhotoKit获取照片2(列出所有相簿、并选择)

Swift - 使用PhotoKit获取照片2(列出所有相簿、并选择)

(本文代码已升级至Swift3)

在前面一篇文章中: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()
    }
}

--- 缩略图展示页 CollectionViewController.swift ---
(注意:高亮部分表示相较于前文,修改过的地方)
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,源码下载hangge_1233.zip
评论6
  • 6楼
    2018-01-12 16:05
    玩牌的鱼

    如何把UIImage转成PHAsset?教程中是能获取到系统的相册,但是如果我需要在相册分组的最后加上自己的图片,而自己的图片是UIImage, 跟PHAsset不同,这个可以转换么?我目前想到的是将图片先导入到系统相册中,并新建一个分类,然后再从相册中导入,但感觉这样有些麻烦,能否直接将自己的图片新建分组放到最后?

    站长回复

    不用把UIImage转成PHAsse的,你就直接在相册分组最后面加上一行(自己的图片分组),点击分组时判断一下,如果是自定义的这个分组加载自己的UIImage图片就好了。

  • 5楼
    2017-12-18 15:50
    玩牌的鱼

    航哥, 我用详情页图片配合ScrollView,做成可以左右滑动,查看该分类的所有图片,如果这个分类的图片数量只有30多张,那么可以正常获取,并且正常显示出来。但是如果图片张数达到上百,那么会卡住,然后程序崩溃,有什么好的解决方法么?

    站长回复

    建议使用CollectionView代替ScrollView做图片展示,这样其内部cell可以复用,节约资源。具体可参考我的这篇文章:Swift - 实现图片全屏展示功能(可左右滑动切换图片)

  • 4楼
    2017-07-31 11:44
    buoge

    else if title == "Screenshots" {
    return "屏幕快照"
    }
    拿到这个图片的集合,获取最新的一张图片即是用户截屏的那个图片,这个技巧可以用来处理截屏分享,获取截屏的图片,用系统的方式实现,可靠高效

    站长回复

    谢谢你的补充,确实是这样的。我原来也写过一篇类似的文章,不过当时是获取的是所有图片的第一张:Swift - 监听照片库里的变化(自动获取最新添加的图片)

  • 3楼
    2016-12-01 10:54
    太阳

    你好,我用真机调试,相薄没用显示出来

    站长回复

    我把代码升级成Swift3了,顺便还做了些修改。你再试试看,应该没问题了。

  • 2楼
    2016-11-16 09:26
    上孟

    缩略图片显示,有压缩拉伸现象,是怎么处理的?

    站长回复

    你是不是忘记把imageView设置成等比缩放了 imageView.contentMode = .scaleAspectFit

  • 1楼
    2016-07-09 15:00
    清晨

    在手机相册照片比较多的情况下,获取缩略图,不会有问题么

    站长回复

    不会有问题,获取到的缩略图只是引用,如果不使用是不会占用什么内存的。而且后面将获取到的缩略图显示在collectionView里,由于collectionView的特性,只会加载显示当前页的图片。如果滑动collectionView,那些不在可视区域的图片又会从内存移除。