iOS Swift圖片選擇SDK開(kāi)發(fā)設(shè)計(jì)

該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)航操作。界面流程如圖所示:

界面流程.png

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í)。吼吼····

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容