授人以鱼不如授人以渔,今天就谈谈如何利用Photos框架自定义照片选择器。
目录代码:
AlbumManager.swift
//
// CustomAlbum.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/3.
// Copyright © 2017年 Artron. All rights reserved.
//
// AlbumManager.shared 提供请求相册、请求图片、创建相册、保存相片等操作。
//
import Foundation
import UIKit
import Photos
/// 需要的图片尺寸:缩略图或原图
///
/// - Thumbnail: 缩略图
/// - Origin: 原图
/// - All: 原图和缩略图
enum ImageSizeType {
case Thumbnail
case Origin
case All
}
class AlbumManager: NSObject, PHPhotoLibraryChangeObserver {
static let shared = AlbumManager()
var albumType: PHAssetCollectionType = .smartAlbum
var albumSubType: PHAssetCollectionSubtype = .albumRegular
fileprivate let imageManager = PHCachingImageManager()
fileprivate var thumbnailSize: CGSize = CGSize(width: (screenWidth-spacing*5)/4, height: (screenWidth-spacing*5)/4)
private override init() {}
// 创建相册
func createAlbum(albumName: String, completionHandler: @escaping (_ success: Bool, _ error: Error?) -> ()) {
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumName)
}, completionHandler: { success, error in
completionHandler(success, error)
})
}
/// 保存图片到相册
///
/// - Parameters:
/// - image: 需要保存的图片
/// - album: 指定相册
/// - Returns: 保存是否成功
func addAsset(image: UIImage, to album: PHAssetCollection) -> Bool {
var isSuccess = false
PHPhotoLibrary.shared().performChanges({
// 为image 创建asset
let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
// 请求编辑相册
guard let addAssetRequest = PHAssetCollectionChangeRequest(for: album)
else { return }
// 创建一个占位符然后将图片asset添加进去
addAssetRequest.addAssets([creationRequest.placeholderForCreatedAsset!] as NSArray)
}, completionHandler: { success, error in
if !success { NSLog("error:添加asset失败 \(error.debugDescription)") }
isSuccess = success
})
return isSuccess
}
/***************************************************************************/
// MARK: Public Methods
/// 获取所有相册model
///
/// - Returns: [AlbumModel]
func fetchAlbumModels() -> [AlbumModel] {
var models = [AlbumModel]()
// 智能相册
let collections = fetchCollection(type: albumType, subType: albumSubType)
collections.enumerateObjects({ (collection, index, stop) in
// 过滤掉删除/视频的相册
if collection.localizedTitle?.range(of: "Deleted") != nil || collection.localizedTitle?.range(of: "Videos") != nil { return }
// 过滤掉没有照片的相册
let assetResult = PHAsset.fetchAssets(in: collection, options: nil)
if assetResult.count == 0 { return }
let model = AlbumModel(collection: collection)
models.append(model)
})
//获取用户创建的相册
let userCollections = PHAssetCollection.fetchTopLevelUserCollections(with: nil)
userCollections.enumerateObjects({ (collection, index, stop) in
guard let collection:PHAssetCollection = collection as? PHAssetCollection else {
return
}
let assetResult = PHAsset.fetchAssets(in: collection, options: nil)
if assetResult.count == 0 { return }
let model = AlbumModel(collection: collection)
models.append(model)
})
return models
}
/// 从某一相册获取model
///
/// - Parameter result:PHFetchResult<PHAsset>
/// - Returns:[PhotoModel]
func fetchPhotoModels(result: PHFetchResult<PHAsset>) -> [PhotoModel] {
var models = [PhotoModel]()
result.enumerateObjects({ (asset, index, stop) in
let model = PhotoModel(asset: asset)
models.append(model)
})
return models
}
/// 从PHAssetCollection获取 PHFetchResult<PHAsset>
///
/// - Parameter assetCollection: PHAssetCollection
/// - Returns: PHFetchResult<PHAsset>
func fetchResult(in assetCollection: PHAssetCollection) -> PHFetchResult<PHAsset> {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
//这里只检索照片
allPhotosOptions.predicate = NSPredicate.init(format: "mediaType in %@", [PHAssetMediaType.image.rawValue])
return PHAsset.fetchAssets(in: assetCollection, options: allPhotosOptions)
}
// 获取所有照片
func fetchAllPhotosResult() -> PHFetchResult<PHAsset> {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
//这里只检索照片
allPhotosOptions.predicate = NSPredicate.init(format: "mediaType in %@", [PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue])
return PHAsset.fetchAssets(with: allPhotosOptions)
}
/// 获取相册
///
/// - Parameters:
/// - type: PHAssetCollectionType
/// - subType: PHAssetCollectionSubtype
/// - Returns: PHFetchResult<PHAssetCollection>
func fetchCollection(type: PHAssetCollectionType, subType: PHAssetCollectionSubtype) -> PHFetchResult<PHAssetCollection> {
let options = PHFetchOptions()
return PHAssetCollection.fetchAssetCollections(with: type, subtype: subType, options: options)
}
func fetchCollection(name: String) -> PHAssetCollection? {
let collections = fetchCollection(type: albumType, subType: albumSubType)
var result: PHAssetCollection?
collections.enumerateObjects({ (collection, index, stop) in
if collection.localizedTitle == name {
result = collection
}
})
return result
}
/// 从PHAsset中获取data
///
/// - Parameters:
/// - asset: 目标PHAsset
/// - options: 定制options
/// - Returns: Data?
func fetchPhotoData(in asset: PHAsset, options: PHImageRequestOptions?) -> Data? {
var resultData: Data?
imageManager.requestImageData(for: asset, options: options) { (data, string, orientation, info) in
resultData = data
}
return resultData
}
/// 默认配置的options
///
/// - Returns: PHImageRequestOptions
func defaultOptions() -> PHImageRequestOptions {
let imageRequestOption = PHImageRequestOptions()
imageRequestOption.isSynchronous = false
imageRequestOption.isNetworkAccessAllowed = true
imageRequestOption.deliveryMode = .opportunistic
return imageRequestOption
}
/***************************************************************************/
// MARK: PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
DispatchQueue.main.sync {
}
}
}
Common.swift
//
// Common.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/5.
// Copyright © 2017年 Artron. All rights reserved.
//
import Foundation
import UIKit
/// 自定义打印方法
///
/// - Parameters:
/// - message: 需要打印的信息
/// - file: 当前文件名
/// - method: 当前方法名
/// - line: 当前行
func printLog<T>(message: T,
file: String = #file,
method: String = #function,
line: Int = #line)
{
#if DEBUG
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
#endif
}
let screenWidth: CGFloat = UIScreen.main.bounds.width
let screenHeight: CGFloat = UIScreen.main.bounds.height
let screenScale: CGFloat = UIScreen.main.scale
let defaultBackColor = UIColor.colorFromString("#f0f8ff")
func fontRoundedBold(size: CGFloat) -> UIFont {
return UIFont(name: "Arial Rounded MT Bold", size: size)!
}
func fontMarkerFelt(size: CGFloat) -> UIFont {
return UIFont(name: "Marker Felt", size: size)!
}
func fontSystem(size: CGFloat) -> UIFont {
return UIFont.systemFont(ofSize: size)
}
// 将时间转成 00:00:00 格式字符串
func stringFromTimeInterval(interval: TimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
AlbumModel
//
// AlbumModel.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/9.
// Copyright © 2017年 Artron. All rights reserved.
//
import Foundation
import UIKit
import Photos
struct AlbumModel {
var albumName: String! = "" //相册名称
var result: [PhotoModel]! //资源集合
var lastPhotoAsset: PHAsset?
var photoesCount: Int { //资源数量
get {
return result.count
}
}
init(collection: PHAssetCollection) {
albumName = collection.localizedTitle
let assetResult = AlbumManager.shared.fetchResult(in: collection)
result = AlbumManager.shared.fetchPhotoModels(result: assetResult)
// 判断相册中是否有资源
if result.isEmpty { return }
// 判断资源是否可以转成图片
lastPhotoAsset = result[result.count-1].asset
}
}
PhotoModel
//
// AlbumImageModel.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/8.
// Copyright © 2017年 Artron. All rights reserved.
//
import Foundation
import UIKit
import Photos
struct PhotoModel {
var iselected: Bool = false
var canSelect: Bool = true
var asset: PHAsset
init(asset: PHAsset) {
self.asset = asset
}
}
AlbumListViewController.swift
//
// AlbumListViewController.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/9.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
private let cellIdentifier: String = String(describing: AlbumListCell.self)
class AlbumListViewController: UITableViewController {
var albumList = [AlbumModel]()
var maxSelectCount: Int = 10
override func viewDidLoad() {
super.viewDidLoad()
customUI()
}
private func customUI() {
self.title = "照片"
self.view.backgroundColor = defaultBackColor
self.tableView.register(AlbumListCell.self, forCellReuseIdentifier: cellIdentifier)
self.tableView.tableFooterView = UIView()
let rightBtn: UIButton = UIButton(type: .custom)
rightBtn.addTarget(self, action: #selector(rightAction(btn:)), for: UIControlEvents.touchUpInside)
rightBtn.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
rightBtn.setTitle("取消", for: UIControlState.normal)
rightBtn.setTitleColor(UIColor.colorFromString("#1E90FF"), for: .normal)
rightBtn.titleLabel?.font = fontSystem(size: 16)
let right: UIBarButtonItem = UIBarButtonItem(customView: rightBtn)
self.navigationItem.rightBarButtonItem = right
}
func rightAction(btn: UIButton) {
let navi: AlbumViewController = self.navigationController as! AlbumViewController
navi.albumDelegate?.didCancelSelect?()
self.dismiss(animated: true, completion: nil)
}
deinit {
printLog(message: "AlbumListController deinit")
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return albumList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: AlbumListCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! AlbumListCell
let model: AlbumModel = albumList[indexPath.row]
cell.loadData(model: model)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellWidth + 2 * spacing
}
// MARK: Delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = albumList[indexPath.row]
let photoListVC: PhotoListViewController = PhotoListViewController()
photoListVC.models = model.result
photoListVC.title = model.albumName
photoListVC.maxSelectCount = self.maxSelectCount
self.navigationController?.pushViewController(photoListVC, animated: true)
}
}
AlbumViewController.swift
//
// AlbumViewController.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/9.
// Copyright © 2017年 Artron. All rights reserved.
//
// Album入口VC,初始化时由CollectionType选择进入相册列表还是直接查看所有照片。
// AlbumViewControllerDelegate协议的`didSelect`方法获取所有选中的照片资源。
import UIKit
import Photos
enum CollectionType {
case albumList
case photoList
}
@objc protocol AlbumViewControllerDelegate: NSObjectProtocol {
func didSelect(photoAssets: [PHAsset])
@objc optional func didCancelSelect()
}
class AlbumViewController: UINavigationController {
weak var albumDelegate: AlbumViewControllerDelegate?
convenience init(type collectionType: CollectionType, maxSelectCount: Int = 10){
var vc: UIViewController
switch collectionType {
case .albumList:
let albumVC = AlbumListViewController()
albumVC.albumList = AlbumManager.shared.fetchAlbumModels()
albumVC.maxSelectCount = maxSelectCount
vc = albumVC
break
default:
let photoVC = PhotoListViewController()
let allPhotoResult = AlbumManager.shared.fetchAllPhotosResult()
photoVC.models = AlbumManager.shared.fetchPhotoModels(result: allPhotoResult)
photoVC.maxSelectCount = maxSelectCount
vc = photoVC
break
}
self.init(rootViewController: vc)
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.barTintColor = UIColor.colorFromString("#E6E9ED")
self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.colorFromString("#434a54")]
}
}
PhotoListViewController.swift
//
// ShowPhotosViewController.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/4.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
import Photos
import PhotosUI
let spacing: CGFloat = 3
let cellWidth = (screenWidth - spacing * 5) / 4
class PhotoListViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
// 选中照片集合
var selectedAssets: Array<PHAsset> = Array() {
didSet {
selectedCountDidChange()
}
}
// 选中数量
var selectedCount: Int {
get {
return selectedAssets.count
}
}
var maxSelectCount: Int = 10 //最大选择数量,默认10张
var models = [PhotoModel]()
fileprivate let imageManager = PHCachingImageManager()
fileprivate var thumbnailSize: CGSize! = CGSize(width: cellWidth * UIScreen.main.scale, height: cellWidth * UIScreen.main.scale)
fileprivate var previousPreheatRect = CGRect.zero
fileprivate let editBtn = UIButton(type: .system)
fileprivate let previewBtn = UIButton(type: .system)
fileprivate let sendBtn = UIButton(type: .system)
fileprivate let countLabel = UILabel()
lazy var collectionView: UICollectionView = {
let flowLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: cellWidth , height: cellWidth)
flowLayout.sectionInset = UIEdgeInsetsMake(spacing, spacing, spacing, spacing)
flowLayout.minimumInteritemSpacing = spacing;
flowLayout.minimumLineSpacing = spacing;
let rect: CGRect = CGRect(x: 0, y: 64, width: screenWidth, height: screenHeight - 64 - 49)
let collectionView = UICollectionView(frame: rect, collectionViewLayout: flowLayout)
collectionView.backgroundColor = defaultBackColor
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(PhotoListCell.self, forCellWithReuseIdentifier: String(describing: PhotoListCell.self))
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
customUI()
resetCachedAssets()
}
fileprivate func customUI() {
view.addSubview(collectionView)
self.automaticallyAdjustsScrollViewInsets = false
self.navigationController?.toolbar.isHidden = false
view.backgroundColor = defaultBackColor
let tool: UIView = UIView(frame: CGRect(x: 0, y: screenHeight - 49, width: screenWidth, height: 49))
tool.backgroundColor = UIColor.colorFromString("#e6e9ed")
let line: CAShapeLayer = CAShapeLayer()
line.frame = CGRect(x: 0, y: 0, width: screenWidth, height: 0.5)
line.backgroundColor = UIColor.colorFromString("#656d78").cgColor
tool.layer.addSublayer(line)
view.addSubview(tool)
let normalColor = UIColor.colorFromString("#4a89dc")
let disableColor = UIColor.colorFromString("#aab2bd")
// 编辑按钮
editBtn.frame = CGRect(x: 8, y: 4, width: 40, height: 40)
editBtn.setTitle("edit", for: UIControlState.normal)
editBtn.setTitleColor(normalColor, for: UIControlState.normal)
editBtn.setTitleColor(disableColor, for: UIControlState.disabled)
editBtn.titleLabel?.font = fontMarkerFelt(size: 16)
editBtn.addTarget(self, action: #selector(editAction), for: UIControlEvents.touchUpInside)
editBtn.isEnabled = false
tool.addSubview(editBtn)
// 预览按钮
previewBtn.frame = CGRect(x: 60, y: 4, width: 60, height: 40)
previewBtn.setTitle("preview", for: UIControlState.normal)
previewBtn.setTitleColor(normalColor, for: UIControlState.normal)
previewBtn.setTitleColor(disableColor, for: UIControlState.disabled)
previewBtn.titleLabel?.font = fontMarkerFelt(size: 16)
previewBtn.addTarget(self, action: #selector(previewAction), for: UIControlEvents.touchUpInside)
previewBtn.isEnabled = false
tool.addSubview(previewBtn)
// 使用按钮
sendBtn.frame = CGRect(x: screenWidth - 50, y: 4, width: 40, height: 40)
sendBtn.setTitle("select", for: UIControlState.normal)
sendBtn.setTitleColor(normalColor, for: UIControlState.normal)
sendBtn.setTitleColor(disableColor, for: UIControlState.disabled)
sendBtn.titleLabel?.font = fontMarkerFelt(size: 16)
sendBtn.addTarget(self, action: #selector(sendAction), for: UIControlEvents.touchUpInside)
sendBtn.isEnabled = false
tool.addSubview(sendBtn)
// 数量标签:
countLabel.frame = CGRect(x: 0, y: 4, width: 40, height: 40)
countLabel.center = CGPoint(x: tool.center.x, y: countLabel.center.y)
countLabel.font = fontMarkerFelt(size: 18)
countLabel.textColor = UIColor.colorFromString("#434a5d")
countLabel.textAlignment = .center
countLabel.text = "(\(selectedCount))"
tool.addSubview(countLabel)
let rightBtn: UIButton = UIButton(type: .custom)
rightBtn.addTarget(self, action: #selector(rightAction(btn:)), for: UIControlEvents.touchUpInside)
rightBtn.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
rightBtn.setTitle("取消", for: UIControlState.normal)
rightBtn.setTitleColor(UIColor.colorFromString("#1E90FF"), for: .normal)
rightBtn.titleLabel?.font = fontSystem(size: 16)
let right: UIBarButtonItem = UIBarButtonItem(customView: rightBtn)
self.navigationItem.rightBarButtonItem = right
// let lastIndexpath = IndexPath(item: models.count - 1 , section: 0)
// collectionView.scrollToItem(at: lastIndexpath, at: UICollectionViewScrollPosition.bottom, animated: false)
}
func rightAction(btn: UIButton) {
let navi: AlbumViewController = self.navigationController as! AlbumViewController
navi.albumDelegate?.didCancelSelect?()
self.dismiss(animated: true, completion: nil)
}
deinit {
printLog(message: "PhotoListViewController deinit")
}
// MARK: Button Actions
func previewAction() {
printLog(message: "预览")
}
func editAction() {
printLog(message: "编辑")
}
func sendAction() {
printLog(message: "使用")
if selectedCount == 0 {
let alertView = UIAlertView(title: "提示", message: "您没有选中任何照片", delegate: self, cancelButtonTitle: "确定")
alertView.show()
} else {
let navi: AlbumViewController = self.navigationController as! AlbumViewController
if navi.albumDelegate != nil {
navi.albumDelegate?.didSelect(photoAssets: selectedAssets)
}
}
self.dismiss(animated: true, completion: nil)
}
// MARK: 选中照片数改变的监听
func selectedCountDidChange() {
countLabel.text = "(\(selectedCount))"
if selectedCount > 0 {
previewBtn.isEnabled = true
editBtn.isEnabled = true
sendBtn.isEnabled = true
} else {
previewBtn.isEnabled = false
editBtn.isEnabled = false
sendBtn.isEnabled = false
}
// 选择数量>=最大选择数时,需要更新models的可选状态,非选中的model,其可选状态设为false。选择数量<最大选择数时,将models可选状态都重新设为可选
if selectedCount < maxSelectCount {
for i in 0 ..< models.count {
var model = models[i]
if model.canSelect == false {
model.canSelect = true
models[i] = model
}
}
} else {
for i in 0 ..< models.count {
var model = models[i]
if model.iselected == false {
model.canSelect = false
models[i] = model
}
}
}
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let indexPathes = collectionView.indexPathsForElements(in: visibleRect)
collectionView.reloadItems(at: indexPathes)
}
// MARK:UICollectionView DataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PhotoListCell.self), for: indexPath) as? PhotoListCell
else { fatalError("unexpected cell in collection view") }
let model = models[indexPath.row]
let asset = model.asset
// 添加Live Photo标识
if #available(iOS 9.1, *) {
if asset.mediaSubtypes.contains(.photoLive) {
cell.livePhotoBadgeImageView.image = PHLivePhotoView.livePhotoBadgeImage(options: .overContent)
cell.contentView.addSubview(cell.livePhotoBadgeImageView)
} else {
cell.livePhotoBadgeImageView.image = nil
cell.livePhotoBadgeImageView.removeFromSuperview()
}
}
cell.loadData(model:self.models[indexPath.row])
cell.selectPhotoDelegate = self
return cell
}
// MARK: delegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! PhotoListCell
if selectedCount == maxSelectCount && cell.selecedImage == false {
let alertView = UIAlertView(title: "不能再多选了,这么多够了!", message: "最多只能选择\(maxSelectCount)张照片", delegate: self, cancelButtonTitle: "确定")
alertView.show()
return
}
let preview: PreviewController = PreviewController()
preview.currentIndexPath = indexPath
preview.maxSelectCount = maxSelectCount
preview.photos = models
preview.selectedAssets = selectedAssets
preview.previewDelegate = self
self.navigationController?.pushViewController(preview, animated: true)
}
// MARK: UIScrollView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateCachedAssets()
}
// MARK: Asset Caching
fileprivate func resetCachedAssets() {
imageManager.stopCachingImagesForAllAssets()
previousPreheatRect = .zero
}
//
fileprivate func updateCachedAssets() {
// 只更新可见区域内的视图
guard isViewLoaded && view.window != nil else { return }
// 预见区高度为可见区高度的2倍
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let preheatRect = visibleRect.insetBy(dx: 0, dy: -0.5 * visibleRect.height)
// 可见区与最后的预见区有明显差异再更新(减少更新频率)
let delta = abs(preheatRect.midY - previousPreheatRect.midY)
guard delta > view.bounds.height / 3 else { return }
// 计算缓存起点和终点
let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect)
let addedAssets = addedRects
.flatMap { rect in collectionView.indexPathsForElements(in: rect) }
.map { indexPath in models[indexPath.row].asset }
let removedAssets = removedRects
.flatMap { rect in collectionView.indexPathsForElements(in: rect) }
.map { indexPath in models[indexPath.row].asset }
// 更新PHCachingImageManager正在缓存的assets.
imageManager.startCachingImages(for: addedAssets,
targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
imageManager.stopCachingImages(for: removedAssets,
targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
// 存储最后的预见区
previousPreheatRect = preheatRect
}
fileprivate func differencesBetweenRects(_ old: CGRect, _ new: CGRect) -> (added: [CGRect], removed: [CGRect]) {
if old.intersects(new) {
var added = [CGRect]()
if new.maxY > old.maxY {
added += [CGRect(x: new.origin.x, y: old.maxY,
width: new.width, height: new.maxY - old.maxY)]
}
if old.minY > new.minY {
added += [CGRect(x: new.origin.x, y: new.minY,
width: new.width, height: old.minY - new.minY)]
}
var removed = [CGRect]()
if new.maxY < old.maxY {
removed += [CGRect(x: new.origin.x, y: new.maxY,
width: new.width, height: old.maxY - new.maxY)]
}
if old.minY < new.minY {
removed += [CGRect(x: new.origin.x, y: old.minY,
width: new.width, height: new.minY - old.minY)]
}
return (added, removed)
} else {
return ([new], [old])
}
}
private func drawColorImage()-> UIImage {
// 创建随机颜色和方向的图片
let size = (arc4random_uniform(2) == 0) ?
CGSize(width: 400, height: 300) :
CGSize(width: 300, height: 400)
var image: UIImage?
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(size: size)
image = renderer.image { context in
UIColor(hue: CGFloat(arc4random_uniform(100))/100,
saturation: 1, brightness: 1, alpha: 1).setFill()
context.fill(context.format.bounds)
}
} else {
//开启图片上下文
UIGraphicsBeginImageContext(size)
//从当前上下文获取图片
image = UIGraphicsGetImageFromCurrentImageContext()
//关闭上下文
UIGraphicsEndImageContext()
}
return image!
}
}
private extension UICollectionView {
// 获取区域内所有IndexPath
func indexPathsForElements(in rect: CGRect) -> [IndexPath] {
let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
return allLayoutAttributes.map { $0.indexPath }
}
}
// MARK: SelectPhotoProtocol
extension PhotoListViewController: SelectPhotoProtocol {
func selectPhoto(cell: PhotoListCell) {
let indexPath = collectionView.indexPath(for: cell)!
var model = models[indexPath.row]
cell.selecedImage = !cell.selecedImage
model.iselected = cell.selecedImage
models[indexPath.row] = model
if model.iselected == true {
selectedAssets.append(model.asset)
} else {
for i in 0 ..< selectedAssets.count {
let asset = selectedAssets[i]
if asset.localIdentifier == model.asset.localIdentifier {
selectedAssets.remove(at: i)
return
}
}
}
}
}
extension PhotoListViewController: PreviewProtocol {
func previewChangeSelection(indexPath: IndexPath) {
let cell: PhotoListCell? = collectionView.cellForItem(at: indexPath) as? PhotoListCell
if cell == nil {
collectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.bottom, animated: false)
}
var model = models[indexPath.row]
model.iselected = !model.iselected
models[indexPath.row] = model
if model.iselected == true {
selectedAssets.append(model.asset)
} else {
for i in 0 ..< selectedAssets.count {
let asset = selectedAssets[i]
if asset.localIdentifier == model.asset.localIdentifier {
selectedAssets.remove(at: i)
return
}
}
}
}
}
PreviewController.swift
//
// PreviewController.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/15.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
import Photos
@objc protocol PreviewProtocol {
@objc optional func previewChangeSelection(indexPath: IndexPath)
}
private let cellIdentifier: String = String(describing: PreviewCell.self)
private let cellMargin: CGFloat = 15.0
class PreviewController: UICollectionViewController {
var currentIndexPath: IndexPath?
var photos: [PhotoModel]?
var selectBtn: UIButton!
var maxSelectCount: Int = 10
weak var previewDelegate: PreviewProtocol?
// 选中照片集合
var selectedAssets: Array<PHAsset> = Array() {
willSet {
sendBtn.isEnabled = newValue.count > 0 ? true : false
countLabel.text = "(\(newValue.count))"
}
}
// 选中数量
var selectedCount: Int {
get {
return selectedAssets.count
}
}
fileprivate let editBtn = UIButton(type: .system)
fileprivate let sendBtn = UIButton(type: .system)
fileprivate let countLabel = UILabel()
override init(collectionViewLayout layout: UICollectionViewLayout) {
super.init(collectionViewLayout: layout)
}
convenience init() {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: screenWidth, height: screenHeight - 64)
layout.minimumLineSpacing = 15.0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
self.init(collectionViewLayout: layout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
printLog(message: "preview deinit")
}
override func viewDidLoad() {
super.viewDidLoad()
customUI()
}
func customUI() {
self.automaticallyAdjustsScrollViewInsets = false
// 右上角选择按钮
selectBtn = UIButton(type: .custom)
selectBtn.frame = CGRect(x: 0, y: 0, width: 22, height: 22)
let btnImage = UIImage(named: photos![currentIndexPath!.row].iselected ? "selected1" : "select")
selectBtn.setImage(btnImage, for: .normal)
selectBtn.addTarget(self, action: #selector(selectImage(btn:)), for: .touchUpInside)
let rightBarItem: UIBarButtonItem = UIBarButtonItem(customView: selectBtn)
self.navigationItem.rightBarButtonItem = rightBarItem
// collectionview的设置
self.collectionView!.register(PreviewCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView?.frame = CGRect(x: 0, y: 64, width: screenWidth + cellMargin, height: screenHeight - 64)
self.collectionView?.isPagingEnabled = true
guard currentIndexPath != nil else {
return
}
// 设置显示时的位置(默认是从[0,0]开始的)
self.collectionView?.scrollToItem(at: currentIndexPath! , at: .left, animated: false)
let tool: UIView = UIView(frame: CGRect(x: 0, y: screenHeight - 49, width: screenWidth, height: 49))
tool.backgroundColor = UIColor.colorFromString("#040b27", alpha: 0.4)
let line: CAShapeLayer = CAShapeLayer()
line.frame = CGRect(x: 0, y: 0, width: screenWidth, height: 0.5)
line.backgroundColor = UIColor.colorFromString("#656d78").cgColor
tool.layer.addSublayer(line)
view.addSubview(tool)
let normalColor = UIColor.colorFromString("#ffffff")
let disableColor = UIColor.colorFromString("#4a485c")
// 编辑按钮
editBtn.frame = CGRect(x: 8, y: 4, width: 40, height: 40)
editBtn.setTitle("edit", for: UIControlState.normal)
editBtn.setTitleColor(normalColor, for: UIControlState.normal)
editBtn.setTitleColor(disableColor, for: UIControlState.disabled)
editBtn.titleLabel?.font = fontMarkerFelt(size: 16)
editBtn.addTarget(self, action: #selector(editAction), for: UIControlEvents.touchUpInside)
tool.addSubview(editBtn)
// 使用按钮
sendBtn.frame = CGRect(x: screenWidth - 50, y: 4, width: 40, height: 40)
sendBtn.setTitle("select", for: UIControlState.normal)
sendBtn.setTitleColor(normalColor, for: UIControlState.normal)
sendBtn.setTitleColor(disableColor, for: UIControlState.disabled)
sendBtn.titleLabel?.font = fontMarkerFelt(size: 16)
sendBtn.addTarget(self, action: #selector(sendAction), for: UIControlEvents.touchUpInside)
tool.addSubview(sendBtn)
// 数量标签:
countLabel.frame = CGRect(x: 0, y: 4, width: 40, height: 40)
countLabel.center = CGPoint(x: tool.center.x, y: countLabel.center.y)
countLabel.font = fontMarkerFelt(size: 18)
countLabel.textColor = UIColor.colorFromString("#fffdf8")
countLabel.textAlignment = .center
countLabel.text = "(\(selectedCount))"
tool.addSubview(countLabel)
}
// 选取图片action
func selectImage(btn: UIButton) {
guard previewDelegate != nil && previewDelegate!.previewChangeSelection?(indexPath: currentIndexPath!) != nil else {
return
}
var currentModel: PhotoModel = photos![currentIndexPath!.row]
if selectedCount == maxSelectCount && currentModel.iselected == false {
let alertView = UIAlertView(title: "不能再多选了,这么多够了!", message: "最多只能选择\(maxSelectCount)张照片", delegate: self, cancelButtonTitle: "确定")
alertView.show()
return
}
currentModel.iselected = !currentModel.iselected
photos![currentIndexPath!.row] = currentModel
if currentModel.iselected == true {
btn.setImage(UIImage(named: "selected1"), for: .normal)
selectedAssets.append(currentModel.asset)
} else {
btn.setImage(UIImage(named: "select"), for: .normal)
for i in 0 ..< selectedAssets.count {
if selectedAssets[i].localIdentifier == currentModel.asset.localIdentifier {
selectedAssets.remove(at: i)
return
}
}
}
}
func editAction() {
printLog(message: "编辑")
}
func sendAction() {
if selectedCount == 0 {
let alertView = UIAlertView(title: "提示", message: "您没有选中任何照片", delegate: self, cancelButtonTitle: "确定")
alertView.show()
} else {
let navi: AlbumViewController = self.navigationController as! AlbumViewController
if navi.albumDelegate != nil {
navi.albumDelegate?.didSelect(photoAssets: selectedAssets)
}
}
self.dismiss(animated: true, completion: nil)
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! PreviewCell
guard let model:PhotoModel = photos?[indexPath.item] else {
cell.filterWithImage(UIImage(named: "default.gif")!,resizeStyle: kCAGravityResizeAspect)
return cell
}
cell.loadData(model: model)
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
printLog(message: "点击了\(indexPath.row)cell")
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == collectionView {
currentIndexPath?.row = Int(ceil(collectionView!.contentOffset.x / (screenWidth + cellMargin)))
let currentModel: PhotoModel = photos![currentIndexPath!.row]
if currentModel.iselected == true {
selectBtn.setImage(UIImage(named: "selected1"), for: .normal)
} else {
selectBtn.setImage(UIImage(named: "select"), for: .normal)
}
let cell: PreviewCell = collectionView?.cellForItem(at: currentIndexPath!) as! PreviewCell
printLog(message: cell.imgView.image)
}
}
}
AlbumListCell
//
// AlbumTableViewCell.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/8.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
import Photos
class AlbumListCell: UITableViewCell {
fileprivate let phmanager: PHCachingImageManager = PHCachingImageManager()
fileprivate var representedAssetIdentifier: String?
lazy var photoView: UIImageView = {
let phView: UIImageView = UIImageView(frame: CGRect(x:10, y:spacing, width:cellWidth, height:cellWidth ))
phView.contentMode = UIViewContentMode.scaleAspectFill
phView.layer.masksToBounds = true
return phView
}()
lazy var nameLabel: UILabel = {
let label: UILabel = UILabel(frame: CGRect(x: cellWidth + 20, y: 20 , width: screenWidth - cellWidth - 20, height: 30))
label.center = CGPoint(x: label.center.x, y: cellWidth / 2 )
label.font = fontMarkerFelt(size: 20)
label.textColor = UIColor.colorFromString("#111111")
return label
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.separatorInset = UIEdgeInsetsMake(3, 5 + cellWidth + 10, 0, 5)
contentView.addSubview(photoView)
contentView.addSubview(nameLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
}
func loadData(model: AlbumModel) {
nameLabel.text = model.albumName + " (\(model.photoesCount))"
guard model.lastPhotoAsset != nil else {
photoView.image = UIImage(named: "default.gif")
return
}
representedAssetIdentifier = model.lastPhotoAsset?.localIdentifier
weak var weakSelf = self
phmanager.requestImage(for: model.lastPhotoAsset!, targetSize: CGSize(width: cellWidth * screenScale ,height: cellWidth * screenScale), contentMode: PHImageContentMode.aspectFill, options: nil) { (image, info) in
guard image != nil else {
weakSelf!.photoView.image = UIImage(named: "default.gif")
return
}
if weakSelf?.representedAssetIdentifier == model.lastPhotoAsset?.localIdentifier {
weakSelf!.photoView.image = image
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
PhotoListCell
//
// CustomCollectionViewCell.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/4.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
import Photos
/// 选择照片协议
protocol SelectPhotoProtocol: NSObjectProtocol {
func selectPhoto(cell: PhotoListCell)
}
class PhotoListCell: UICollectionViewCell {
weak var selectPhotoDelegate: SelectPhotoProtocol?
fileprivate var representedAssetIdentifier: String?
fileprivate let phmanager: PHCachingImageManager = PHCachingImageManager()
lazy var coverView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: cellWidth, height: cellWidth))
view.backgroundColor = UIColor(white: 1.0, alpha: 0.4)
return view
}()
lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: cellWidth , height: cellWidth))
imageView.backgroundColor = UIColor.white
imageView.contentMode = UIViewContentMode.scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
lazy var livePhotoBadgeImageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 22, height: 22))
imageView.backgroundColor = UIColor.clear
imageView.contentMode = UIViewContentMode.scaleAspectFill
return imageView
}()
lazy var selectedImageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: cellWidth - 25 - 2, y: 2, width: 25, height: 25))
imageView.backgroundColor = UIColor.clear
imageView.contentMode = UIViewContentMode.scaleAspectFit
imageView.image = UIImage(named: "select")
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapSelect(tap:)))
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true
return imageView
}()
var selecedImage: Bool = false {
willSet(newValue) {
if newValue == true {
selectedImageView.image = UIImage(named: "selected1")
} else {
selectedImageView.image = UIImage(named: "select")
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .white
contentView.addSubview(imageView)
contentView.addSubview(selectedImageView)
}
func loadData(model: PhotoModel) {
selecedImage = model.iselected
if model.canSelect == false {
addCover()
} else {
removeCover()
}
representedAssetIdentifier = model.asset.localIdentifier
weak var weakSelf = self
phmanager.requestImage(for: model.asset, targetSize: CGSize(width: cellWidth * screenScale, height: cellWidth * screenScale), contentMode: .aspectFill, options: nil) { (image, info) in
guard image != nil else {
return
}
if weakSelf?.representedAssetIdentifier == model.asset.localIdentifier {
weakSelf!.imageView.image = image
}
}
}
func tapSelect(tap: UITapGestureRecognizer) {
if selectPhotoDelegate != nil {
selectPhotoDelegate!.selectPhoto(cell: self)
}
}
func addCover() {
if !contentView.subviews.contains(coverView) {
contentView.addSubview(coverView)
}
}
func removeCover() {
if contentView.subviews.contains(coverView) {
coverView.removeFromSuperview()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
PreviewCell
//
// PreviewCell.swift
// PhotoLibraryDemo
//
// Created by AtronJia on 2017/5/15.
// Copyright © 2017年 Artron. All rights reserved.
//
import UIKit
import Photos
class PreviewCell: UICollectionViewCell, UIScrollViewDelegate {
fileprivate lazy var progressView: UIProgressView = {
let progressView = UIProgressView(frame: CGRect(x:20, y: 100, width: screenWidth - 40, height: 5))
progressView.progress = 0
progressView.tintColor = UIColor.colorFromString("#040b27")
return progressView
}()
fileprivate lazy var scroll: UIScrollView = {
let scroll = UIScrollView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight - 64))
scroll.backgroundColor = .black
scroll.maximumZoomScale = 3.0
scroll.minimumZoomScale = 1.0
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapAction(tap:)))
doubleTap.numberOfTapsRequired = 2
scroll.addGestureRecognizer(doubleTap)
return scroll
}()
lazy var imgView: UIImageView = {
let imgView: UIImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight - 64 - 49))
// imgView.center = self.contentView.center
imgView.backgroundColor = .black
imgView.isUserInteractionEnabled = true
imgView.contentMode = .scaleAspectFit
return imgView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 需要交互时采用imageview添加图片
func addUI() {
self.contentView.addSubview(scroll)
scroll.delegate = self
scroll.addSubview(imgView)
}
// 加载数据
func loadData(model: PhotoModel) {
let phmanager = PHCachingImageManager.default()
let size: CGSize = CGSize(width: screenWidth * screenScale, height: screenHeight * screenScale)
let imageRequestOption = AlbumManager.shared.defaultOptions()
imageRequestOption.progressHandler = { progress, _, stop, _ in
DispatchQueue.main.sync {
if progress < 1{
self.contentView.addSubview(self.progressView)
self.progressView.center = self.contentView.center
self.progressView.progress = Float(progress)
} else {
self.progressView.removeFromSuperview()
}
}
}
phmanager.requestImage(for: model.asset, targetSize: size, contentMode: .aspectFill, options: imageRequestOption) { (image, info) in
guard image != nil else {
return
}
self.imgView.image = image
}
}
// // 加载原图
// func loadOriginalData(model: PhotoModel) {
//
// let phmanager = PHCachingImageManager.default()
// let scale = 5
// phmanager.requestImage(for: model.asset, targetSize: CGSize(width: model.asset.pixelWidth/scale, height: model.asset.pixelHeight/scale), contentMode: .aspectFill, options: nil) { (image, info) in
// guard image != nil else {
// return
// }
// self.imgView.image = image
// }
// }
/**
* 如果只是一张图片做背景,直接设置layer
* @parm: image:UIImage 目标图片
* @parm: resizeStyle:String 图片缩放方式
*/
func filterWithImage(_ image:UIImage, resizeStyle:String) {
// 下面方法据说耗内存太严重,没测试
// self.contentView.backgroundColor = UIColor(patternImage: image)
self.contentView.layer.contents = image.cgImage
self.contentView.layer.masksToBounds = true
self.contentView.layer.contentsGravity = resizeStyle
}
func resetScale() {
if scroll.zoomScale > 1 {
scroll.setZoomScale(1, animated: false)
}
}
//MARKL 双击事件
func doubleTapAction(tap: UITapGestureRecognizer) {
if scroll.zoomScale >= 1.5 {
scroll.setZoomScale(1, animated: true)
} else {
let point = tap.location(in: scroll)
scroll.zoom(to: CGRect(x:point.x - 40,y: point.y - 40, width:80, height:80), animated: true)
}
}
// MARK: scrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgView
}
}
网友评论