该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的倍数)为临界,具体看代码实现。
该管理类中还涉及简单本地缓存方法。
二、UI布局
整体设计为一个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图片展示问题和切换图片空白问题)。
三、导入SDK方式
支持Carthage动态库,在Cartfile中添加
github "wCodeQ/WQPhotoAlbum"
功能说明
- 模态弹出所有相册列表,选择图片和预览。
- 返回列表为相册列表,点击取消dismiss
- 单独公开预览界面,支持删除
- 支持裁剪功能
接入说明
- 修改皮肤色
WQPhotoAlbumSkinColor = UIColor.red
- 直接跳转所有照片,默认是选择图片
let photoAlbumVC = WQPhotoNavigationViewController(photoAlbumDelegate: self, photoAlbumType: .selectPhoto) //初始化需要设置代理对象
photoAlbumVC.maxSelectCount = 10 //最大可选择张数
self.navigationController?.present(photoAlbumVC, animated: true, completion: nil)
//跳转裁剪图片选择列表
let photoAlbumVC = WQPhotoNavigationViewController(photoAlbumDelegate: self, photoAlbumType: .clipPhoto)
photoAlbumVC.clipBounds = CGSize(width: self.view.frame.width, height: 400) //裁剪框大小,不设置默认为屏幕宽度正方形
self.navigationController?.present(photoAlbumVC, animated: true, completion: nil)
//跳转图片列表类型
public enum WQPhotoAlbumType {
case selectPhoto, clipPhoto
}
//实现WQPhotoAlbumProtocol协议获取选择图片资源
@objc public protocol WQPhotoAlbumProtocol: NSObjectProtocol {
//返回图片原资源,需要用PHCachingImageManager或者我封装的WQCachingImageManager进行解析处理
@available(iOS 8.0, *)
@objc optional func photoAlbum(selectPhotoAssets: [PHAsset]) -> Void
//返回WQPhotoModel数组,其中包含选择的缩略图和预览图
@available(iOS 8.0, *)
@objc optional func photoAlbum(selectPhotos: [WQPhotoModel]) -> Void
//返回裁剪后图片
@available(iOS 8.0, *)
@objc optional func photoAlbum(clipPhoto: UIImage?) -> Void
}
- 直接预览跳转,支持删除
//基于WQPhotoModel中的资源,如果有原图直接展示,否者先展示缩略图然后加载网络图,完成后再展示原图,已做掉缓存
let wqPhotoPreviewVC = WQPhotoPreviewDeleteViewController()
wqPhotoPreviewVC.previewPhotoArray = self.selectIamgeArr //传入预览源,为WQPhotoModel数组,支持缩略图,原图和网络图
wqPhotoPreviewVC.currentIndex = currentIndex //当前展示第几张
wqPhotoPreviewVC.isAllowDelete = true //设置是否支持删除,默认不支持,当设置了deleteClicked闭包时默认支持删除
wqPhotoPreviewVC.deleteClicked = { [unowned self] (photos: [WQPhotoModel], deleteModel: WQPhotoModel) in
self.selectIamgeArr = photos
}
self.navigationController?.pushViewController(wqPhotoPreviewVC, animated: true)
- 可以根据PHAsset或者UIImage获取相应缩略图和预览图
// WQCachingImageManager实例方法
// 获取缩略图
public func requestThumbnailImage(for asset: PHAsset, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID
// 获取预览图
public func requestPreviewImage(for asset: PHAsset, progressHandler: Photos.PHAssetImageProgressHandler?, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID
// 根据原图获取缩略图和预览图
public func getThumbnailAndPreviewImage(originImage: UIImage) -> (thumbnailImage: UIImage?, previewImage: UIImage?)
总结:
github地址:https://github.com/wCodeQ/WQPhotoAlbum
欢迎大家点星
本人第一次发布源码和文章,可能存在好多不足的地方,望大家多多指教,共同学习。吼吼····