该SDK设计参考微信选择,支持预览(支持网络图预览及删除)、多选、单张裁剪(一般头像上传用)。基本相关主要页面有相册选择列表、图片选择列表以及预览。仅支持iOS8以上系统。 GitHub源码
通过Photos.framework库获取系统相册,创建一个管理类集成PHCachingImageManager进行对图片各种处理,主要通过系统API请求图片。
// 获取缩略图 public func requestThumbnailImage(for asset: PHAsset, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID { let option = PHImageRequestOptions() // option.resizeMode = .fast let targetSize = self.getThumbnailSize(originSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight)) return self.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: option) { (image: UIImage?, dictionry: Dictionary?) in resultHandler(image, dictionry) } } // 获取预览图 public func requestPreviewImage(for asset: PHAsset, progressHandler: Photos.PHAssetImageProgressHandler?, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID { let option = PHImageRequestOptions() // option.version = .current option.resizeMode = .exact // option.deliveryMode = .fastFormat option.isNetworkAccessAllowed = true option.progressHandler = progressHandler let targetSize = self.getPriviewSize(originSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight)) return self.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: option) { (image: UIImage?, dictionry: Dictionary?) in resultHandler(image, dictionry) } } private func getPriviewSize(originSize: CGSize) -> CGSize { let width = originSize.width let height = originSize.height let pixelScale = CGFloat(width)/CGFloat(height) var targetSize = CGSize() if width <= 1280 && height <= 1280 { //a,图片宽或者高均小于或等于1280时图片尺寸保持不变,不改变图片大小 targetSize.width = CGFloat(width) targetSize.height = CGFloat(height) } else if width > 1280 && height > 1280 { //宽以及高均大于1280,但是图片宽高比例大于(小于)2时,则宽或者高取小(大)的等比压缩至1280 if pixelScale > 2 { targetSize.width = 1280*pixelScale targetSize.height = 1280 } else if pixelScale < 0.5 { targetSize.width = 1280 targetSize.height = 1280/pixelScale } else if pixelScale > 1 { targetSize.width = 1280 targetSize.height = 1280/pixelScale } else { targetSize.width = 1280*pixelScale targetSize.height = 1280 } } else { //b,宽或者高大于1280,但是图片宽度高度比例小于或等于2,则将图片宽或者高取大的等比压缩至1280 if pixelScale <= 2 && pixelScale > 1 { targetSize.width = 1280 targetSize.height = 1280/pixelScale } else if pixelScale > 0.5 && pixelScale <= 1 { targetSize.width = 1280*pixelScale targetSize.height = 1280 } else { targetSize.width = CGFloat(width) targetSize.height = CGFloat(height) } } return targetSize }这里需要注意requestImage的参数设置的坑。具体请参考参数详解。 此处获取预览图替代原图,是因为预览图的清晰度足够满足清晰度需求,反而加载原图会大量消耗内存。预览图的获取规则以1280(8的倍数)为临界,具体看代码实现。 该管理类中还涉及简单本地缓存方法。
整体设计为一个UINavigationController,目的是为了通过模态弹出后,可以内部进行自己的导航操作。界面流程如图所示:
1、WQPhotoNavigationViewController 该NavigationController为进入SDK照片选择导航控制器,通过初始化设置代理等各种参数设置。该类中初始化需要注意一点,因为进入SDK默认展示所有图片选择viewController,点击返回到相册列表选择viewController,所以该navigationController的rootViewController要为相册列表VC,然后将所有图片VCpush进来。实现如下:
private let photoAlbumVC = WQPhotoAlbumViewController() private convenience init() { self.init(photoAlbumDelegate: nil, photoAlbumType: .selectPhoto) } public init(photoAlbumDelegate: WQPhotoAlbumProtocol?, photoAlbumType: WQPhotoAlbumType) { let photoAlbumListVC = WQPhotoAlbumListViewController() photoAlbumListVC.photoAlbumDelegate = photoAlbumDelegate photoAlbumListVC.type = photoAlbumType super.init(rootViewController: photoAlbumListVC) self.isNavigationBarHidden = true photoAlbumVC.photoAlbumDelegate = photoAlbumDelegate photoAlbumVC.type = photoAlbumType self.pushViewController(photoAlbumVC, animated: false) }该处要把默认init()方法private处理,防止接入使用默认init()。 2、WQPhotoAlbumListViewController 该VC没什么可说的,就是通过Photos库API获取相册列表信息资源,然后通过UITableView实现展示内容,点击cell时将获取的相册照片传递个选择VC去展示。 3、WQPhotoAlbumViewController 这个VC是默认进入SDK后展示所有照片,模态弹出时,会默认push展示,此时就如上图所示。 这里只有默认进来时会请求所以照片,当点击返回相册列表后在点进来则会加载选择相册的照片,而无需请求。 还需要注意的一点就是,UICollectionView中cell重用的的问题,会导致选中状态UI重用,我这里的思路是在获取的照片数据在PhotoData类中进行存储和标记,在collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)代理方法中取相应的标记即可。
class WQPhotoData: NSObject { // 判断数据是否发生变化 var dataChanged = false // 存储每个cell选择标记(false:未选中,true:选中) var divideArray = [Bool]() { didSet { self.dataChanged = true } } // 相册所有图片数据源 var assetArray = [PHAsset]() // 已选图片数组,数据类型是 PHAsset var seletedAssetArray = [PHAsset]() } // 所有图片处理后都会是该model public class WQPhotoModel: NSObject { // 缩略图 public var thumbnailImage: UIImage? // 预览图 public var originImage: UIImage? // 网络图URL public var imageURL: String? public convenience override init() { self.init(thumbnailImage: nil, originImage: nil, imageURL: nil) } public init(thumbnailImage: UIImage?, originImage: UIImage?, imageURL: String?) { self.thumbnailImage = thumbnailImage self.originImage = originImage self.imageURL = imageURL } }这里还有个PhotoModel的类是贯通所有处理的model。当选择完成和需要预览时,都需通过该model进行返回和赋值处理。 4、预览和裁剪viewController 这里WQPhotoPreviewViewController、WQPhotoPreviewDeleteViewController、WQPhotoClipViewController三个界面设计思想基本一样。只不过previewVC是用来内部展示预览使用,previewDeleteVC和clipVC是公开外部使用。之中previewDeleteVC可以通过设置是否支持删除。 这里为了优化预览体验,使用的是预览图,而不是原图,原因上面已说明,且刚开始进入时优先展示的是缩略图,然后再请求预览图去展示(目的是为了适配iCloud图片展示问题和切换图片空白问题)。
支持Carthage动态库,在Cartfile中添加 github “wCodeQ/WQPhotoAlbum”
github地址:https://github.com/wCodeQ/WQPhotoAlbum 欢迎大家点星�� 本人第一次发布源码和文章,可能存在好多不足的地方,望大家多多指教,共同学习。吼吼····
