版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.10.09 星期五 |
前言
在我們開發(fā)中總有和系統(tǒng)相冊進行交互的時候,包含圖片和視頻的獲取坎穿,存儲展父,修改等操作返劲。這個模塊我們就一起來看下這個相關(guān)的框架
PhotoKit
。感興趣的可以看下面幾篇文章栖茉。
1. PhotoKit框架詳細解析(一) —— 基本概覽(一)
2. PhotoKit框架詳細解析(二) —— 圖像的獲取篮绿、修改、保存衡载、編輯以及撤銷等簡單示例(一)
源碼
1. Swift
首先我們看下工程組織結(jié)構(gòu)
接著看下sb中的內(nèi)容
下面就是源碼了
1. AlbumCollectionViewController.swift
import UIKit
import Photos
class AlbumCollectionViewController: UICollectionViewController {
var sections: [AlbumCollectionSectionType] = [.all, .smartAlbums, .userCollections]
var allPhotos = PHFetchResult<PHAsset>()
var smartAlbums = PHFetchResult<PHAssetCollection>()
var userCollections = PHFetchResult<PHAssetCollection>()
override func viewDidLoad() {
super.viewDidLoad()
getPermissionIfNecessary { granted in
guard granted else { return }
self.fetchAssets()
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
PHPhotoLibrary.shared().register(self)
}
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
@IBSegueAction func makePhotosCollectionViewController(_ coder: NSCoder) -> PhotosCollectionViewController? {
guard
let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first
else { return nil }
let sectionType = sections[selectedIndexPath.section]
let item = selectedIndexPath.item
let assets: PHFetchResult<PHAsset>
let title: String
switch sectionType {
case .all:
assets = allPhotos
title = AlbumCollectionSectionType.all.description
case .smartAlbums, .userCollections:
let album =
sectionType == .smartAlbums ? smartAlbums[item] : userCollections[item]
assets = PHAsset.fetchAssets(in: album, options: nil)
title = album.localizedTitle ?? ""
}
return PhotosCollectionViewController(assets: assets, title: title, coder: coder)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
guard let headerView = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: AlbumCollectionReusableView.reuseIdentifier,
for: indexPath) as? AlbumCollectionReusableView
else {
fatalError("Unable to dequeue AlbumCollectionReusableView")
}
headerView.title.text = sections[indexPath.section].description
return headerView
}
return UICollectionReusableView()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 1
guard let cell =
collectionView.dequeueReusableCell(
withReuseIdentifier: AlbumCollectionViewCell.reuseIdentifier,
for: indexPath) as? AlbumCollectionViewCell
else {
fatalError("Unable to dequeue AlbumCollectionViewCell")
}
// 2
var coverAsset: PHAsset?
let sectionType = sections[indexPath.section]
switch sectionType {
// 3
case .all:
coverAsset = allPhotos.firstObject
cell.update(title: sectionType.description, count: allPhotos.count)
// 4
case .smartAlbums, .userCollections:
let collection = sectionType == .smartAlbums ?
smartAlbums[indexPath.item] :
userCollections[indexPath.item]
let fetchedAssets = PHAsset.fetchAssets(in: collection, options: nil)
coverAsset = fetchedAssets.firstObject
cell.update(title: collection.localizedTitle, count: fetchedAssets.count)
}
// 5
guard let asset = coverAsset else { return cell }
cell.photoView.fetchImageAsset(asset, targetSize: cell.bounds.size) { success in
cell.photoView.isHidden = !success
cell.emptyView.isHidden = success
}
return cell
}
func getPermissionIfNecessary(completionHandler: @escaping (Bool) -> Void) {
// 1
guard PHPhotoLibrary.authorizationStatus() != .authorized else {
completionHandler(true)
return
}
// 2
PHPhotoLibrary.requestAuthorization { status in
completionHandler(status == .authorized ? true : false)
}
}
func fetchAssets() {// 1
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [
NSSortDescriptor(
key: "creationDate",
ascending: false)
]
// 2
allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
// 3
smartAlbums = PHAssetCollection.fetchAssetCollections(
with: .smartAlbum,
subtype: .albumRegular,
options: nil)
// 4
userCollections = PHAssetCollection.fetchAssetCollections(
with: .album,
subtype: .albumRegular,
options: nil)
}
override func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
switch sections[section] {
case .all: return 1
case .smartAlbums: return smartAlbums.count
case .userCollections: return userCollections.count
}
}
}
extension AlbumCollectionViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
DispatchQueue.main.sync {
// 1
if let changeDetails = changeInstance.changeDetails(for: allPhotos) {
allPhotos = changeDetails.fetchResultAfterChanges
}
// 2
if let changeDetails = changeInstance.changeDetails(for: smartAlbums) {
smartAlbums = changeDetails.fetchResultAfterChanges
}
if let changeDetails = changeInstance.changeDetails(for: userCollections) {
userCollections = changeDetails.fetchResultAfterChanges
}
// 4
collectionView.reloadData()
}
}
}
2. PhotosCollectionViewController.swift
import UIKit
import Photos
class PhotosCollectionViewController: UICollectionViewController {
var assets: PHFetchResult<PHAsset>
required init?(coder: NSCoder) {
fatalError("init(coder:) not implemented.")
}
init?(assets: PHFetchResult<PHAsset>, title: String, coder: NSCoder) {
self.assets = assets
super.init(coder: coder)
self.title = title
}
override func viewDidLoad() {
super.viewDidLoad()
PHPhotoLibrary.shared().register(self)
}
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
@IBSegueAction func makePhotoViewController(_ coder: NSCoder) -> PhotoViewController? {
guard let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else { return nil }
return PhotoViewController(asset: assets[selectedIndexPath.item], coder: coder)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return assets.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: PhotoCollectionViewCell.reuseIdentifier,
for: indexPath) as? PhotoCollectionViewCell else {
fatalError("Unable to dequeue PhotoCollectionViewCell")
}
let asset = assets[indexPath.item]
cell.photoView.fetchImageAsset(asset, targetSize: cell.photoView.bounds.size, completionHandler: nil)
return cell
}
}
extension PhotosCollectionViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 1
guard let change = changeInstance.changeDetails(for: assets) else {
return
}
DispatchQueue.main.sync {
// 2
assets = change.fetchResultAfterChanges
collectionView.reloadData()
}
}
}
3. PhotoViewController.swift
import UIKit
import Photos
import PhotosUI
class PhotoViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var toolbar: UIToolbar!
@IBOutlet weak var favoriteButton: UIBarButtonItem!
@IBAction func favoriteTapped(_ sender: Any) { toggleFavorite() }
@IBOutlet weak var saveButton: UIBarButtonItem!
@IBAction func saveTapped(_ sender: Any) { saveImage() }
@IBOutlet weak var undoButton: UIBarButtonItem!
@IBAction func undoTapped(_ sender: Any) { undo() }
@IBAction func applyFilterTapped(_ sender: Any) { applyFilter() }
var asset: PHAsset
var editingOutput: PHContentEditingOutput?
required init?(coder: NSCoder) {
fatalError("init(coder:) not implemented")
}
init?(asset: PHAsset, coder: NSCoder) {
self.asset = asset
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
getPhoto()
updateFavoriteButton()
updateUndoButton()
saveButton.isEnabled = false
PHPhotoLibrary.shared().register(self)
}
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
func updateFavoriteButton() {
if asset.isFavorite {
favoriteButton.image = UIImage(systemName: "heart.fill")
} else {
favoriteButton.image = UIImage(systemName: "heart")
}
}
func updateUndoButton() {
let adjustmentResources = PHAssetResource.assetResources(for: asset)
.filter { $0.type == .adjustmentData }
undoButton.isEnabled = !adjustmentResources.isEmpty
}
func toggleFavorite() {
// 1
let changeHandler: () -> Void = {
let request = PHAssetChangeRequest(for: self.asset)
request.isFavorite = !self.asset.isFavorite
}
// 2
PHPhotoLibrary.shared().performChanges(changeHandler, completionHandler: nil)
}
func applyFilter() {
// 1
asset.requestContentEditingInput(with: nil) { input, _ in
// 2
guard let bundleID = Bundle.main.bundleIdentifier else {
fatalError("Error: unable to get bundle identifier")
}
guard let input = input else {
fatalError("Error: cannot get editing input")
}
guard let filterData = Filter.noir.data else {
fatalError("Error: cannot get filter data")
}
// 3
let adjustmentData = PHAdjustmentData(
formatIdentifier: bundleID,
formatVersion: "1.0",
data: filterData)
// 4
self.editingOutput = PHContentEditingOutput(contentEditingInput: input)
guard let editingOutput = self.editingOutput else { return }
editingOutput.adjustmentData = adjustmentData
// 5
let fitleredImage = self.imageView.image?.applyFilter(.noir)
self.imageView.image = fitleredImage
// 6
let jpegData = fitleredImage?.jpegData(compressionQuality: 1.0)
do {
try jpegData?.write(to: editingOutput.renderedContentURL)
} catch {
print(error.localizedDescription)
}
// 7
DispatchQueue.main.async {
self.saveButton.isEnabled = true
}
}
}
func saveImage() {
// 1
let changeRequest: () -> Void = {
let changeRequest = PHAssetChangeRequest(for: self.asset)
changeRequest.contentEditingOutput = self.editingOutput
}
// 2
let completionHandler: (Bool, Error?) -> Void = { success, error in
guard success else {
print("Error: cannot edit asset: \(String(describing: error))")
return
}
// 3
self.editingOutput = nil
DispatchQueue.main.async {
self.saveButton.isEnabled = false
}
}
// 4
PHPhotoLibrary.shared().performChanges(
changeRequest,
completionHandler: completionHandler)
}
func undo() {
// 1
let changeRequest: () -> Void = {
let request = PHAssetChangeRequest(for: self.asset)
request.revertAssetContentToOriginal()
}
// 2
let completionHandler: (Bool, Error?) -> Void = { success, error in
guard success else {
print("Error: can't revert the asset: \(String(describing: error))")
return
}
DispatchQueue.main.async {
self.undoButton.isEnabled = false
}
}
// 3
PHPhotoLibrary.shared().performChanges(
changeRequest,
completionHandler: completionHandler)
}
func getPhoto() {
imageView.fetchImageAsset(asset, targetSize: view.bounds.size, completionHandler: nil)
}
}
// 1
extension PhotoViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 2
guard
let change = changeInstance.changeDetails(for: asset),
let updatedAsset = change.objectAfterChanges
else { return }
// 3
DispatchQueue.main.sync {
// 4
asset = updatedAsset
imageView.fetchImageAsset(
asset,
targetSize: view.bounds.size
) { [weak self] _ in
guard let self = self else { return }
// 5
self.updateFavoriteButton()
self.updateUndoButton()
}
}
}
}
4. UIImageView+Extension.swift
import UIKit
import Photos
extension UIImageView {
func fetchImageAsset(_ asset: PHAsset?, targetSize size: CGSize, contentMode: PHImageContentMode = .aspectFill, options: PHImageRequestOptions? = nil, completionHandler: ((Bool) -> Void)?) {
// 1
guard let asset = asset else {
completionHandler?(false)
return
}
// 2
let resultHandler: (UIImage?, [AnyHashable: Any]?) -> Void = { image, info in
self.image = image
completionHandler?(true)
}
// 3
PHImageManager.default().requestImage(
for: asset,
targetSize: size,
contentMode: contentMode,
options: options,
resultHandler: resultHandler)
}
}
5. AlbumCollectionReusableView.swift
import UIKit
class AlbumCollectionReusableView: UICollectionReusableView {
static let reuseIdentifier = "headerView"
@IBOutlet weak var title: UILabel!
}
6. AlbumCollectionViewCell.swift
import UIKit
class AlbumCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "albumCell"
@IBOutlet weak var emptyView: UIImageView!
@IBOutlet weak var photoView: UIImageView!
@IBOutlet weak var albumTitle: UILabel!
@IBOutlet weak var albumCount: UILabel!
override func prepareForReuse() {
super.prepareForReuse()
albumTitle.text = "Untitled"
albumCount.text = "0 photos"
photoView.image = nil
photoView.isHidden = true
emptyView.isHidden = false
}
func update(title: String?, count: Int) {
albumTitle.text = title ?? "Untitled"
albumCount.text = "\(count.description) \(count == 1 ? "photo" : "photos")"
}
}
7. AlbumSectionType.swift
enum AlbumCollectionSectionType: Int, CustomStringConvertible {
case all, smartAlbums, userCollections
var description: String {
switch self {
case .all: return "All Photos"
case .smartAlbums: return "Smart Albums"
case .userCollections: return "User Collections"
}
}
}
8. AlbumCollectionViewController+Extensions.swift
import UIKit
extension AlbumCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CollectionViewFlowLayoutType(.album, frame: view.frame).sizeForItem
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return CollectionViewFlowLayoutType(.album, frame: view.frame).sectionInsets
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return CollectionViewFlowLayoutType(.album, frame: view.frame).sectionInsets.left
}
}
9. CollectionViewFlowLayout.swift
import UIKit
struct CollectionViewFlowLayoutType {
enum ViewType { case album, photos }
private var viewType: ViewType = .album
private var viewFrame: CGRect = .zero
var itemsPerRow: CGFloat {
switch viewType {
case .album: return 2
case .photos: return 3
}
}
var sectionInsets: UIEdgeInsets {
switch viewType {
case .album: return UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 8.0)
case .photos: return UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
}
}
var sizeForItem: CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = viewFrame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
init(_ type: ViewType, frame: CGRect) {
viewType = type
viewFrame = frame
}
}
10. Filter.swift
import Foundation
enum Filter: String {
case noir = "CIPhotoEffectNoir"
var data: Data? {
return self.rawValue.data(using: .utf8)
}
}
11. PhotoCollectionViewCell.swift
import UIKit
class PhotoCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "photoCell"
@IBOutlet weak var photoView: UIImageView!
@IBOutlet weak var livePhotoIndicator: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
photoView.image = nil
livePhotoIndicator.isHidden = true
}
}
12. PhotosCollectionViewController+Extensions.swift
import UIKit
extension PhotosCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CollectionViewFlowLayoutType(.photos, frame: view.frame).sizeForItem
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return CollectionViewFlowLayoutType(.photos, frame: view.frame).sectionInsets
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return CollectionViewFlowLayoutType(.photos, frame: view.frame).sectionInsets.left
}
}
13. UIImage+Extensions.swift
import UIKit
extension UIImage {
func applyFilter(_ filter: Filter) -> UIImage? {
let filter = CIFilter(name: filter.rawValue)
let inputImage = CIImage(image: self)
filter?.setValue(inputImage, forKey: "inputImage")
guard let finalImage = filter?.outputImage else { return nil }
guard let cgImage = CIContext().createCGImage(finalImage, from: finalImage.extent) else { return nil }
return UIImage(cgImage: cgImage)
}
}
后記
本篇主要講述了圖像的獲取搔耕、修改、保存痰娱、編輯以及撤銷等簡單示例弃榨,感興趣的給個贊或者關(guān)注~~~