diff --git a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.h b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.h new file mode 100644 index 00000000..9af10ad5 --- /dev/null +++ b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.h @@ -0,0 +1,43 @@ +/** + * Tencent is pleased to support the open source community by making QMUI_iOS available. + * Copyright (C) 2016-2020 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +// +// QMUIAssetFetchResultChange.h +// qmui +// +// Created by QMUI Team on 20/8/29. +// + +#import +#import "QMUIAssetsGroup.h" + +@class PHFetchResultChangeDetails; + +NS_ASSUME_NONNULL_BEGIN + +@interface QMUIAssetFetchResultChange : NSObject + +@property (nonatomic, readonly) BOOL hasIncrementalChanges; + +@property (nonatomic, readonly) BOOL hasMoves; + +@property (nonatomic, readonly) NSArray *removedIndexPaths; + +@property (nonatomic, readonly) NSArray *insertedIndexPaths; + +@property (nonatomic, readonly) NSArray *changedIndexPaths; + + +- (instancetype)initWithChangeDetails:(PHFetchResultChangeDetails *)changeDetails + albumSortType:(QMUIAlbumSortType)albumSortType; + +- (void)enumerateMovesWithBlock:(void(^)(NSIndexPath *fromIndexPath, NSIndexPath *toIndexPath))handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.m b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.m new file mode 100644 index 00000000..f1b200a1 --- /dev/null +++ b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetFetchResultChange.m @@ -0,0 +1,95 @@ +/** + * Tencent is pleased to support the open source community by making QMUI_iOS available. + * Copyright (C) 2016-2020 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +// +// QMUIAssetFetchResultChange.m +// qmui +// +// Created by QMUI Team on 20/8/29. +// + +#import "QMUIAssetFetchResultChange.h" +#import + +@interface QMUIAssetFetchResultChangeMovePair : NSObject + +@property (nonatomic, strong) NSIndexPath *fromIndexPath; + +@property (nonatomic, strong) NSIndexPath *toIndexPath; + +@end + +@implementation QMUIAssetFetchResultChangeMovePair + +@end + +@interface QMUIAssetFetchResultChange () + +@property (nonatomic, strong) NSMutableArray *movePairs; + +@end + +@implementation QMUIAssetFetchResultChange + +- (instancetype)initWithChangeDetails:(PHFetchResultChangeDetails *)changeDetails + albumSortType:(QMUIAlbumSortType)albumSortType { + self = [super init]; + if (self) { + _hasIncrementalChanges = changeDetails.hasIncrementalChanges; + _hasMoves = changeDetails.hasMoves; + const NSUInteger countAfterChanges = changeDetails.fetchResultAfterChanges.count; + _removedIndexPaths = [self indexPathsForIndexSet:changeDetails.removedIndexes + albumSortType:albumSortType + count:changeDetails.fetchResultBeforeChanges.count]; + _insertedIndexPaths = [self indexPathsForIndexSet:changeDetails.insertedIndexes + albumSortType:albumSortType + count:countAfterChanges]; + _changedIndexPaths = [self indexPathsForIndexSet:changeDetails.changedIndexes + albumSortType:albumSortType + count:countAfterChanges]; + NSMutableArray *movePairs = [[NSMutableArray alloc] init]; + [changeDetails enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex) { + QMUIAssetFetchResultChangeMovePair *movePair = [[QMUIAssetFetchResultChangeMovePair alloc] init]; + movePair.fromIndexPath = [NSIndexPath indexPathForItem:[self convertIndex:fromIndex + albumSortType:albumSortType + count:countAfterChanges] + inSection:0]; + movePair.toIndexPath = [NSIndexPath indexPathForItem:[self convertIndex:toIndex + albumSortType:albumSortType + count:countAfterChanges] + inSection:0]; + [movePairs addObject:movePair]; + }]; + _movePairs = movePairs; + } + return self; +} + +- (void)enumerateMovesWithBlock:(void (^)(NSIndexPath * _Nonnull, NSIndexPath * _Nonnull))handler { + +} + +#pragma mark - Private +- (NSArray *)indexPathsForIndexSet:(NSIndexSet *)indexSet + albumSortType:(QMUIAlbumSortType)albumSortType + count:(NSUInteger)count { + NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + [indexPaths addObject:[NSIndexPath indexPathForItem:[self convertIndex:idx + albumSortType:albumSortType + count:count] + inSection:0]]; + }]; + return indexPaths; +} + +- (NSUInteger)convertIndex:(NSUInteger)index albumSortType:(QMUIAlbumSortType)albumSortType count:(NSUInteger)count { + return albumSortType == QMUIAlbumSortTypeReverse ? count - 1 - index : index; +} + +@end diff --git a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h index c13a75b3..7b74172b 100644 --- a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h +++ b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h @@ -47,7 +47,7 @@ typedef NS_ENUM(NSUInteger, QMUIAlbumSortType) { @property(nonatomic, strong, readonly) PHAssetCollection *phAssetCollection; /// 仅能通过 initWithPHCollection 和 initWithPHCollection:fetchAssetsOption 方法修改 phAssetCollection 后,产生一个对应的 PHAssetsFetchResults 保存到 phFetchResult 中 -@property(nonatomic, strong, readonly) PHFetchResult *phFetchResult; +@property(nonatomic, strong) PHFetchResult *phFetchResult; /// 相册的名称 - (NSString *)name; @@ -55,6 +55,8 @@ typedef NS_ENUM(NSUInteger, QMUIAlbumSortType) { /// 相册内的资源数量,包括视频、图片、音频(如果支持)这些类型的所有资源 - (NSInteger)numberOfAssets; +- (NSInteger)convertedIndexForIndex:(NSInteger)index albumSortType:(QMUIAlbumSortType)albumSortType; + /** * 相册的缩略图,即系统接口中的相册海报(Poster Image) * diff --git a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m index 4ff280a9..a01df879 100644 --- a/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m +++ b/QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m @@ -21,7 +21,6 @@ @interface QMUIAssetsGroup() @property(nonatomic, strong, readwrite) PHAssetCollection *phAssetCollection; -@property(nonatomic, strong, readwrite) PHFetchResult *phFetchResult; @end @@ -44,6 +43,14 @@ - (NSInteger)numberOfAssets { return self.phFetchResult.count; } +- (NSInteger)convertedIndexForIndex:(NSInteger)index albumSortType:(QMUIAlbumSortType)albumSortType { + if (albumSortType == QMUIAlbumSortTypeReverse) { + return self.phFetchResult.count - 1 - index; + } else { + return index; + } +} + - (NSString *)name { NSString *resultName = self.phAssetCollection.localizedTitle; return NSLocalizedString(resultName, resultName); diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m index 98ca25a6..1b8944c0 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m @@ -21,6 +21,7 @@ #import "QMUIImagePickerViewController.h" #import "QMUIImagePickerHelper.h" #import "QMUIAppearance.h" +#import "QMUIAssetFetchResultChange.h" #import #import #import @@ -125,7 +126,7 @@ + (void)initAppearance { #pragma mark - QMUIAlbumViewController -@interface QMUIAlbumViewController () +@interface QMUIAlbumViewController () @property(nonatomic, strong) NSMutableArray *albumsArray; @property(nonatomic, strong) QMUIImagePickerViewController *imagePickerViewController; @@ -133,6 +134,10 @@ @interface QMUIAlbumViewController () @implementation QMUIAlbumViewController +- (void)dealloc { + [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; +} + - (void)didInitialize { [super didInitialize]; _shouldShowDefaultLoadingView = YES; @@ -154,6 +159,8 @@ - (void)initTableView { - (void)viewDidLoad { [super viewDidLoad]; + [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; + if ([QMUIAssetsManager authorizationStatus] == QMUIAssetAuthorizationStatusNotAuthorized) { // 如果没有获取访问授权,或者访问授权状态已经被明确禁止,则显示提示语,引导用户开启授权 NSString *tipString = self.tipTextWhenNoPhotosAuthorization; @@ -214,11 +221,11 @@ - (void)refreshAlbumAndShowEmptyTipIfNeed { if (self.shouldShowDefaultLoadingView) { [self hideEmptyView]; } - [self.tableView reloadData]; } else { NSString *tipString = self.tipTextWhenPhotosEmpty ? : @"空照片"; [self showEmptyViewWithText:tipString detailText:nil buttonTitle:nil buttonAction:nil]; } + [self.tableView reloadData]; } - (void)pickAlbumsGroup:(QMUIAssetsGroup *)assetsGroup animated:(BOOL)animated { @@ -277,4 +284,23 @@ - (void)handleCancelSelectAlbum:(id)sender { }]; } +#pragma mark - PHPhotoLibraryChangeObserver + +- (void)photoLibraryDidChange:(PHChange *)changeInstance { + NSMutableArray *albums = [[NSMutableArray alloc] init]; + [[QMUIAssetsManager sharedInstance] enumerateAllAlbumsWithAlbumContentType:self.contentType usingBlock:^(QMUIAssetsGroup *resultAssetsGroup) { + if (resultAssetsGroup) { + [albums addObject:resultAssetsGroup]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + self.albumsArray = albums; + [self sortAlbumArray]; + [self refreshAlbumAndShowEmptyTipIfNeed]; + if (self.imagePickerViewController) { + [self.imagePickerViewController updateCollectionViewWithChangeInstance:changeInstance]; + } + }); + } + }]; +} @end diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h index e3a0d287..a58771a8 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h @@ -16,12 +16,14 @@ #import #import "QMUIImagePreviewViewController.h" #import "QMUIAsset.h" +#import "QMUIAssetsGroup.h" NS_ASSUME_NONNULL_BEGIN @class QMUIButton, QMUINavigationButton; @class QMUIImagePickerViewController; @class QMUIImagePickerPreviewViewController; +@class QMUIAssetFetchResultChange; @protocol QMUIImagePickerPreviewViewControllerDelegate @@ -52,10 +54,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable, nonatomic, strong, readonly) QMUINavigationButton *backButton; @property(nullable, nonatomic, strong, readonly) QMUIButton *checkboxButton; -/// 由于组件需要通过本地图片的 QMUIAsset 对象读取图片的详细信息,因此这里的需要传入的是包含一个或多个 QMUIAsset 对象的数组 -@property(nullable, nonatomic, strong) NSMutableArray *imagesAssetArray; -@property(nullable, nonatomic, strong) NSMutableArray *selectedImageAssetArray; - @property(nonatomic, assign) QMUIAssetDownloadStatus downloadStatus; /// 最多可以选择的图片数,默认为无穷大 @@ -67,6 +65,11 @@ NS_ASSUME_NONNULL_BEGIN /// 选择图片超出最大图片限制时 alertView 的标题 @property(nullable, nonatomic, copy) NSString *alertButtonTitleWhenExceedMaxSelectImageCount; +/// 已选照片,单选模式下为 nil +@property (nonatomic, strong, nullable) NSMutableArray *selectedImageAssetArray; + +/// 照片排序方式 +@property (nonatomic, assign) QMUIAlbumSortType albumSortType; /** * 更新数据并刷新 UI,手工调用 * @@ -75,10 +78,16 @@ NS_ASSUME_NONNULL_BEGIN * @param currentImageIndex 当前展示的图片在 imageAssetArray 的索引 * @param singleCheckMode 是否为单选模式,如果是单选模式,则不显示 checkbox */ -- (void)updateImagePickerPreviewViewWithImagesAssetArray:(NSMutableArray * _Nullable)imageAssetArray - selectedImageAssetArray:(NSMutableArray * _Nullable)selectedImageAssetArray - currentImageIndex:(NSInteger)currentImageIndex - singleCheckMode:(BOOL)singleCheckMode; +- (void)updateImagePickerPreviewViewWithAssetGroup:(QMUIAssetsGroup *)assetGroup + imagesAssets:(NSMutableDictionary *)imageAssets + selectedImageAssetArray:(NSMutableArray * _Nullable)selectedImageAssetArray + currentImageIndex:(NSInteger)currentImageIndex + singleCheckMode:(BOOL)singleCheckMode + onlyPreviewSelectedImageAssets:(BOOL)onlyPreviewSelectedImageAssets; + +- (void)updateCollectionViewWithAssetFetchResultChange:(QMUIAssetFetchResultChange *)assetFetchResultChange; + +- (QMUIAsset *)imageAssetForIndex:(NSInteger)index; @end diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m index e970aa3b..43603f2d 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m @@ -29,6 +29,7 @@ #import "UIView+QMUI.h" #import "QMUILog.h" #import "QMUIAppearance.h" +#import "QMUIAssetFetchResultChange.h" #pragma mark - QMUIImagePickerPreviewViewController (UIAppearance) @@ -52,6 +53,16 @@ + (void)initAppearance { @end +@interface QMUIImagePickerPreviewViewController () + +@property (nonatomic, assign) BOOL onlyPreviewSelectedImageAssets; + +@property (nonatomic, strong) NSMutableDictionary *imageAssets; + +@property (nonatomic, strong, nullable) QMUIAssetsGroup *assetsGroup; + +@end + @implementation QMUIImagePickerPreviewViewController { BOOL _singleCheckMode; } @@ -60,7 +71,7 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibB if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { self.maximumSelectImageCount = INT_MAX; self.minimumSelectImageCount = 0; - + self.imageAssets = [[NSMutableDictionary alloc] init]; [self qmui_applyAppearance]; } return self; @@ -98,7 +109,7 @@ - (void)initSubviews { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (!_singleCheckMode) { - QMUIAsset *imageAsset = self.imagesAssetArray[self.imagePreviewView.currentImageIndex]; + QMUIAsset *imageAsset = [self imageAssetForIndex:self.imagePreviewView.currentImageIndex]; self.checkboxButton.selected = [self.selectedImageAssetArray containsObject:imageAsset]; } @@ -161,11 +172,15 @@ - (void)setDownloadStatus:(QMUIAssetDownloadStatus)downloadStatus { } } -- (void)updateImagePickerPreviewViewWithImagesAssetArray:(NSMutableArray *)imageAssetArray - selectedImageAssetArray:(NSMutableArray *)selectedImageAssetArray - currentImageIndex:(NSInteger)currentImageIndex - singleCheckMode:(BOOL)singleCheckMode { - self.imagesAssetArray = imageAssetArray; +- (void)updateImagePickerPreviewViewWithAssetGroup:(QMUIAssetsGroup *)assetGroup + imagesAssets:(NSMutableDictionary *)imageAssets + selectedImageAssetArray:(NSMutableArray * _Nullable)selectedImageAssetArray + currentImageIndex:(NSInteger)currentImageIndex + singleCheckMode:(BOOL)singleCheckMode + onlyPreviewSelectedImageAssets:(BOOL)onlyPreviewSelectedImageAssets { + self.onlyPreviewSelectedImageAssets = onlyPreviewSelectedImageAssets; + self.assetsGroup = assetGroup; + self.imageAssets = imageAssets; self.selectedImageAssetArray = selectedImageAssetArray; self.imagePreviewView.currentImageIndex = currentImageIndex; _singleCheckMode = singleCheckMode; @@ -174,14 +189,50 @@ - (void)updateImagePickerPreviewViewWithImagesAssetArray:(NSMutableArray - (NSUInteger)numberOfImagesInImagePreviewView:(QMUIImagePreviewView *)imagePreviewView { - return [self.imagesAssetArray count]; + if (_singleCheckMode) { + return 1; + } + if (self.onlyPreviewSelectedImageAssets) { + return self.selectedImageAssetArray.count; + } + return self.assetsGroup.phFetchResult.count; } - (QMUIImagePreviewMediaType)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView assetTypeAtIndex:(NSUInteger)index { - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:index]; + QMUIAsset *imageAsset = [self imageAssetForIndex:index]; if (imageAsset.assetType == QMUIAssetTypeImage) { if (imageAsset.assetSubType == QMUIAssetSubTypeLivePhoto) { return QMUIImagePreviewMediaTypeLivePhoto; @@ -200,7 +251,7 @@ - (void)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView renderZoomImag - (void)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView willScrollHalfToIndex:(NSUInteger)index { if (!_singleCheckMode) { - QMUIAsset *imageAsset = self.imagesAssetArray[index]; + QMUIAsset *imageAsset = [self imageAssetForIndex:index]; self.checkboxButton.selected = [self.selectedImageAssetArray containsObject:imageAsset]; } } @@ -242,7 +293,7 @@ - (void)handleCheckButtonClick:(QMUIButton *)button { } button.selected = NO; - QMUIAsset *imageAsset = self.imagesAssetArray[self.imagePreviewView.currentImageIndex]; + QMUIAsset *imageAsset = [self imageAssetForIndex:self.imagePreviewView.currentImageIndex]; [self.selectedImageAssetArray removeObject:imageAsset]; if ([self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:didUncheckImageAtIndex:)]) { @@ -269,7 +320,7 @@ - (void)handleCheckButtonClick:(QMUIButton *)button { button.selected = YES; [QMUIImagePickerHelper springAnimationOfImageCheckedWithCheckboxButton:button]; - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:self.imagePreviewView.currentImageIndex]; + QMUIAsset *imageAsset = [self imageAssetForIndex:self.imagePreviewView.currentImageIndex]; [self.selectedImageAssetArray addObject:imageAsset]; if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:didCheckImageAtIndex:)]) { @@ -285,7 +336,7 @@ - (void)requestImageForZoomImageView:(QMUIZoomImageView *)zoomImageView withInde // 如果是走 PhotoKit 的逻辑,那么这个 block 会被多次调用,并且第一次调用时返回的图片是一张小图, // 拉取图片的过程中可能会多次返回结果,且图片尺寸越来越大,因此这里调整 contentMode 以防止图片大小跳动 imageView.contentMode = UIViewContentModeScaleAspectFit; - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:index]; + QMUIAsset *imageAsset = [self imageAssetForIndex:index]; // 获取资源图片的预览图,这是一张适合当前设备屏幕大小的图片,最终展示时把图片交给组件控制最终展示出来的大小。 // 系统相册本质上也是这么处理的,因此无论是系统相册,还是这个系列组件,由始至终都没有显示照片原图, // 这也是系统相册能加载这么快的原因。 @@ -412,4 +463,23 @@ - (void)requestImageForZoomImageView:(QMUIZoomImageView *)zoomImageView withInde } } +#pragma mark - Image asset for index + +- (QMUIAsset *)imageAssetForIndex:(NSInteger)index { + if (_singleCheckMode) { + return self.imageAssets.allValues.firstObject; + } + if (self.onlyPreviewSelectedImageAssets) { + QMUIAsset *assets = self.selectedImageAssetArray[index]; + return assets; + } + const NSInteger newIndex = [self.assetsGroup convertedIndexForIndex:index + albumSortType:self.albumSortType]; + if (self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier] == nil) { + self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier] = + [[QMUIAsset alloc] initWithPHAsset:self.assetsGroup.phFetchResult[newIndex]]; + } + return self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier]; +} + @end diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h index ecd35525..b9166b4a 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @class QMUIImagePickerViewController; @class QMUIButton; - + @protocol QMUIImagePickerViewControllerDelegate @optional @@ -111,10 +111,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable, nonatomic, strong, readonly) QMUIButton *sendButton; @property(nullable, nonatomic, strong, readonly) UILabel *imageCountLabel; -/// 也可以直接传入 QMUIAssetsGroup,然后读取其中的 QMUIAsset 并储存到 imagesAssetArray 中,传入后会赋值到 QMUIAssetsGroup,并自动刷新 UI 展示 -- (void)refreshWithAssetsGroup:(QMUIAssetsGroup * _Nullable)assetsGroup; - -@property(nullable, nonatomic, strong, readonly) NSMutableArray *imagesAssetArray; @property(nullable, nonatomic, strong, readonly) QMUIAssetsGroup *assetsGroup; /// 当前被选择的图片对应的 QMUIAsset 对象数组 @@ -141,6 +137,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, assign) BOOL shouldShowDefaultLoadingView; +/// 也可以直接传入 QMUIAssetsGroup,然后读取其中的 QMUIAsset 并储存到 imagesAssetArray 中,传入后会赋值到 QMUIAssetsGroup,并自动刷新 UI 展示 +- (void)refreshWithAssetsGroup:(QMUIAssetsGroup * _Nullable)assetsGroup; + +- (void)updateCollectionViewWithChangeInstance:(PHChange *)changeInstance; + @end diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m index f890ad12..6fa198e9 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m @@ -31,6 +31,7 @@ #import "UIViewController+QMUI.h" #import "QMUILog.h" #import "QMUIAppearance.h" +#import "QMUIAssetFetchResultChange.h" static NSString * const kVideoCellIdentifier = @"video"; static NSString * const kImageOrUnknownCellIdentifier = @"imageorunknown"; @@ -61,10 +62,13 @@ + (void)initAppearance { @interface QMUIImagePickerViewController () -@property(nonatomic, strong) QMUIImagePickerPreviewViewController *imagePickerPreviewViewController; -@property(nonatomic, assign) BOOL isImagesAssetLoaded;// 这个属性的作用描述:https://github.com/Tencent/QMUI_iOS/issues/219 -@property(nonatomic, assign) BOOL hasScrollToInitialPosition; -@property(nonatomic, assign) BOOL canScrollToInitialPosition;// 要等数据加载完才允许滚动 +@property (nonatomic, strong) QMUIImagePickerPreviewViewController *imagePickerPreviewViewController; +@property (nonatomic, assign) BOOL isImagesAssetLoaded;// 这个属性的作用描述:https://github.com/Tencent/QMUI_iOS/issues/219 +@property (nonatomic, assign) BOOL hasScrollToInitialPosition; +@property (nonatomic, assign) BOOL canScrollToInitialPosition;// 要等数据加载完才允许滚动 +@property (nonatomic, strong) NSMutableDictionary *imageAssets; +@property (nonatomic, assign, readonly) QMUIAlbumSortType albumSortType; + @end @implementation QMUIImagePickerViewController @@ -157,20 +161,15 @@ - (void)viewDidLayoutSubviews { - (void)refreshWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup { _assetsGroup = assetsGroup; - if (!self.imagesAssetArray) { - _imagesAssetArray = [[NSMutableArray alloc] init]; + if (!self.imageAssets) { + _imageAssets = [[NSMutableDictionary alloc] init]; _selectedImageAssetArray = [[NSMutableArray alloc] init]; } else { - [self.imagesAssetArray removeAllObjects]; + [self.imageAssets removeAllObjects]; // 这里不用 remove 选中的图片,因为支持跨相簿选图 // [self.selectedImageAssetArray removeAllObjects]; } // 通过 QMUIAssetsGroup 获取该相册所有的图片 QMUIAsset,并且储存到数组中 - QMUIAlbumSortType albumSortType = QMUIAlbumSortTypePositive; - // 从 delegate 中获取相册内容的排序方式,如果没有实现这个 delegate,则使用 QMUIAlbumSortType 的默认值,即最新的内容排在最后面 - if (self.imagePickerViewControllerDelegate && [self.imagePickerViewControllerDelegate respondsToSelector:@selector(albumSortTypeForImagePickerViewController:)]) { - albumSortType = [self.imagePickerViewControllerDelegate albumSortTypeForImagePickerViewController:self]; - } // 遍历相册内的资源较为耗时,交给子线程去处理,因此这里需要显示 Loading if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(imagePickerViewControllerWillStartLoading:)]) { [self.imagePickerViewControllerDelegate imagePickerViewControllerWillStartLoading:self]; @@ -179,12 +178,12 @@ - (void)refreshWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup { [self showEmptyViewWithLoading]; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [assetsGroup enumerateAssetsWithOptions:albumSortType usingBlock:^(QMUIAsset *resultAsset) { + [assetsGroup enumerateAssetsWithOptions:self.albumSortType usingBlock:^(QMUIAsset *resultAsset) { // 这里需要对 UI 进行操作,因此放回主线程处理 dispatch_async(dispatch_get_main_queue(), ^{ if (resultAsset) { self.isImagesAssetLoaded = NO; - [self.imagesAssetArray addObject:resultAsset]; + self.imageAssets[resultAsset.identifier] = resultAsset; } else { // result 为 nil,即遍历相片或视频完毕 self.isImagesAssetLoaded = YES;// 这个属性的作用描述: https://github.com/Tencent/QMUI_iOS/issues/219 @@ -205,11 +204,59 @@ - (void)refreshWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup { }); } +- (void)updateCollectionViewWithChangeInstance:(PHChange *)changeInstance { + PHFetchResultChangeDetails *changeDetails = + [changeInstance changeDetailsForFetchResult:self.assetsGroup.phFetchResult]; + self.assetsGroup.phFetchResult = changeDetails.fetchResultAfterChanges; + QMUIAssetFetchResultChange *fetchResultChange = [[QMUIAssetFetchResultChange alloc] initWithChangeDetails:changeDetails + albumSortType:self.albumSortType]; + if (!fetchResultChange.hasIncrementalChanges) { + [self.collectionView reloadData]; + + } else { + [changeDetails.removedObjects enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [self.imageAssets removeObjectForKey:obj.localIdentifier]; + }]; + [changeDetails.insertedObjects enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + self.imageAssets[obj.localIdentifier] = [[QMUIAsset alloc] initWithPHAsset:obj]; + }]; + [changeDetails.changedObjects enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + self.imageAssets[obj.localIdentifier] = [[QMUIAsset alloc] initWithPHAsset:obj]; + }]; + [self.collectionView performBatchUpdates:^{ + [self.collectionView deleteItemsAtIndexPaths:fetchResultChange.removedIndexPaths]; + [self.collectionView insertItemsAtIndexPaths:fetchResultChange.insertedIndexPaths]; + } completion:^(BOOL finished) { + [self.collectionView reloadItemsAtIndexPaths:fetchResultChange.changedIndexPaths]; + [fetchResultChange enumerateMovesWithBlock:^(NSIndexPath * _Nonnull fromIndexPath, NSIndexPath * _Nonnull toIndexPath) { + [self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; + + }]; + } + NSMutableArray *selectedImageAssetArray = [[NSMutableArray alloc] init]; + [_selectedImageAssetArray enumerateObjectsUsingBlock:^(QMUIAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + PHObjectChangeDetails *objectChangeDetails = [changeInstance changeDetailsForObject:obj.phAsset]; + if (!objectChangeDetails.objectWasDeleted) { + [selectedImageAssetArray addObject:obj]; + } + }]; + _selectedImageAssetArray = selectedImageAssetArray; + [self updateImageCountAndCheckLimited]; + + // 更新 imagePickerPreviewViewController + if (self.imagePickerPreviewViewController != nil) { + self.imagePickerPreviewViewController.selectedImageAssetArray = selectedImageAssetArray; + [self.imagePickerPreviewViewController updateCollectionViewWithAssetFetchResultChange:fetchResultChange]; + } +} + - (void)initPreviewViewControllerIfNeeded { if (!self.imagePickerPreviewViewController) { self.imagePickerPreviewViewController = [self.imagePickerViewControllerDelegate imagePickerPreviewViewControllerForImagePickerViewController:self]; self.imagePickerPreviewViewController.maximumSelectImageCount = self.maximumSelectImageCount; self.imagePickerPreviewViewController.minimumSelectImageCount = self.minimumSelectImageCount; + self.imagePickerPreviewViewController.albumSortType = self.albumSortType; } } @@ -235,7 +282,7 @@ - (void)setMinimumImageWidth:(CGFloat)minimumImageWidth { - (void)scrollToInitialPositionIfNeeded { if (_collectionView.qmui_visible && self.isImagesAssetLoaded && !self.hasScrollToInitialPosition) { - if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(albumSortTypeForImagePickerViewController:)] && [self.imagePickerViewControllerDelegate albumSortTypeForImagePickerViewController:self] == QMUIAlbumSortTypeReverse) { + if (self.albumSortType == QMUIAlbumSortTypeReverse) { [_collectionView qmui_scrollToTop]; } else { [_collectionView qmui_scrollToBottom]; @@ -270,7 +317,7 @@ - (UICollectionView *)collectionView { _collectionView.dataSource = self; _collectionView.showsHorizontalScrollIndicator = NO; _collectionView.alwaysBounceHorizontal = NO; - _collectionView.backgroundColor = UIColorClear; + _collectionView.backgroundColor = TableViewBackgroundColor; [_collectionView registerClass:[QMUIImagePickerCollectionViewCell class] forCellWithReuseIdentifier:kVideoCellIdentifier]; [_collectionView registerClass:[QMUIImagePickerCollectionViewCell class] forCellWithReuseIdentifier:kImageOrUnknownCellIdentifier]; if (@available(iOS 11, *)) { @@ -286,7 +333,7 @@ - (UICollectionView *)collectionView { - (UIView *)operationToolBarView { if (!_operationToolBarView) { _operationToolBarView = [[UIView alloc] init]; - _operationToolBarView.backgroundColor = UIColorWhite; + _operationToolBarView.backgroundColor = UIColorForBackground; _operationToolBarView.qmui_borderPosition = QMUIViewBorderPositionTop; [_operationToolBarView addSubview:self.sendButton]; @@ -356,6 +403,15 @@ - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection { } } +- (QMUIAlbumSortType)albumSortType { + if (self.imagePickerViewControllerDelegate != nil && + [self.imagePickerViewControllerDelegate respondsToSelector:@selector(albumSortTypeForImagePickerViewController:)]) { + return [self.imagePickerViewControllerDelegate albumSortTypeForImagePickerViewController:self]; + } else { + return QMUIAlbumSortTypePositive; + } +} + #pragma mark - - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { @@ -363,7 +419,7 @@ - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return [self.imagesAssetArray count]; + return [self.assetsGroup.phFetchResult count]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { @@ -371,8 +427,7 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:indexPath.item]; - + QMUIAsset *imageAsset = [self imageAssetForIndex:indexPath.item]; NSString *identifier = nil; if (imageAsset.assetType == QMUIAssetTypeVideo) { identifier = kVideoCellIdentifier; @@ -392,7 +447,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - QMUIAsset *imageAsset = self.imagesAssetArray[indexPath.item]; + QMUIAsset *imageAsset = [self imageAssetForIndex:indexPath.item]; if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(imagePickerViewController:didSelectImageWithImagesAsset:afterImagePickerPreviewViewControllerUpdate:)]) { [self.imagePickerViewControllerDelegate imagePickerViewController:self didSelectImageWithImagesAsset:imageAsset afterImagePickerPreviewViewControllerUpdate:self.imagePickerPreviewViewController]; } @@ -400,16 +455,20 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa [self initPreviewViewControllerIfNeeded]; if (!self.allowsMultipleSelection) { // 单选的情况下 - [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithImagesAssetArray:@[imageAsset].mutableCopy - selectedImageAssetArray:nil - currentImageIndex:0 - singleCheckMode:YES]; + [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithAssetGroup:self.assetsGroup + imagesAssets:@{imageAsset.identifier: imageAsset}.mutableCopy + selectedImageAssetArray:nil + currentImageIndex:0 + singleCheckMode:YES + onlyPreviewSelectedImageAssets:NO]; } else { // cell 处于编辑状态,即图片允许多选 - [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithImagesAssetArray:self.imagesAssetArray - selectedImageAssetArray:self.selectedImageAssetArray - currentImageIndex:indexPath.item - singleCheckMode:NO]; + [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithAssetGroup:self.assetsGroup + imagesAssets:self.imageAssets + selectedImageAssetArray:self.selectedImageAssetArray + currentImageIndex:indexPath.item + singleCheckMode:NO + onlyPreviewSelectedImageAssets:NO]; } [self.navigationController pushViewController:self.imagePickerPreviewViewController animated:YES]; } @@ -428,10 +487,12 @@ - (void)handleSendButtonClick:(id)sender { - (void)handlePreviewButtonClick:(id)sender { [self initPreviewViewControllerIfNeeded]; // 手工更新图片预览界面 - [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithImagesAssetArray:[self.selectedImageAssetArray copy] - selectedImageAssetArray:self.selectedImageAssetArray - currentImageIndex:0 - singleCheckMode:NO]; + [self.imagePickerPreviewViewController updateImagePickerPreviewViewWithAssetGroup:self.assetsGroup + imagesAssets:self.imageAssets + selectedImageAssetArray:self.selectedImageAssetArray + currentImageIndex:0 + singleCheckMode:NO + onlyPreviewSelectedImageAssets:YES]; [self.navigationController pushViewController:self.imagePickerPreviewViewController animated:YES]; } @@ -452,7 +513,7 @@ - (void)handleCheckBoxButtonClick:(UIButton *)checkboxButton { } QMUIImagePickerCollectionViewCell *cell = (QMUIImagePickerCollectionViewCell *)[_collectionView cellForItemAtIndexPath:indexPath]; - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:indexPath.item]; + QMUIAsset *imageAsset = [self imageAssetForIndex:indexPath.item]; if (cell.checked) { // 移除选中状态 if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(imagePickerViewController:willUncheckImageAtIndex:)]) { @@ -522,7 +583,7 @@ - (void)updateImageCountAndCheckLimited { - (void)requestImageWithIndexPath:(NSIndexPath *)indexPath { // 发出请求获取大图,如果图片在 iCloud,则会发出网络请求下载图片。这里同时保存请求 id,供取消请求使用 - QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:indexPath.item]; + QMUIAsset *imageAsset = [self imageAssetForIndex:indexPath.item]; QMUIImagePickerCollectionViewCell *cell = (QMUIImagePickerCollectionViewCell *)[_collectionView cellForItemAtIndexPath:indexPath]; imageAsset.requestID = [imageAsset requestOriginImageWithCompletion:^(UIImage *result, NSDictionary *info) { @@ -561,4 +622,16 @@ - (void)requestImageWithIndexPath:(NSIndexPath *)indexPath { }]; } +#pragma mark - Image asset for index + +- (QMUIAsset *)imageAssetForIndex:(NSInteger)index { + const NSInteger newIndex = [self.assetsGroup convertedIndexForIndex:index + albumSortType:self.albumSortType]; + if (self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier] == nil) { + self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier] = + [[QMUIAsset alloc] initWithPHAsset:self.assetsGroup.phFetchResult[newIndex]]; + } + return self.imageAssets[self.assetsGroup.phFetchResult[newIndex].localIdentifier]; +} + @end diff --git a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h index af2317f9..3d69b276 100644 --- a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h +++ b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h @@ -72,12 +72,16 @@ typedef NS_ENUM (NSUInteger, QMUIImagePreviewMediaType) { @property(nonatomic, strong, readonly) UICollectionView *collectionView; @property(nonatomic, strong, readonly) QMUICollectionViewPagingLayout *collectionViewLayout; +/// 每一页里的 loading 的颜色,默认为 UIColorWhite +@property(nonatomic, strong) UIColor *loadingColor; + /// 获取当前正在查看的图片 index,也可强制将图片滚动到指定的 index @property(nonatomic, assign) NSUInteger currentImageIndex; + - (void)setCurrentImageIndex:(NSUInteger)currentImageIndex animated:(BOOL)animated; -/// 每一页里的 loading 的颜色,默认为 UIColorWhite -@property(nonatomic, strong) UIColor *loadingColor; +/// 更新 currentImageIndex ,可用于手动调用,主动更新 currentImageIndex +- (void)updateCurrentImgeIndex; @end diff --git a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m index d210da8f..6d074663 100644 --- a/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m +++ b/QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m @@ -132,6 +132,38 @@ - (void)setCurrentImageIndex:(NSUInteger)currentImageIndex animated:(BOOL)animat } } +- (void)updateCurrentImgeIndex { + CGFloat pageWidth = [self collectionView:self.collectionView layout:self.collectionViewLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]].width; + CGFloat pageHorizontalMargin = self.collectionViewLayout.minimumLineSpacing; + CGFloat contentOffsetX = self.collectionView.contentOffset.x; + CGFloat index = contentOffsetX / (pageWidth + pageHorizontalMargin); + + // 在滑动过临界点的那一次才去调用 delegate,避免过于频繁的调用 + BOOL isFirstDidScroll = self.previousIndexWhenScrolling == 0; + + // fastToRight example : self.previousIndexWhenScrolling 1.49, index = 2.0 + BOOL fastToRight = (floor(index) - floor(self.previousIndexWhenScrolling) >= 1.0) && (floor(index) - self.previousIndexWhenScrolling > 0.5); + BOOL turnPageToRight = fastToRight || betweenOrEqual(self.previousIndexWhenScrolling, floor(index) + 0.5, index); + + // fastToLeft example : self.previousIndexWhenScrolling 2.51, index = 1.99 + BOOL fastToLeft = (floor(self.previousIndexWhenScrolling) - floor(index) >= 1.0) && (self.previousIndexWhenScrolling - ceil(index) > 0.5); + BOOL turnPageToLeft = fastToLeft || betweenOrEqual(index, floor(index) + 0.5, self.previousIndexWhenScrolling); + + if (!isFirstDidScroll && (turnPageToRight || turnPageToLeft)) { + index = round(index); + if (0 <= index && index < [self.collectionView numberOfItemsInSection:0]) { + + // 不调用 setter,避免又走一次 scrollToItem + _currentImageIndex = index; + + if ([self.delegate respondsToSelector:@selector(imagePreviewView:willScrollHalfToIndex:)]) { + [self.delegate imagePreviewView:self willScrollHalfToIndex:index]; + } + } + } + self.previousIndexWhenScrolling = index; +} + - (void)setLoadingColor:(UIColor *)loadingColor { BOOL isLoadingColorChanged = _loadingColor && ![_loadingColor isEqual:loadingColor]; _loadingColor = loadingColor; @@ -212,35 +244,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView { return; } - CGFloat pageWidth = [self collectionView:self.collectionView layout:self.collectionViewLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]].width; - CGFloat pageHorizontalMargin = self.collectionViewLayout.minimumLineSpacing; - CGFloat contentOffsetX = self.collectionView.contentOffset.x; - CGFloat index = contentOffsetX / (pageWidth + pageHorizontalMargin); - - // 在滑动过临界点的那一次才去调用 delegate,避免过于频繁的调用 - BOOL isFirstDidScroll = self.previousIndexWhenScrolling == 0; - - // fastToRight example : self.previousIndexWhenScrolling 1.49, index = 2.0 - BOOL fastToRight = (floor(index) - floor(self.previousIndexWhenScrolling) >= 1.0) && (floor(index) - self.previousIndexWhenScrolling > 0.5); - BOOL turnPageToRight = fastToRight || betweenOrEqual(self.previousIndexWhenScrolling, floor(index) + 0.5, index); - - // fastToLeft example : self.previousIndexWhenScrolling 2.51, index = 1.99 - BOOL fastToLeft = (floor(self.previousIndexWhenScrolling) - floor(index) >= 1.0) && (self.previousIndexWhenScrolling - ceil(index) > 0.5); - BOOL turnPageToLeft = fastToLeft || betweenOrEqual(index, floor(index) + 0.5, self.previousIndexWhenScrolling); - - if (!isFirstDidScroll && (turnPageToRight || turnPageToLeft)) { - index = round(index); - if (0 <= index && index < [self.collectionView numberOfItemsInSection:0]) { - - // 不调用 setter,避免又走一次 scrollToItem - _currentImageIndex = index; - - if ([self.delegate respondsToSelector:@selector(imagePreviewView:willScrollHalfToIndex:)]) { - [self.delegate imagePreviewView:self willScrollHalfToIndex:index]; - } - } - } - self.previousIndexWhenScrolling = index; + [self updateCurrentImgeIndex]; } @end diff --git a/qmui.xcodeproj/project.pbxproj b/qmui.xcodeproj/project.pbxproj index ba495dac..106dacb8 100644 --- a/qmui.xcodeproj/project.pbxproj +++ b/qmui.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 08B399CA22E18A3B000A8A45 /* UITraitCollection+QMUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 08B399C822E18A3B000A8A45 /* UITraitCollection+QMUI.m */; }; 1178D5692198258700AA30E5 /* NSURL+QMUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 1178D5672198258700AA30E5 /* NSURL+QMUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1178D56A2198258700AA30E5 /* NSURL+QMUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 1178D5682198258700AA30E5 /* NSURL+QMUI.m */; }; + 8C63888224F9637D00E6E2B6 /* QMUIAssetFetchResultChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C63888024F9637D00E6E2B6 /* QMUIAssetFetchResultChange.h */; }; + 8C63888324F9637D00E6E2B6 /* QMUIAssetFetchResultChange.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C63888124F9637D00E6E2B6 /* QMUIAssetFetchResultChange.m */; }; AA8860BA2107455C005E4054 /* QMUIWeakObjectContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = AA8860B82107455C005E4054 /* QMUIWeakObjectContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; AA8860BB2107455C005E4054 /* QMUIWeakObjectContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8860B92107455C005E4054 /* QMUIWeakObjectContainer.m */; }; CD046C412018668900092035 /* QMUILogItem.h in Headers */ = {isa = PBXBuildFile; fileRef = CD046C3F2018668900092035 /* QMUILogItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -342,6 +344,8 @@ 1178D5672198258700AA30E5 /* NSURL+QMUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSURL+QMUI.h"; sourceTree = ""; }; 1178D5682198258700AA30E5 /* NSURL+QMUI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+QMUI.m"; sourceTree = ""; }; 6D03A56D1B53895D003BDDE4 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 8C63888024F9637D00E6E2B6 /* QMUIAssetFetchResultChange.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QMUIAssetFetchResultChange.h; sourceTree = ""; }; + 8C63888124F9637D00E6E2B6 /* QMUIAssetFetchResultChange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QMUIAssetFetchResultChange.m; sourceTree = ""; }; AA8860B82107455C005E4054 /* QMUIWeakObjectContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QMUIWeakObjectContainer.h; sourceTree = ""; }; AA8860B92107455C005E4054 /* QMUIWeakObjectContainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QMUIWeakObjectContainer.m; sourceTree = ""; }; CD046C3F2018668900092035 /* QMUILogItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QMUILogItem.h; sourceTree = ""; }; @@ -731,7 +735,9 @@ CD4DA9BF1E8E3B0500836A1A /* QMUIConfigurationTemplate */, CDB8CA2D1DCC870700769DF0 /* QMUIKit */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; }; CD44C1C61956D5970098D0A2 /* Products */ = { isa = PBXGroup; @@ -1095,6 +1101,8 @@ CDC86F441F68D5F9000E8829 /* QMUIAssetsGroup.m */, CDC86F451F68D5F9000E8829 /* QMUIAssetsManager.h */, CDC86F461F68D5F9000E8829 /* QMUIAssetsManager.m */, + 8C63888024F9637D00E6E2B6 /* QMUIAssetFetchResultChange.h */, + 8C63888124F9637D00E6E2B6 /* QMUIAssetFetchResultChange.m */, ); path = AssetLibrary; sourceTree = ""; @@ -1328,6 +1336,7 @@ CD84F31D1E52DBEA00546111 /* UITabBar+QMUI.h in Headers */, FE1FBCAF1E8BA79000C6C01A /* UITextView+QMUI.h in Headers */, CD6631DB1FD929F4004DF7E8 /* QMUITableViewHeaderFooterView.h in Headers */, + 8C63888224F9637D00E6E2B6 /* QMUIAssetFetchResultChange.h in Headers */, CDD12D3C1FBB320E00114EA9 /* NSArray+QMUI.h in Headers */, CDC86FBD1F68D617000E8829 /* QMUIAsset.h in Headers */, CDC86FBE1F68D617000E8829 /* QMUIAssetsGroup.h in Headers */, @@ -1546,6 +1555,7 @@ files = ( CDB8CBC81DCC870800769DF0 /* UINavigationController+QMUI.m in Sources */, CDB8CBDC1DCC870800769DF0 /* UIView+QMUI.m in Sources */, + 8C63888324F9637D00E6E2B6 /* QMUIAssetFetchResultChange.m in Sources */, CDD12D3D1FBB320E00114EA9 /* NSArray+QMUI.m in Sources */, CDE418FC20761A0F002ED021 /* UIBarItem+QMUI.m in Sources */, D0FB669921CBF00F00806600 /* UIInterface+QMUI.m in Sources */,