iOS Swift图片选择SDK开发设计

该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
欢迎大家点星��
本人第一次发布源码和文章,可能存在好多不足的地方,望大家多多指教,共同学习。吼吼····

你可能感兴趣的:(图片选择)