該SDK設(shè)計(jì)參考微信選擇,支持預(yù)覽(支持網(wǎng)絡(luò)圖預(yù)覽及刪除)、多選、單張裁剪(一般頭像上傳用)。基本相關(guān)主要頁(yè)面有相冊(cè)選擇列表、圖片選擇列表以及預(yù)覽。僅支持iOS8以上系統(tǒng)。
GitHub源碼
一、系統(tǒng)資源的獲取
通過(guò)Photos.framework庫(kù)獲取系統(tǒng)相冊(cè),創(chuàng)建一個(gè)管理類集成PHCachingImageManager進(jìn)行對(duì)圖片各種處理,主要通過(guò)系統(tǒng)API請(qǐng)求圖片。
// 獲取縮略圖
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)
}
}
// 獲取預(yù)覽圖
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時(shí)圖片尺寸保持不變,不改變圖片大小
targetSize.width = CGFloat(width)
targetSize.height = CGFloat(height)
} else if width > 1280 && height > 1280 {
//寬以及高均大于1280,但是圖片寬高比例大于(小于)2時(shí),則寬或者高取小(大)的等比壓縮至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的參數(shù)設(shè)置的坑。具體請(qǐng)參考參數(shù)詳解。
此處獲取預(yù)覽圖替代原圖,是因?yàn)轭A(yù)覽圖的清晰度足夠滿足清晰度需求,反而加載原圖會(huì)大量消耗內(nèi)存。預(yù)覽圖的獲取規(guī)則以1280(8的倍數(shù))為臨界,具體看代碼實(shí)現(xiàn)。
該管理類中還涉及簡(jiǎn)單本地緩存方法。
二、UI布局
整體設(shè)計(jì)為一個(gè)UINavigationController,目的是為了通過(guò)模態(tài)彈出后,可以內(nèi)部進(jìn)行自己的導(dǎo)航操作。界面流程如圖所示:
1、WQPhotoNavigationViewController
該NavigationController為進(jìn)入SDK照片選擇導(dǎo)航控制器,通過(guò)初始化設(shè)置代理等各種參數(shù)設(shè)置。該類中初始化需要注意一點(diǎn),因?yàn)檫M(jìn)入SDK默認(rèn)展示所有圖片選擇viewController,點(diǎn)擊返回到相冊(cè)列表選擇viewController,所以該navigationController的rootViewController要為相冊(cè)列表VC,然后將所有圖片VCpush進(jìn)來(lái)。實(shí)現(xiàn)如下:
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)
}
該處要把默認(rèn)init()方法private處理,防止接入使用默認(rèn)init()。
2、WQPhotoAlbumListViewController
該VC沒(méi)什么可說(shuō)的,就是通過(guò)Photos庫(kù)API獲取相冊(cè)列表信息資源,然后通過(guò)UITableView實(shí)現(xiàn)展示內(nèi)容,點(diǎn)擊cell時(shí)將獲取的相冊(cè)照片傳遞個(gè)選擇VC去展示。
3、WQPhotoAlbumViewController
這個(gè)VC是默認(rèn)進(jìn)入SDK后展示所有照片,模態(tài)彈出時(shí),會(huì)默認(rèn)push展示,此時(shí)就如上圖所示。
這里只有默認(rèn)進(jìn)來(lái)時(shí)會(huì)請(qǐng)求所以照片,當(dāng)點(diǎn)擊返回相冊(cè)列表后在點(diǎn)進(jìn)來(lái)則會(huì)加載選擇相冊(cè)的照片,而無(wú)需請(qǐng)求。
還需要注意的一點(diǎn)就是,UICollectionView中cell重用的的問(wèn)題,會(huì)導(dǎo)致選中狀態(tài)UI重用,我這里的思路是在獲取的照片數(shù)據(jù)在PhotoData類中進(jìn)行存儲(chǔ)和標(biāo)記,在collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)代理方法中取相應(yīng)的標(biāo)記即可。
class WQPhotoData: NSObject {
// 判斷數(shù)據(jù)是否發(fā)生變化
var dataChanged = false
// 存儲(chǔ)每個(gè)cell選擇標(biāo)記(false:未選中,true:選中)
var divideArray = [Bool]() {
didSet {
self.dataChanged = true
}
}
// 相冊(cè)所有圖片數(shù)據(jù)源
var assetArray = [PHAsset]()
// 已選圖片數(shù)組,數(shù)據(jù)類型是 PHAsset
var seletedAssetArray = [PHAsset]()
}
// 所有圖片處理后都會(huì)是該model
public class WQPhotoModel: NSObject {
// 縮略圖
public var thumbnailImage: UIImage?
// 預(yù)覽圖
public var originImage: UIImage?
// 網(wǎng)絡(luò)圖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
}
}
這里還有個(gè)PhotoModel的類是貫通所有處理的model。當(dāng)選擇完成和需要預(yù)覽時(shí),都需通過(guò)該model進(jìn)行返回和賦值處理。
4、預(yù)覽和裁剪viewController
這里WQPhotoPreviewViewController、WQPhotoPreviewDeleteViewController、WQPhotoClipViewController三個(gè)界面設(shè)計(jì)思想基本一樣。只不過(guò)previewVC是用來(lái)內(nèi)部展示預(yù)覽使用,previewDeleteVC和clipVC是公開(kāi)外部使用。之中previewDeleteVC可以通過(guò)設(shè)置是否支持刪除。
這里為了優(yōu)化預(yù)覽體驗(yàn),使用的是預(yù)覽圖,而不是原圖,原因上面已說(shuō)明,且剛開(kāi)始進(jìn)入時(shí)優(yōu)先展示的是縮略圖,然后再請(qǐng)求預(yù)覽圖去展示(目的是為了適配iCloud圖片展示問(wèn)題和切換圖片空白問(wèn)題)。
三、導(dǎo)入SDK方式
支持Carthage動(dòng)態(tài)庫(kù),在Cartfile中添加
github "wCodeQ/WQPhotoAlbum"
功能說(shuō)明
- 模態(tài)彈出所有相冊(cè)列表,選擇圖片和預(yù)覽。
- 返回列表為相冊(cè)列表,點(diǎn)擊取消dismiss
- 單獨(dú)公開(kāi)預(yù)覽界面,支持刪除
- 支持裁剪功能
接入說(shuō)明
- 修改皮膚色
WQPhotoAlbumSkinColor = UIColor.red
- 直接跳轉(zhuǎn)所有照片,默認(rèn)是選擇圖片
let photoAlbumVC = WQPhotoNavigationViewController(photoAlbumDelegate: self, photoAlbumType: .selectPhoto) //初始化需要設(shè)置代理對(duì)象
photoAlbumVC.maxSelectCount = 10 //最大可選擇張數(shù)
self.navigationController?.present(photoAlbumVC, animated: true, completion: nil)
//跳轉(zhuǎn)裁剪圖片選擇列表
let photoAlbumVC = WQPhotoNavigationViewController(photoAlbumDelegate: self, photoAlbumType: .clipPhoto)
photoAlbumVC.clipBounds = CGSize(width: self.view.frame.width, height: 400) //裁剪框大小,不設(shè)置默認(rèn)為屏幕寬度正方形
self.navigationController?.present(photoAlbumVC, animated: true, completion: nil)
//跳轉(zhuǎn)圖片列表類型
public enum WQPhotoAlbumType {
case selectPhoto, clipPhoto
}
//實(shí)現(xiàn)WQPhotoAlbumProtocol協(xié)議獲取選擇圖片資源
@objc public protocol WQPhotoAlbumProtocol: NSObjectProtocol {
//返回圖片原資源,需要用PHCachingImageManager或者我封裝的WQCachingImageManager進(jìn)行解析處理
@available(iOS 8.0, *)
@objc optional func photoAlbum(selectPhotoAssets: [PHAsset]) -> Void
//返回WQPhotoModel數(shù)組,其中包含選擇的縮略圖和預(yù)覽圖
@available(iOS 8.0, *)
@objc optional func photoAlbum(selectPhotos: [WQPhotoModel]) -> Void
//返回裁剪后圖片
@available(iOS 8.0, *)
@objc optional func photoAlbum(clipPhoto: UIImage?) -> Void
}
- 直接預(yù)覽跳轉(zhuǎn),支持刪除
//基于WQPhotoModel中的資源,如果有原圖直接展示,否者先展示縮略圖然后加載網(wǎng)絡(luò)圖,完成后再展示原圖,已做掉緩存
let wqPhotoPreviewVC = WQPhotoPreviewDeleteViewController()
wqPhotoPreviewVC.previewPhotoArray = self.selectIamgeArr //傳入預(yù)覽源,為WQPhotoModel數(shù)組,支持縮略圖,原圖和網(wǎng)絡(luò)圖
wqPhotoPreviewVC.currentIndex = currentIndex //當(dāng)前展示第幾張
wqPhotoPreviewVC.isAllowDelete = true //設(shè)置是否支持刪除,默認(rèn)不支持,當(dāng)設(shè)置了deleteClicked閉包時(shí)默認(rèn)支持刪除
wqPhotoPreviewVC.deleteClicked = { [unowned self] (photos: [WQPhotoModel], deleteModel: WQPhotoModel) in
self.selectIamgeArr = photos
}
self.navigationController?.pushViewController(wqPhotoPreviewVC, animated: true)
- 可以根據(jù)PHAsset或者UIImage獲取相應(yīng)縮略圖和預(yù)覽圖
// WQCachingImageManager實(shí)例方法
// 獲取縮略圖
public func requestThumbnailImage(for asset: PHAsset, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID
// 獲取預(yù)覽圖
public func requestPreviewImage(for asset: PHAsset, progressHandler: Photos.PHAssetImageProgressHandler?, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID
// 根據(jù)原圖獲取縮略圖和預(yù)覽圖
public func getThumbnailAndPreviewImage(originImage: UIImage) -> (thumbnailImage: UIImage?, previewImage: UIImage?)
總結(jié):
github地址:https://github.com/wCodeQ/WQPhotoAlbum
歡迎大家點(diǎn)星??
本人第一次發(fā)布源碼和文章,可能存在好多不足的地方,望大家多多指教,共同學(xué)習(xí)。吼吼····