// This file is based on third party code, see below for the original author // and original license. // Modifications are (c) by Threema GmbH and licensed under the AGPLv3. // // DKGroupDataManager.swift // DKImagePickerControllerDemo // // Created by ZhangAo on 15/12/16. // Copyright © 2015年 ZhangAo. All rights reserved. // import Photos @objc protocol DKGroupDataManagerObserver { @objc optional func groupDidUpdate(_ groupId: String) @objc optional func groupDidRemove(_ groupId: String) @objc optional func group(_ groupId: String, didRemoveAssets assets: [DKAsset]) @objc optional func group(_ groupId: String, didInsertAssets assets: [DKAsset]) @objc optional func groupDidUpdateComplete(_ groupId: String) @objc optional func groupsDidInsert(_ groupIds: [String]) } public class DKGroupDataManager: DKBaseManager, PHPhotoLibraryChangeObserver { public var groupIds: [String]? private var groups: [String : DKAssetGroup]? private var assets = [String: DKAsset]() public var assetGroupTypes: [PHAssetCollectionSubtype]? public var assetFetchOptions: PHFetchOptions? public var showsEmptyAlbums: Bool = true public var assetFilter: ((_ asset: PHAsset) -> Bool)? deinit { PHPhotoLibrary.shared().unregisterChangeObserver(self) } public func invalidate() { self.groupIds?.removeAll() self.groups?.removeAll() self.assets.removeAll() PHPhotoLibrary.shared().unregisterChangeObserver(self) } public func fetchGroups(_ completeBlock: @escaping (_ groups: [String]?, _ error: NSError?) -> Void) { if let assetGroupTypes = self.assetGroupTypes { DispatchQueue.global(qos: .userInteractive).async { [weak self] in guard let strongSelf = self else { return } guard strongSelf.groups == nil else { DispatchQueue.main.async { completeBlock(strongSelf.groupIds, nil) } return } var groups: [String : DKAssetGroup] = [:] var groupIds: [String] = [] strongSelf.fetchGroups(assetGroupTypes: assetGroupTypes, block: { (collection) in let assetGroup = strongSelf.makeDKAssetGroup(with: collection) if strongSelf.showsEmptyAlbums || assetGroup.totalCount > 0 { groups[assetGroup.groupId] = assetGroup groupIds.append(assetGroup.groupId) } if !groupIds.isEmpty { strongSelf.updatePartial(groups: groups, groupIds: groupIds, completeBlock: completeBlock) } }) PHPhotoLibrary.shared().register(strongSelf) if !groupIds.isEmpty { strongSelf.updatePartial(groups: groups, groupIds: groupIds, completeBlock: completeBlock) } /***** BEGIN THREEMA MODIFICATION: add reload data *********/ let fetchMoments = PHAssetCollection.fetchMoments(with: nil) if (fetchMoments.count > 0) { var assetsCount = 0 fetchMoments.enumerateObjects({ (collection, idx, stop) in assetsCount = assetsCount + collection.estimatedAssetCount }) let momentsAssets = [PHAsset](repeating: PHAsset(), count: assetsCount) let collection = PHAssetCollection.transientAssetCollection(with: momentsAssets, title: NSLocalizedString("gallery_moments", comment: "")) let assetGroup = strongSelf.makeDKAssetGroup(with: collection) assetGroup.momentsResult = fetchMoments if strongSelf.showsEmptyAlbums || assetGroup.totalCount > 0 { groups[assetGroup.groupId] = assetGroup groupIds.insert(assetGroup.groupId, at: 1) } if !groupIds.isEmpty { strongSelf.updatePartial(groups: groups, groupIds: groupIds, completeBlock: completeBlock) } } /***** END THREEMA MODIFICATION: add reload data *********/ } } } public func fetchGroupWithGroupId(_ groupId: String) -> DKAssetGroup { return self.groups![groupId]! } public func fetchGroupThumbnailForGroup(_ groupId: String, size: CGSize, options: PHImageRequestOptions, completeBlock: @escaping (_ image: UIImage?, _ info: [AnyHashable: Any]?) -> Void) { let group = self.fetchGroupWithGroupId(groupId) if group.totalCount == 0 { completeBlock(nil, nil) return } /***** BEGIN THREEMA MODIFICATION: add reload data *********/ let latestAsset: DKAsset! if (group.momentsResult != nil && !group.momentsLoaded) { let result = PHAsset.fetchAssets(in: (group.momentsResult?.lastObject)!, options: nil) latestAsset = DKAsset(originalAsset: result.lastObject!) latestAsset.fetchImageWithSize(size, options: options, completeBlock: completeBlock) } else { latestAsset = DKAsset(originalAsset:group.fetchResult.lastObject!) latestAsset.fetchImageWithSize(size, options: options, completeBlock: completeBlock) } /***** END THREEMA MODIFICATION: add reload data *********/ } public func fetchAsset(_ group: DKAssetGroup, index: Int) -> DKAsset { let originalAsset = self.fetchOriginalAsset(group, index: index) var asset = self.assets[originalAsset.localIdentifier] if asset == nil { asset = DKAsset(originalAsset:originalAsset) self.assets[originalAsset.localIdentifier] = asset } return asset! } public func fetchOriginalAsset(_ group: DKAssetGroup, index: Int) -> PHAsset { return group.fetchResult[group.totalCount - index - 1] } /***** BEGIN THREEMA MODIFICATION: add reload data *********/ public func loadMomentsGroup(_ group: DKAssetGroup, completion: () -> ()) { var momentsAssets = [PHAsset]() group.momentsResult?.enumerateObjects({ (collection, idx, stop) in let fetchResult = PHAsset.fetchAssets(in: collection, options: nil) fetchResult.enumerateObjects({ (assets, jdx, stop2) in momentsAssets.append(assets) }) }) let collection = PHAssetCollection.transientAssetCollection(with: momentsAssets, title: NSLocalizedString("gallery_moments", comment: "")) self.updateGroup(group, collection: collection) self.updateGroup(group, fetchResult: PHAsset.fetchAssets(in: collection, options: self.assetFetchOptions)) completion() } /***** END THREEMA MODIFICATION: add reload data *********/ // MARK: - Private methods private func makeDKAssetGroup(with collection: PHAssetCollection) -> DKAssetGroup { let assetGroup = DKAssetGroup() assetGroup.groupId = collection.localIdentifier self.updateGroup(assetGroup, collection: collection) self.updateGroup(assetGroup, fetchResult: PHAsset.fetchAssets(in: collection, options: self.assetFetchOptions)) return assetGroup } private func collectionTypeForSubtype(_ subtype: PHAssetCollectionSubtype) -> PHAssetCollectionType { return subtype.rawValue < PHAssetCollectionSubtype.smartAlbumGeneric.rawValue ? .album : .smartAlbum } private func fetchGroups(assetGroupTypes: [PHAssetCollectionSubtype], block: @escaping (PHAssetCollection) -> Void) { for (_, groupType) in assetGroupTypes.enumerated() { let fetchResult = PHAssetCollection.fetchAssetCollections(with: self.collectionTypeForSubtype(groupType), subtype: groupType, options: nil) fetchResult.enumerateObjects({ (collection, index, stop) in block(collection) }) } } private func updatePartial(groups: [String : DKAssetGroup], groupIds: [String], completeBlock: @escaping (_ groups: [String]?, _ error: NSError?) -> Void) { self.groups = groups self.groupIds = groupIds DispatchQueue.main.async { completeBlock(groupIds, nil) } } private func updateGroup(_ group: DKAssetGroup, collection: PHAssetCollection) { group.groupName = collection.localizedTitle group.originalCollection = collection } private func updateGroup(_ group: DKAssetGroup, fetchResult: PHFetchResult) { group.fetchResult = filterResults(fetchResult) group.totalCount = group.fetchResult.count } private func filterResults(_ fetchResult: PHFetchResult) -> PHFetchResult { guard let filter = assetFilter else { return fetchResult } var filtered = [PHAsset]() for i in 0.. 0 { self.notifyObserversWithSelector(#selector(DKGroupDataManagerObserver.group(_:didRemoveAssets:)), object: group.groupId as AnyObject?, objectTwo: removedAssets as AnyObject?) } self.updateGroup(group, fetchResult: changeDetails.fetchResultAfterChanges) let insertedAssets = changeDetails.insertedObjects.map{ DKAsset(originalAsset: $0) } if insertedAssets.count > 0 { self.notifyObserversWithSelector(#selector(DKGroupDataManagerObserver.group(_:didInsertAssets:)), object: group.groupId as AnyObject?, objectTwo: insertedAssets as AnyObject?) } self.notifyObserversWithSelector(#selector(DKGroupDataManagerObserver.groupDidUpdateComplete(_:)), object: group.groupId as AnyObject?) } } if let assetGroupTypes = self.assetGroupTypes { var insertedGroupIds: [String] = [] self.fetchGroups(assetGroupTypes: assetGroupTypes, block: { (collection) in if (self.groups![collection.localIdentifier] == nil) { let assetGroup = self.makeDKAssetGroup(with: collection) self.groups![assetGroup.groupId] = assetGroup self.groupIds!.append(assetGroup.groupId) insertedGroupIds.append(assetGroup.groupId) } }) if (insertedGroupIds.count > 0) { self.notifyObserversWithSelector(#selector(DKGroupDataManagerObserver.groupsDidInsert(_:)), object: insertedGroupIds as AnyObject) } } } }