Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for asynchronous asset loading #70

Merged
merged 6 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ open class AssetsAlbumViewController: UIViewController {
return view
}()

fileprivate lazy var loadingActivityIndicatorView: UIActivityIndicatorView = {

if #available(iOS 13.0, *) {
let indicator = UIActivityIndicatorView(style: .large)
return indicator
} else {
let indicator = UIActivityIndicatorView()
return indicator
}
}()
fileprivate lazy var loadingPlaceholderView: UIView = UIView()

public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Expand All @@ -84,6 +96,8 @@ open class AssetsAlbumViewController: UIViewController {
view.backgroundColor = .ap_background

view.addSubview(collectionView)
view.addSubview(loadingPlaceholderView)
view.addSubview(loadingActivityIndicatorView)
view.setNeedsUpdateConstraints()

AssetsManager.shared.subscribe(subscriber: self)
Expand All @@ -102,10 +116,24 @@ open class AssetsAlbumViewController: UIViewController {
make.edges.equalToSuperview()
}

loadingPlaceholderView.isHidden = true
loadingPlaceholderView.backgroundColor = .white
loadingPlaceholderView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}

loadingActivityIndicatorView.snp.makeConstraints { (make) in
make.center.equalToSuperview()
}

AssetsManager.shared.authorize(completion: { [weak self] isAuthorized in
if isAuthorized {
self?.loadingPlaceholderView.isHidden = false
self?.loadingActivityIndicatorView.startAnimating()
AssetsManager.shared.fetchAlbums { (_) in
self?.collectionView.reloadData()
self?.loadingPlaceholderView.isHidden = true
self?.loadingActivityIndicatorView.stopAnimating()
}
} else {
self?.dismiss(animated: true, completion: nil)
Expand Down
92 changes: 62 additions & 30 deletions AssetsPickerViewController/Classes/Assets/AssetsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ open class AssetsManager: NSObject {
fileprivate(set) open var selectedAlbum: PHAssetCollection?

fileprivate var isFetchedAlbums: Bool = false
fileprivate var resourceLoadingQueue: DispatchQueue = DispatchQueue(label: "com.assetspicker.loader", qos: .userInitiated)

private override init() {
super.init()
Expand Down Expand Up @@ -322,6 +323,25 @@ extension AssetsManager {
return false
}
}

open func selectAsync(album newAlbum: PHAssetCollection, complection: @escaping (Bool) -> Void) {
if let oldAlbumIdentifier = self.selectedAlbum?.localIdentifier, oldAlbumIdentifier == newAlbum.localIdentifier {
logi("Selected same album.")
complection(false)
}
self.selectedAlbum = newAlbum
if let fetchResult = fetchMap[newAlbum.localIdentifier] {
resourceLoadingQueue.async { [weak self] in
let indexSet = IndexSet(0..<fetchResult.count)
self?.assetArray = fetchResult.objects(at: indexSet)
DispatchQueue.main.async {
complection(true)
}
}
} else {
complection(false)
}
}
}

// MARK: - Model Manipulation
Expand Down Expand Up @@ -430,7 +450,7 @@ extension AssetsManager {
// MARK: - Fetch
extension AssetsManager {

open func fetchAlbums(isRefetch: Bool = false, completion: (([[PHAssetCollection]]) -> Void)? = nil) {
open func fetchAlbums(isRefetch: Bool = false, completion: @escaping (([[PHAssetCollection]]) -> Void)) {

if isRefetch {
selectedAlbum = nil
Expand All @@ -442,42 +462,54 @@ extension AssetsManager {
albumMap.removeAll()
}

if !isFetchedAlbums {

let smartAlbumEntry = fetchAlbums(forAlbumType: .smartAlbum)
fetchedAlbumsArray.append(smartAlbumEntry.fetchedAlbums)
sortedAlbumsArray.append(smartAlbumEntry.sortedAlbums)
albumsFetchArray.append(smartAlbumEntry.fetchResult)

let albumEntry = fetchAlbums(forAlbumType: .album)
fetchedAlbumsArray.append(albumEntry.fetchedAlbums)
sortedAlbumsArray.append(albumEntry.sortedAlbums)
albumsFetchArray.append(albumEntry.fetchResult)

if pickerConfig.albumIsShowMomentAlbums {
let momentEntry = fetchAlbums(forAlbumType: .moment)
fetchedAlbumsArray.append(momentEntry.fetchedAlbums)
sortedAlbumsArray.append(momentEntry.sortedAlbums)
albumsFetchArray.append(momentEntry.fetchResult)
resourceLoadingQueue.async { [weak self] in
guard let `self` = self else { return }
if !self.isFetchedAlbums {

let smartAlbumEntry = self.fetchAlbums(forAlbumType: .smartAlbum)
self.fetchedAlbumsArray.append(smartAlbumEntry.fetchedAlbums)
self.sortedAlbumsArray.append(smartAlbumEntry.sortedAlbums)
self.albumsFetchArray.append(smartAlbumEntry.fetchResult)

let albumEntry = self.fetchAlbums(forAlbumType: .album)
self.fetchedAlbumsArray.append(albumEntry.fetchedAlbums)
self.sortedAlbumsArray.append(albumEntry.sortedAlbums)
self.albumsFetchArray.append(albumEntry.fetchResult)

if self.pickerConfig.albumIsShowMomentAlbums {
let momentEntry = self.fetchAlbums(forAlbumType: .moment)
self.fetchedAlbumsArray.append(momentEntry.fetchedAlbums)
self.sortedAlbumsArray.append(momentEntry.sortedAlbums)
self.albumsFetchArray.append(momentEntry.fetchResult)
}
self.isFetchedAlbums = true
}
// notify
DispatchQueue.main.async {
completion(self.sortedAlbumsArray)
}
isFetchedAlbums = true
}
// notify
completion?(sortedAlbumsArray)
}

open func fetchAssets(isRefetch: Bool = false, completion: (([PHAsset]) -> Void)? = nil) {

fetchAlbums(isRefetch: isRefetch)

if isRefetch {
assetArray.removeAll()
}

// set default album
select(album: defaultAlbum ?? cameraRollAlbum)
fetchAlbums(isRefetch: isRefetch, completion: { [weak self] _ in

guard let `self` = self else { return }
if isRefetch {
self.assetArray.removeAll()
}

// set default album
self.selectAsync(album: self.defaultAlbum ?? self.cameraRollAlbum) { [weak self] (result) in
guard let `self` = self else {
completion?([])
return
}
completion?(self.assetArray)
}
})

completion?(assetArray)
}

func fetchAlbums(forAlbumType type: PHAssetCollectionType) -> (fetchedAlbums: [PHAssetCollection], sortedAlbums: [PHAssetCollection], fetchResult: PHFetchResult<PHAssetCollection>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ open class AssetsPhotoViewController: UIViewController {
return view
}()

fileprivate lazy var loadingActivityIndicatorView: UIActivityIndicatorView = {

if #available(iOS 13.0, *) {
let indicator = UIActivityIndicatorView(style: .large)
return indicator
} else {
let indicator = UIActivityIndicatorView()
return indicator
}
}()
fileprivate lazy var loadingPlaceholderView: UIView = UIView()

var selectedAssets: [PHAsset] {
return selectedArray
}
Expand All @@ -111,6 +123,8 @@ open class AssetsPhotoViewController: UIViewController {
view.addSubview(emptyView)
view.addSubview(noPermissionView)
view.setNeedsUpdateConstraints()
view.addSubview(loadingPlaceholderView)
view.addSubview(loadingActivityIndicatorView)
}

override open func viewDidLoad() {
Expand All @@ -119,6 +133,8 @@ open class AssetsPhotoViewController: UIViewController {
setupCommon()
setupBarButtonItems()
setupCollectionView()
setupPlaceholderView()
setupLoadActivityIndicatorView()

updateEmptyView(count: 0)
updateNoPermissionView()
Expand Down Expand Up @@ -270,33 +286,58 @@ extension AssetsPhotoViewController {
}
}

func setupPlaceholderView() {
loadingPlaceholderView.isHidden = true
loadingPlaceholderView.backgroundColor = .white
loadingPlaceholderView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}

func setupLoadActivityIndicatorView() {
loadingActivityIndicatorView.snp.makeConstraints { (make) in
make.center.equalToSuperview()
}
}

func setupAssets() {
loadingPlaceholderView.isHidden = false
loadingActivityIndicatorView.startAnimating()
let manager = AssetsManager.shared
manager.subscribe(subscriber: self)
manager.fetchAlbums()
manager.fetchAssets() { [weak self] photos in

guard let `self` = self else { return }

self.updateEmptyView(count: photos.count)
self.title = self.title(forAlbum: manager.selectedAlbum)

if self.selectedArray.count > 0 {
self.collectionView.performBatchUpdates({ [weak self] in
self?.collectionView.reloadData()
}, completion: { [weak self] (finished) in
guard let `self` = self else { return }
// initialize preselected assets
self.selectedArray.forEach({ [weak self] (asset) in
if let row = photos.firstIndex(of: asset) {
let indexPathToSelect = IndexPath(row: row, section: 0)
self?.collectionView.selectItem(at: indexPathToSelect, animated: false, scrollPosition: UICollectionView.ScrollPosition(rawValue: 0))
}
})
self.updateSelectionCount()
})
manager.fetchAlbums { _ in
manager.fetchAssets() { [weak self] photos in

guard let `self` = self else { return }

self.updateEmptyView(count: photos.count)
self.title = self.title(forAlbum: manager.selectedAlbum)

if self.selectedArray.count > 0 {
self.collectionView.performBatchUpdates({ [weak self] in
self?.collectionView.reloadData()
}, completion: { [weak self] (finished) in
guard let `self` = self else { return }
// initialize preselected assets
self.selectedArray.forEach({ [weak self] (asset) in
if let row = photos.firstIndex(of: asset) {
let indexPathToSelect = IndexPath(row: row, section: 0)
self?.collectionView.selectItem(at: indexPathToSelect, animated: false, scrollPosition: UICollectionView.ScrollPosition(rawValue: 0))
}
})
self.updateSelectionCount()
})
} else {
self.collectionView.reloadData()
let item = self.collectionView(self.collectionView, numberOfItemsInSection: 0) - 1
let lastItemIndex = NSIndexPath(item: item, section: 0)
self.collectionView.scrollToItem(at: lastItemIndex as IndexPath, at: .top, animated: false)
}
self.loadingPlaceholderView.isHidden = true
self.loadingActivityIndicatorView.stopAnimating()
}
}

}

func setupGestureRecognizer() {
Expand Down Expand Up @@ -367,29 +408,37 @@ extension AssetsPhotoViewController {
}

func select(album: PHAssetCollection) {
if AssetsManager.shared.select(album: album) {
// set title with selected count if exists
if selectedArray.count > 0 {
updateNavigationStatus()
} else {
title = title(forAlbum: album)
}
collectionView.reloadData()

for asset in selectedArray {
if let index = AssetsManager.shared.assetArray.firstIndex(of: asset) {
logi("reselecting: \(index)")
collectionView.selectItem(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .init(rawValue: 0))
}
}
if AssetsManager.shared.assetArray.count > 0 {
if pickerConfig.assetsIsScrollToBottom == true {
collectionView.scrollToItem(at: IndexPath(row: AssetsManager.shared.assetArray.count - 1, section: 0), at: .bottom, animated: false)
loadingPlaceholderView.isHidden = false
loadingActivityIndicatorView.startAnimating()
AssetsManager.shared.selectAsync(album: album, complection: { [weak self] (result) in
guard let `self` = self else { return }
if result {

// set title with selected count if exists
if self.selectedArray.count > 0 {
self.updateNavigationStatus()
} else {
collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .bottom, animated: false)
self.title = self.title(forAlbum: album)
}
self.collectionView.reloadData()

for asset in self.selectedArray {
if let index = AssetsManager.shared.assetArray.firstIndex(of: asset) {
logi("reselecting: \(index)")
self.collectionView.selectItem(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .init(rawValue: 0))
}
}
if AssetsManager.shared.assetArray.count > 0 {
if self.pickerConfig.assetsIsScrollToBottom == true {
self.collectionView.scrollToItem(at: IndexPath(row: AssetsManager.shared.assetArray.count - 1, section: 0), at: .bottom, animated: false)
} else {
self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .bottom, animated: false)
}
}
}
}
self.loadingPlaceholderView.isHidden = true
self.loadingActivityIndicatorView.stopAnimating()
})
}

func select(asset: PHAsset, at indexPath: IndexPath) {
Expand Down Expand Up @@ -706,7 +755,9 @@ extension AssetsPhotoViewController: UICollectionViewDataSourcePrefetching {
public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
var assets = [PHAsset]()
for indexPath in indexPaths {
assets.append(AssetsManager.shared.assetArray[indexPath.row])
if AssetsManager.shared.assetArray.count > indexPath.row {
assets.append(AssetsManager.shared.assetArray[indexPath.row])
}
}
AssetsManager.shared.cache(assets: assets, size: pickerConfig.assetCacheSize)
}
Expand Down