├── .gitignore ├── .swift-version ├── LICENSE ├── README.md ├── XYPhotoKit.podspec ├── XYPhotoKit ├── DataSources │ ├── XYPhotoCollectionDataSource.h │ ├── XYPhotoCollectionDataSource.m │ ├── XYPhotoTableDataSource.h │ └── XYPhotoTableDataSource.m ├── ViewControllers │ ├── XYPhotoAlbumDetailController.h │ ├── XYPhotoAlbumDetailController.m │ ├── XYPhotoAlbumListController.h │ ├── XYPhotoAlbumListController.m │ ├── XYPhotoCameraController.h │ ├── XYPhotoCameraController.m │ ├── XYPhotoMultiCameraPicker.h │ ├── XYPhotoMultiCameraPicker.m │ ├── XYPhotoMultiImagePicker.h │ ├── XYPhotoMultiImagePicker.m │ ├── XYPhotoPreviewViewController.h │ └── XYPhotoPreviewViewController.m ├── Views │ ├── XYPhotoBottomPreviewCell.h │ ├── XYPhotoBottomPreviewCell.m │ ├── XYPhotoCameraOverlayView.h │ ├── XYPhotoCameraOverlayView.m │ ├── XYPhotoCollectionViewCell.h │ ├── XYPhotoCollectionViewCell.m │ ├── XYPhotoHorizontalScrollItemView.h │ ├── XYPhotoHorizontalScrollItemView.m │ ├── XYPhotoHorizontalScrollView.h │ ├── XYPhotoHorizontalScrollView.m │ ├── XYPhotoPreviewOverlayView.h │ ├── XYPhotoPreviewOverlayView.m │ ├── XYPhotoSelectedAssetPreviewView.h │ ├── XYPhotoSelectedAssetPreviewView.m │ ├── XYPhotoTableCell.h │ └── XYPhotoTableCell.m ├── XYPhotoCollectionFlowLayout.h ├── XYPhotoCollectionFlowLayout.m ├── XYPhotoDataCategory.h ├── XYPhotoDataCategory.m ├── XYPhotoKit.bundle │ └── image │ │ ├── cl_photo_picker_camera_devicemode@2x.png │ │ ├── cl_photo_picker_camera_flash@2x.png │ │ ├── cl_photo_picker_inaccessible@2x.png │ │ ├── cl_photo_picker_picked@2x.png │ │ ├── cl_photo_picker_shutter@2x.png │ │ ├── cl_photo_picker_shutter_h@2x.png │ │ ├── cl_photo_picker_unpicked@2x.png │ │ ├── cl_photokit_placeholder@2x.png │ │ ├── cl_photokit_play@2x.png │ │ └── cl_photokit_play@3x.png ├── XYPhotoKitHelper.h ├── XYPhotoKitHelper.m ├── XYPhotoSelectedAssetManager.h └── XYPhotoSelectedAssetManager.m └── XYPhotoKitDemo ├── XYPhotoKitDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── XYPhotoKitDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── add.imageset │ ├── Contents.json │ └── add@2x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── RootViewController.h ├── RootViewController.m ├── XYDemoCollectionViewCell.h ├── XYDemoCollectionViewCell.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zhiping Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XYPhotoKit 2 | 3 | 基于PhotoKit开发的照片获取方式,支持多选单选(对应的UI略有不同),支持iCloud获取相册等 4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | ## 使用方式 19 | 20 | 详情见`demo`查看 21 | 22 | ### 相册获取方式 23 | 24 | | 属性 | 说明 | 25 | | --- | --- | 26 | | **Controller** | | 27 | | XYPhotoMultiImagePicker | NavigationController,承载两个VC栈、相册相关参数初始化工作及 delegate & datasource | 28 | | XYPhotoAlbumListController | 系统和用户的相册列表(tableview),根据CLPhotoMultiPickerStartPosition来确定初始展示哪个VC界面 | 29 | | XYPhotoAlbumDetailController | 某个相册的详情(collectionView),单选多选照片的不同交互主要完成于此,可进入大图预览 | 30 | | XYPhotoPreviewViewController | 大图预览(XYHorizontalScrollView)| 31 | 32 | ### 用法 33 | 34 | ```objc 35 | 36 | XYPhotoMultiImagePicker *multiImagePicker = [[XYPhotoMultiImagePicker alloc] init]; 37 | multiImagePicker.pickerDelegate = self; 38 | multiImagePicker.pickerDataSource = self; 39 | multiImagePicker.mediaType = PHAssetMediaTypeImage; // 支持图片和视屏 40 | multiImagePicker.maxNumberOfAssets = 9; // 最多选择九张照片 41 | multiImagePicker.assetCollectionViewColumnCount = 4; // 获取 42 | multiImagePicker.startPosition = CLPhotoMultiPickerStartPositionAlbums; // 相册列表开始进入 43 | multiImagePicker.allowNetRequestIfiCloud = YES; // 支持iCloud网络获取 44 | [self presentViewController:multiImagePicker animated:YES completion:nil]; 45 | 46 | 47 | #pragma mark - XYPhotoMultiImagePickerDelegate, XYPhotoMultiImagePickerDataSource 48 | 49 | - (void)multiImagePicker:(XYPhotoMultiImagePicker *)multiImagePicker selectedAssets:(NSArray *)assets 50 | { 51 | _selectedAssets = assets; 52 | 53 | NSLog(@"%@", @(assets.count)); 54 | } 55 | 56 | - (NSArray *)multiImagePickerLoadPresetSelectedAssets 57 | { 58 | return _selectedAssets; 59 | } 60 | 61 | ``` 62 | 63 | ### 相机获取方式 64 | 65 | | 属性 | 说明 | 66 | | --- | --- | 67 | | **Controller** | | 68 | | XYPhotoMultiCameraPicker | NavigationController,承载XYPhotoCameraController、相机相关参数初始化工作及 delegate & datasource | 69 | | XYPhotoCameraController | 基于ImagePickerController,需要有相机和相册两个权限做交互,可进入大图预览 | 70 | | XYPhotoPreviewViewController | 大图预览(XYHorizontalScrollView)| 71 | 72 | ### 用法 73 | 74 | ```objc 75 | 76 | XYPhotoMultiCameraPicker *camera = [[XYPhotoMultiCameraPicker alloc] init]; 77 | camera.pickerDelegate = self; 78 | camera.pickerDataSource = self; 79 | camera.maxNumberOfAssets = 9; // 最多选择九张照片 80 | camera.allowNetRequestIfiCloud = YES; // 支持iCloud网络获取 81 | [self presentViewController:camera animated:YES completion:nil]; 82 | 83 | #pragma mark - XYPhotoMultiCameraPickerDelegate, XYPhotoMultiCameraPickerDataSource 84 | 85 | - (void)multiCameraPicker:(XYPhotoMultiCameraPicker *)multiImagePicker selectedAssets:(NSArray *)assets 86 | { 87 | _selectedAssets = assets.copy; 88 | NSLog(@"%@",assets); 89 | } 90 | 91 | - (NSArray *)multiCameraPickerLoadPresetSelectedAssets 92 | { 93 | return _selectedAssets; 94 | } 95 | 96 | ``` 97 | 98 | ## 综述 99 | 100 | 以上获取都是PHAsset对象,PHAsset 非常灵活,详见[文档](https://developer.apple.com/documentation/photos/phasset),同步异步请求、支持网络、请求progress等等可以查看 `PHImageRequestOptions` 101 | 102 | > 例如:支持网络获取(如果是本地就直接本地获取)asset对象大小为`targetSize`的图片 103 | > 闭包中可能会有多次回调(本地图片)越来越高清,当网络请求时非特殊情况下不可直接获取原图尺寸`CGSizeMake(asset.pixelWidth, asset.pixelHeight)` 104 | 105 | ```objc 106 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 107 | options.networkAccessAllowed = YES; 108 | 109 | _imageRequestId = [self.imageManager requestImageForAsset:asset 110 | targetSize:_targetSize 111 | contentMode:PHImageContentModeAspectFit 112 | options:options 113 | resultHandler:^(UIImage *result, NSDictionary *info) { 114 | BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] 115 | && ![info objectForKey:PHImageErrorKey] 116 | && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]; 117 | if (downloadFinined) { self.imageRequestId = 0; } 118 | if (result) { self.imageView.image = result; } 119 | }]; 120 | ``` 121 | 122 | 123 | ## todo & upgrade 124 | 125 | - [x] 视频播放 126 | - [ ] 拆分类的层级(比如接入LocalImagePicker等等) 127 | - [ ] 完善其他获取照片的拓展方法 & 工具(视频压缩、图片压缩,指定相册图片写入等等) 128 | - [ ] 添加iCloud加载的请求等待提示 129 | - [ ] 大图预览添加过渡动画,视频播放、消失添加fade 130 | - [ ] 大图预览改换使用pageController 131 | - [x] 持久化&有效性验证:对PHAsset持久化存储只需储存 `photoAsset.localIdentifier`的唯一标识,验证对象是否还存在于相册中(即有效性)需要对唯一标识做遍历对比 132 | -------------------------------------------------------------------------------- /XYPhotoKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'XYPhotoKit' 3 | s.summary = '照片选择库,提供拍照和相册两种方式,支持单选多选,数据层是PhotoKit' 4 | s.version = '0.0.1' 5 | s.homepage = "https://github.com/ZhipingYang/XYPhotoKit" 6 | s.license = { :type => 'CHELUN', :file => 'LICENSE' } 7 | s.authors = { 'XcodeYang' => 'yangzhiping@chelun.com' } 8 | s.platform = :ios, '8.0' 9 | s.ios.deployment_target = '8.0' 10 | s.source = { :git => 'https://github.com/ZhipingYang/XYPhotoKit.git', :tag => s.version.to_s } 11 | 12 | s.requires_arc = true 13 | 14 | s.source_files = [ 15 | "XYPhotoKit/**/*.{h,m}", 16 | ] 17 | s.public_header_files = [ 18 | "XYPhotoKit/**/*.h", 19 | ] 20 | s.resources = [ 21 | "XYPhotoKit/resource/*.bundle" 22 | ] 23 | 24 | s.frameworks = 'UIKit', 'Photos', 'AVFoundation', 'AssetsLibrary', 'AVKit' 25 | 26 | end 27 | -------------------------------------------------------------------------------- /XYPhotoKit/DataSources/XYPhotoCollectionDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionDataSource.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | 10 | 11 | @import Foundation; 12 | @import UIKit; 13 | @import Photos; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @class XYPhotoCollectionDataSource; 18 | @protocol XYPhotoCollectionDataSourceDelegate 19 | 20 | - (void)assetCollectionDataSource:(XYPhotoCollectionDataSource *)dataSource selectedIndex:(NSInteger)selectedIndex inFetchResult:(PHFetchResult *)fetchResult; 21 | 22 | @end 23 | 24 | 25 | @interface XYPhotoCollectionDataSource : NSObject 26 | 27 | @property (nullable, nonatomic, weak) id delegate; 28 | @property (nullable, nonatomic, copy) NSArray *assets; 29 | @property (nonatomic) BOOL shouldCache; 30 | 31 | - (instancetype)init __attribute__((unavailable("此方法已弃用,请使用initWithCollectionView:fetchResult方法"))); 32 | + (instancetype)new __attribute__((unavailable("此方法已弃用,请使用initWithCollectionView:fetchResult方法"))); 33 | - (instancetype)initWithCollectionView:(UICollectionView *)collectionView fetchResult:(PHFetchResult *)fetchResult; 34 | 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /XYPhotoKit/DataSources/XYPhotoCollectionDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionDataSource.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoCollectionDataSource.h" 10 | 11 | #import "XYPhotoDataCategory.h" 12 | #import "XYPhotoCollectionViewCell.h" 13 | #import "XYPhotoCollectionFlowLayout.h" 14 | #import "XYPhotoSelectedAssetManager.h" 15 | #import "XYPhotoKitHelper.h" 16 | 17 | @interface XYPhotoCollectionDataSource () 18 | 19 | @property (nonatomic, weak) UICollectionView *collectionView; 20 | @property (nonatomic, copy) PHFetchResult *results; 21 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 22 | @property (nonatomic) CGRect previousPreheatRect; 23 | 24 | @end 25 | 26 | @implementation XYPhotoCollectionDataSource 27 | 28 | static NSString *const XYPhotoCollectionDataSourceCellReuseIdentifier = @"XYPhotoCollectionDataSourceCellReuseIdentifier"; 29 | 30 | - (instancetype)initWithCollectionView:(UICollectionView *)collectionView fetchResult:(PHFetchResult *)fetchResult 31 | { 32 | if (self = [super init]) { 33 | _collectionView = collectionView; 34 | _collectionView.dataSource = self; 35 | _collectionView.delegate = self; 36 | [_collectionView registerClass:[XYPhotoCollectionViewCell class] forCellWithReuseIdentifier:XYPhotoCollectionDataSourceCellReuseIdentifier]; 37 | 38 | _results = fetchResult.copy; 39 | 40 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 41 | if (status == PHAuthorizationStatusAuthorized) { 42 | dispatch_async(dispatch_get_main_queue(), ^{ 43 | self.imageManager = [[PHCachingImageManager alloc] init]; 44 | [self resetCachedAssets]; 45 | [self.collectionView reloadData]; 46 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedAssetsChanged) name:XYPhotoMultiImagePickerNotifications.assetsChanged object:nil]; 48 | }); 49 | } else { 50 | [UIAlertController xy_showAlertPhotoSettingIfUnauthorized]; 51 | } 52 | }]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 60 | } 61 | 62 | - (NSArray *)assets 63 | { 64 | if (!_results) { return nil; } 65 | NSMutableArray *array = @[].mutableCopy; 66 | [_results enumerateObjectsUsingBlock:^(PHAsset *obj, NSUInteger idx, BOOL * _Nonnull stop) { 67 | [array addObject:obj]; 68 | }]; 69 | return array; 70 | } 71 | 72 | #pragma mark - Asset Caching 73 | 74 | - (void)resetCachedAssets 75 | { 76 | [self.imageManager stopCachingImagesForAllAssets]; 77 | self.previousPreheatRect = CGRectZero; 78 | } 79 | 80 | - (void)updateCachedAssets 81 | { 82 | if (!self.shouldCache) { 83 | return; 84 | } 85 | 86 | CGRect preheatRect = self.collectionView.bounds; 87 | preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect)); 88 | 89 | CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect)); 90 | if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) { 91 | 92 | NSMutableArray *addedIndexPaths = [NSMutableArray array]; 93 | NSMutableArray *removedIndexPaths = [NSMutableArray array]; 94 | 95 | [self computeDifferenceBetweenRect:self.previousPreheatRect 96 | andRect:preheatRect 97 | removedHandler:^(CGRect removedRect) { 98 | NSArray *indexPaths = [self.collectionView xy_indexPathsForElementsInRect:removedRect]; 99 | [removedIndexPaths addObjectsFromArray:indexPaths]; 100 | } addedHandler:^(CGRect addedRect) { 101 | NSArray *indexPaths = [self.collectionView xy_indexPathsForElementsInRect:addedRect]; 102 | [addedIndexPaths addObjectsFromArray:indexPaths]; 103 | }]; 104 | 105 | NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths]; 106 | NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths]; 107 | 108 | CGSize itemSize = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout itemSize]; 109 | [self.imageManager startCachingImagesForAssets:assetsToStartCaching 110 | targetSize:CGSizeScale(itemSize, [UIScreen mainScreen].scale) 111 | contentMode:PHImageContentModeAspectFill 112 | options:nil]; 113 | [self.imageManager stopCachingImagesForAssets:assetsToStopCaching 114 | targetSize:CGSizeScale(itemSize, [UIScreen mainScreen].scale) 115 | contentMode:PHImageContentModeAspectFill 116 | options:nil]; 117 | 118 | self.previousPreheatRect = preheatRect; 119 | } 120 | } 121 | 122 | - (void)computeDifferenceBetweenRect:(CGRect)oldRect 123 | andRect:(CGRect)newRect 124 | removedHandler:(void (^)(CGRect removedRect))removedHandler 125 | addedHandler:(void (^)(CGRect addedRect))addedHandler 126 | { 127 | if (CGRectIntersectsRect(newRect, oldRect)) { 128 | // 如果像个区域有交集 129 | CGFloat oldMaxY = CGRectGetMaxY(oldRect); 130 | CGFloat oldMinY = CGRectGetMinY(oldRect); 131 | CGFloat newMaxY = CGRectGetMaxY(newRect); 132 | CGFloat newMinY = CGRectGetMinY(newRect); 133 | 134 | if (newMaxY > oldMaxY) { 135 | CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY)); 136 | addedHandler(rectToAdd); 137 | } 138 | if (oldMinY > newMinY) { 139 | CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY)); 140 | addedHandler(rectToAdd); 141 | } 142 | if (newMaxY < oldMaxY) { 143 | CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY)); 144 | removedHandler(rectToRemove); 145 | } 146 | if (oldMinY < newMinY) { 147 | CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY)); 148 | removedHandler(rectToRemove); 149 | } 150 | } else { 151 | // 没有交集 152 | addedHandler(newRect); 153 | removedHandler(oldRect); 154 | } 155 | } 156 | 157 | - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths 158 | { 159 | if (indexPaths.count == 0) { 160 | return nil; 161 | } 162 | 163 | NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; 164 | for (NSIndexPath *indexPath in indexPaths) { 165 | PHAsset *asset = self.results[indexPath.item]; 166 | [assets addObject:asset]; 167 | } 168 | return assets; 169 | } 170 | 171 | #pragma mark - PHPhotoLibraryChangeObserver 172 | 173 | - (void)photoLibraryDidChange:(PHChange *)changeInstance 174 | { 175 | // 后台线程更新检查 176 | dispatch_async(dispatch_get_main_queue(), ^{ 177 | 178 | // assets 被修改了 (增、删、改) 179 | PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.results]; 180 | if (collectionChanges) { 181 | 182 | self.results = [collectionChanges fetchResultAfterChanges]; 183 | 184 | // 未修改、或位置移动 185 | if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) { 186 | [self.collectionView reloadData]; 187 | } else { 188 | // 照片被修改了 insert/delete/update 189 | [self.collectionView performBatchUpdates:^{ 190 | NSIndexSet *removedIndexes = [collectionChanges removedIndexes]; 191 | if (removedIndexes.count) { 192 | [self.collectionView deleteItemsAtIndexPaths:[removedIndexes xy_indexPathsFromIndexesWithSection:0]]; 193 | } 194 | NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes]; 195 | if (insertedIndexes.count) { 196 | [self.collectionView insertItemsAtIndexPaths:[insertedIndexes xy_indexPathsFromIndexesWithSection:0]]; 197 | } 198 | NSIndexSet *changedIndexes = [collectionChanges changedIndexes]; 199 | if (changedIndexes.count) { 200 | [self.collectionView reloadItemsAtIndexPaths:[changedIndexes xy_indexPathsFromIndexesWithSection:0]]; 201 | } 202 | } completion:NULL]; 203 | } 204 | 205 | [self resetCachedAssets]; 206 | } 207 | }); 208 | } 209 | 210 | #pragma mark - UIScrollViewDelegate 211 | 212 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 213 | { 214 | [self updateCachedAssets]; 215 | } 216 | 217 | #pragma mark - UICollectionViewDataSource 218 | 219 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 220 | { 221 | return self.results.count; 222 | } 223 | 224 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 225 | { 226 | XYPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:XYPhotoCollectionDataSourceCellReuseIdentifier forIndexPath:indexPath]; 227 | PHAsset *asset = self.results[indexPath.row]; 228 | [cell setAsset:asset atIndexPath:indexPath inCollectionView:self.collectionView withCacheManager:_imageManager]; 229 | return cell; 230 | } 231 | 232 | #pragma mark - UICollectionViewDelegate 233 | 234 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 235 | { 236 | [collectionView deselectItemAtIndexPath:indexPath animated:YES]; 237 | 238 | // 单一选择时,无需查看大图 239 | BOOL isSingleSelected = [XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets == 1; 240 | if (isSingleSelected && [[XYPhotoSelectedAssetManager sharedManager].delegate respondsToSelector:@selector(doneSelectingAssets)]) { 241 | [[XYPhotoSelectedAssetManager sharedManager] resetSelectedAsset:@[_results[indexPath.row]]]; 242 | [[XYPhotoSelectedAssetManager sharedManager].delegate doneSelectingAssets]; 243 | } else if ([self.delegate respondsToSelector:@selector(assetCollectionDataSource:selectedIndex:inFetchResult:)]) { 244 | [self.delegate assetCollectionDataSource:self selectedIndex:indexPath.row inFetchResult:self.results]; 245 | } 246 | } 247 | 248 | #pragma mark - Notification 249 | 250 | - (void)selectedAssetsChanged 251 | { 252 | if (!self.collectionView.window) { 253 | [self.collectionView reloadData]; 254 | } 255 | } 256 | 257 | @end 258 | -------------------------------------------------------------------------------- /XYPhotoKit/DataSources/XYPhotoTableDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoTableDataSource.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | @import UIKit; 11 | @import Photos; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class XYPhotoTableDataSource; 16 | 17 | @protocol XYPhotoTableDataSourceDelegate 18 | 19 | - (void)assetTableDataSource:(XYPhotoTableDataSource *)dataSource selectedAssetCollection:(PHAssetCollection *)assetCollection; 20 | 21 | @end 22 | 23 | 24 | @interface XYPhotoTableDataSource : NSObject 25 | 26 | @property (nullable, nonatomic, weak) id delegate; 27 | 28 | - (instancetype)initWithTableView:(UITableView *)tableView; 29 | - (instancetype)init __attribute__((unavailable("此方法已弃用,请使用initWithCollectionView:fetchResult方法"))); 30 | + (instancetype)new __attribute__((unavailable("此方法已弃用,请使用initWithCollectionView:fetchResult方法"))); 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /XYPhotoKit/DataSources/XYPhotoTableDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoTableDataSource.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoTableDataSource.h" 10 | 11 | #import "XYPhotoTableCell.h" 12 | #import "XYPhotoSelectedAssetManager.h" 13 | #import "XYPhotoDataCategory.h" 14 | 15 | @interface XYPhotoTableDataSource () 16 | 17 | @property (nonatomic) UITableView *tableView; 18 | @property (nonatomic) NSArray *collectionFetchResults; 19 | @property (nonatomic) NSArray *> *collectionArrays; 20 | 21 | @end 22 | 23 | @implementation XYPhotoTableDataSource 24 | 25 | NS_ENUM(NSInteger, CLPhotoAlbumDataSourceType) { 26 | CLPhotoAlbumDataSourceTypeAlbums = 0, 27 | CLPhotoAlbumDataSourceTypeTopLevelUserCollections = 1, 28 | 29 | CLPhotoAlbumDataSourceTypeCount = 2 30 | }; 31 | 32 | - (void)dealloc 33 | { 34 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 35 | } 36 | 37 | - (instancetype)initWithTableView:(UITableView *)tableView 38 | { 39 | if (self = [super init]) { 40 | _tableView = tableView; 41 | _tableView.dataSource = self; 42 | _tableView.delegate = self; 43 | _tableView.rowHeight = CLPhotoCollectionCellRowHeight; 44 | [_tableView registerClass:[XYPhotoTableCell class] forCellReuseIdentifier:XYPhotoTableCellReuseIdentifier]; 45 | 46 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 47 | if (status == PHAuthorizationStatusAuthorized) { 48 | 49 | PHFetchResult *albums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum 50 | subtype:PHAssetCollectionSubtypeAlbumRegular 51 | options:nil]; 52 | //获取用户的相册集合 53 | PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil]; 54 | 55 | self.collectionFetchResults = @[albums, topLevelUserCollections]; 56 | 57 | [self updateCollectionArrays]; 58 | 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | [tableView reloadData]; 61 | }); 62 | 63 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 64 | } else { 65 | [UIAlertController xy_showAlertPhotoSettingIfUnauthorized]; 66 | } 67 | }]; 68 | } 69 | return self; 70 | } 71 | 72 | - (void)updateCollectionArrays 73 | { 74 | PHFetchResult *albums = self.collectionFetchResults[CLPhotoAlbumDataSourceTypeAlbums]; 75 | NSMutableArray *albumsWithAssetsArray = [NSMutableArray arrayWithCapacity:albums.count]; 76 | [albums enumerateObjectsUsingBlock:^(PHAssetCollection *obj, NSUInteger idx, BOOL *stop) { 77 | PHFetchResult *assetsInCollection = [PHFetchResult xy_fetchResultWithAssetCollection:obj mediaType:[XYPhotoSelectedAssetManager sharedManager].mediaType]; 78 | if (assetsInCollection.count > 0 && assetsInCollection.count < NSNotFound) { 79 | [albumsWithAssetsArray addObject:obj]; 80 | } 81 | }]; 82 | 83 | PHFetchResult *topLevelUserCollections = self.collectionFetchResults[CLPhotoAlbumDataSourceTypeTopLevelUserCollections]; 84 | NSMutableArray *topLevelUserCollectionsWithAssetsArray = [NSMutableArray arrayWithCapacity:topLevelUserCollections.count]; 85 | [topLevelUserCollections enumerateObjectsUsingBlock:^(PHCollection *obj, NSUInteger idx, BOOL *stop) { 86 | if ([obj isKindOfClass:[PHAssetCollection class]]) { 87 | PHFetchResult *assetsInCollection = [PHFetchResult xy_fetchResultWithAssetCollection:(PHAssetCollection *)obj mediaType:[XYPhotoSelectedAssetManager sharedManager].mediaType]; 88 | if (assetsInCollection.count > 0 && assetsInCollection.count < NSNotFound) { 89 | [topLevelUserCollectionsWithAssetsArray addObject:obj]; 90 | } 91 | } 92 | }]; 93 | 94 | self.collectionArrays = @[albumsWithAssetsArray, topLevelUserCollectionsWithAssetsArray]; 95 | } 96 | 97 | - (PHAssetCollection *)assetCollectionForIndexPath:(NSIndexPath *)indexPath 98 | { 99 | NSArray *fetchResults = self.collectionArrays[indexPath.section]; 100 | return fetchResults[indexPath.row]; 101 | } 102 | 103 | #pragma mark - PHPhotoLibraryChangeObserver 104 | 105 | - (void)photoLibraryDidChange:(PHChange *)changeInstance 106 | { 107 | dispatch_block_t dispatchBlock = ^{ 108 | NSMutableArray *updatedCollectionsFetchResults = nil; 109 | 110 | for (PHFetchResult *collectionsFetchResult in self.collectionFetchResults) { 111 | if (collectionsFetchResult.count<=0) { 112 | continue; 113 | } 114 | PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult]; 115 | if (changeDetails) { 116 | if (!updatedCollectionsFetchResults) { 117 | updatedCollectionsFetchResults = [self.collectionFetchResults mutableCopy]; 118 | } 119 | [updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]]; 120 | } 121 | } 122 | 123 | if (updatedCollectionsFetchResults) { 124 | self.collectionFetchResults = updatedCollectionsFetchResults; 125 | [self updateCollectionArrays]; 126 | [self.tableView reloadData]; 127 | } 128 | }; 129 | 130 | if ([NSThread currentThread] != [NSThread mainThread]) { 131 | dispatch_async(dispatch_get_main_queue(), dispatchBlock); 132 | } else { 133 | dispatchBlock(); 134 | } 135 | } 136 | 137 | #pragma mark - UITableViewDataSource 138 | 139 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 140 | { 141 | return CLPhotoAlbumDataSourceTypeCount; 142 | } 143 | 144 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 145 | { 146 | return self.collectionArrays[section].count; 147 | } 148 | 149 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 150 | { 151 | XYPhotoTableCell *cell = [tableView dequeueReusableCellWithIdentifier:XYPhotoTableCellReuseIdentifier forIndexPath:indexPath]; 152 | cell.collection = [self assetCollectionForIndexPath:indexPath]; 153 | return cell; 154 | } 155 | 156 | #pragma mark - UITableViewDelegate 157 | 158 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 159 | { 160 | PHAssetCollection *assetCollection = [self assetCollectionForIndexPath:indexPath]; 161 | if ([self.delegate respondsToSelector:@selector(assetTableDataSource:selectedAssetCollection:)]) { 162 | [self.delegate assetTableDataSource:self selectedAssetCollection:assetCollection]; 163 | } 164 | [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoAlbumDetailController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoAlbumDetailController.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import Photos; 10 | @import UIKit; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface XYPhotoAlbumDetailController : UIViewController 15 | 16 | @property (nonatomic, readonly) UICollectionView *collectionView; 17 | 18 | - (instancetype)initWithFetchResult:(PHFetchResult *)fetchResult; 19 | 20 | - (instancetype)init __attribute__((unavailable("此方法已弃用,请使用initWithFetchResult方法"))); 21 | + (instancetype)new __attribute__((unavailable("此方法已弃用,请使用initWithFetchResult方法"))); 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoAlbumDetailController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoAlbumDetailController.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoAlbumDetailController.h" 10 | 11 | #import "XYPhotoCollectionDataSource.h" 12 | #import "XYPhotoCollectionFlowLayout.h" 13 | #import "XYPhotoSelectedAssetPreviewView.h" 14 | #import "XYPhotoSelectedAssetManager.h" 15 | #import "XYPhotoMultiImagePicker.h" 16 | #import "XYPhotoPreviewViewController.h" 17 | 18 | @interface XYPhotoAlbumDetailController () 19 | 20 | @property (nonatomic, strong) UICollectionView *collectionView; 21 | @property (nonatomic, strong) XYPhotoSelectedAssetPreviewView *bottomView; 22 | 23 | @property (nonatomic, strong) PHFetchResult *fetchResult; 24 | @property (nonatomic, strong) XYPhotoCollectionDataSource *dataSource; 25 | 26 | @end 27 | 28 | @implementation XYPhotoAlbumDetailController 29 | 30 | - (instancetype)initWithFetchResult:(PHFetchResult *)fetchResult 31 | { 32 | if (self = [super init]) { 33 | _fetchResult = fetchResult; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)loadView 39 | { 40 | [super loadView]; 41 | 42 | UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(close)]; 43 | self.navigationItem.rightBarButtonItems = @[cancel]; 44 | 45 | XYPhotoCollectionFlowLayout *layout = [[XYPhotoCollectionFlowLayout alloc] init]; 46 | self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout]; 47 | self.collectionView.translatesAutoresizingMaskIntoConstraints = false; 48 | self.collectionView.allowsMultipleSelection = true; 49 | [self.view addSubview:self.collectionView]; 50 | 51 | self.bottomView = [[XYPhotoSelectedAssetPreviewView alloc] initWithFrame:self.view.frame]; 52 | self.bottomView.translatesAutoresizingMaskIntoConstraints = false; 53 | self.bottomView.delegate = self; 54 | [self.view addSubview:self.bottomView]; 55 | 56 | if (@available(iOS 13.0, *)) { 57 | self.collectionView.backgroundColor = [UIColor systemBackgroundColor]; 58 | self.view.backgroundColor = [UIColor systemBackgroundColor]; 59 | } else { 60 | self.collectionView.backgroundColor = [UIColor whiteColor]; 61 | self.view.backgroundColor = [UIColor whiteColor]; 62 | } 63 | } 64 | 65 | - (void)viewDidLoad 66 | { 67 | [super viewDidLoad]; 68 | [self addConstrains]; 69 | 70 | // 等loadView完成后调用 71 | self.dataSource = [[XYPhotoCollectionDataSource alloc] initWithCollectionView:self.collectionView fetchResult:self.fetchResult]; 72 | self.dataSource.delegate = self; 73 | } 74 | 75 | - (void)viewDidAppear:(BOOL)animated 76 | { 77 | [super viewDidAppear:animated]; 78 | self.dataSource.shouldCache = true; 79 | } 80 | 81 | - (void)addConstrains 82 | { 83 | BOOL isSingleSelected = [XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets == 1; 84 | self.bottomView.hidden = isSingleSelected; 85 | 86 | [NSLayoutConstraint activateConstraints:@[ 87 | [_collectionView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor], 88 | [_collectionView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor], 89 | [_collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], 90 | [_collectionView.bottomAnchor constraintEqualToAnchor:self.bottomView.topAnchor], 91 | [_bottomView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor], 92 | [_bottomView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor], 93 | [_bottomView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor], 94 | [_bottomView.heightAnchor constraintEqualToConstant:isSingleSelected ? 0:60] 95 | ]]; 96 | } 97 | 98 | - (void)close 99 | { 100 | XYPhotoMultiImagePicker *picker = (XYPhotoMultiImagePicker *)self.navigationController; 101 | 102 | if ([picker.pickerDelegate respondsToSelector:@selector(multiImagePickerDidCancel:)]) { 103 | [picker.pickerDelegate multiImagePickerDidCancel:picker]; 104 | } else { 105 | [self.navigationController dismissViewControllerAnimated:YES completion:^{ 106 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 107 | }]; 108 | } 109 | } 110 | 111 | #pragma mark - XYPhotoSelectedAssetPreviewViewDelegate 112 | 113 | - (void)assetsSelectedPreviewView:(XYPhotoSelectedAssetPreviewView *)previewView didClickWithIndex:(NSInteger)index asset:(PHAsset *)asset; 114 | { 115 | XYPhotoPreviewViewController *previewController = [[XYPhotoPreviewViewController alloc] init]; 116 | previewController.photos = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 117 | previewController.selectedIndex = index; 118 | [self.navigationController pushViewController:previewController animated:YES]; 119 | } 120 | 121 | #pragma mark - XYPhotoCollectionDataSourceDelegate 122 | 123 | - (void)assetCollectionDataSource:(XYPhotoCollectionDataSource *)dataSource selectedIndex:(NSInteger)selectedIndex inFetchResult:(PHFetchResult *)fetchResult 124 | { 125 | XYPhotoPreviewViewController *previewController = [[XYPhotoPreviewViewController alloc] init]; 126 | previewController.fetchResult = fetchResult; 127 | previewController.selectedIndex = selectedIndex; 128 | [self.navigationController pushViewController:previewController animated:YES]; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoAlbumListController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoAlbumListController.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | 10 | @import Photos; 11 | @import UIKit; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface XYPhotoAlbumListController : UIViewController 16 | 17 | @property (nonatomic, readonly) UITableView *tableView; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoAlbumListController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoAlbumListController.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoAlbumListController.h" 10 | #import "XYPhotoDataCategory.h" 11 | #import "XYPhotoTableDataSource.h" 12 | #import "XYPhotoAlbumDetailController.h" 13 | #import "XYPhotoSelectedAssetManager.h" 14 | #import "XYPhotoKitHelper.h" 15 | #import "XYPhotoMultiImagePicker.h" 16 | 17 | @interface XYPhotoAlbumListController () 18 | 19 | @property (nonatomic, strong) UITableView *tableView; 20 | @property (nonatomic, strong) XYPhotoTableDataSource *dataSource; 21 | 22 | @end 23 | 24 | @implementation XYPhotoAlbumListController 25 | 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | 30 | self.title = XYPhotoImagePickerName.selectAnAlbum; 31 | if (@available(iOS 13.0, *)) { 32 | self.view.backgroundColor = [UIColor systemBackgroundColor]; 33 | } else { 34 | self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1]; 35 | } 36 | UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(close)]; 37 | self.navigationItem.rightBarButtonItems = @[cancel]; 38 | 39 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 40 | dispatch_async(dispatch_get_main_queue(), ^{ 41 | if (status == PHAuthorizationStatusAuthorized) { 42 | [self initTableViewAndData]; 43 | } else { 44 | [self showPhotoAccessHelpView]; 45 | } 46 | }); 47 | }]; 48 | } 49 | 50 | - (void)initTableViewAndData 51 | { 52 | if (!self.tableView) { 53 | self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain]; 54 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 55 | self.tableView.translatesAutoresizingMaskIntoConstraints = false; 56 | [self.view addSubview:self.tableView]; 57 | } 58 | 59 | self.dataSource = [[XYPhotoTableDataSource alloc] initWithTableView:self.tableView]; 60 | self.dataSource.delegate = self; 61 | 62 | [NSLayoutConstraint activateConstraints:@[ 63 | [_tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 64 | [_tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 65 | [_tableView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], 66 | [_tableView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor], 67 | ]]; 68 | } 69 | 70 | - (void)showPhotoAccessHelpView 71 | { 72 | [UIAlertController xy_showAlertPhotoSettingIfUnauthorized]; 73 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage xy_imageWithName:@"cl_photo_picker_inaccessible"]]; 74 | imageView.translatesAutoresizingMaskIntoConstraints = false; 75 | imageView.contentMode = UIViewContentModeCenter; 76 | [self.view addSubview:imageView]; 77 | 78 | [NSLayoutConstraint activateConstraints:@[ 79 | [imageView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor], 80 | [imageView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor], 81 | [imageView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], 82 | [imageView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor], 83 | ]]; 84 | } 85 | 86 | - (void)close 87 | { 88 | XYPhotoMultiImagePicker *picker = (XYPhotoMultiImagePicker *)self.navigationController; 89 | 90 | if ([picker.pickerDelegate respondsToSelector:@selector(multiImagePickerDidCancel:)]) { 91 | [picker.pickerDelegate multiImagePickerDidCancel:picker]; 92 | } else { 93 | [self.navigationController dismissViewControllerAnimated:YES completion:^{ 94 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 95 | }]; 96 | } 97 | } 98 | 99 | #pragma mark - XYPhotoTableDataSourceDelegate 100 | 101 | - (void)assetTableDataSource:(XYPhotoTableDataSource *)dataSource selectedAssetCollection:(PHAssetCollection *)assetCollection 102 | { 103 | PHFetchResult *fetchResult = [PHFetchResult xy_fetchResultWithAssetCollection:assetCollection mediaType:XYPhotoSelectedAssetManager.sharedManager.mediaType]; 104 | XYPhotoAlbumDetailController *assetsViewController = [[XYPhotoAlbumDetailController alloc] initWithFetchResult:fetchResult]; 105 | assetsViewController.title = assetCollection.localizedTitle; 106 | [self.navigationController pushViewController:assetsViewController animated:YES]; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoCameraController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCameraController.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 13/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XYPhotoCameraController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoCameraController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCameraController.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 13/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoCameraController.h" 10 | #import "XYPhotoCameraOverlayView.h" 11 | #import "XYPhotoDataCategory.h" 12 | #import "XYPhotoPreviewViewController.h" 13 | #import "XYPhotoMultiCameraPicker.h" 14 | #import "XYPhotoSelectedAssetManager.h" 15 | 16 | @interface XYPhotoCameraController () 17 | 18 | @property (nonatomic, strong) UIImagePickerController *imagePicker; 19 | @property (nonatomic, strong) XYPhotoCameraOverlayView *overlayView; 20 | 21 | @end 22 | 23 | @implementation XYPhotoCameraController 24 | 25 | - (BOOL)shouldAutorotate 26 | { 27 | return false; 28 | } 29 | 30 | - (BOOL)prefersStatusBarHidden 31 | { 32 | return YES; 33 | } 34 | 35 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations 36 | { 37 | return UIInterfaceOrientationMaskPortrait; 38 | } 39 | 40 | - (void)loadView 41 | { 42 | [super loadView]; 43 | self.view.backgroundColor = [UIColor blackColor]; 44 | 45 | if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { 46 | if ([UIAlertController xy_showAlertCameraSettingIfUnauthorized]) { 47 | self.imagePicker = [[UIImagePickerController alloc] init]; 48 | self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; 49 | self.imagePicker.delegate = self; 50 | self.imagePicker.showsCameraControls = false; 51 | self.imagePicker.view.translatesAutoresizingMaskIntoConstraints = false; 52 | [self.view addSubview:self.imagePicker.view]; 53 | [self addChildViewController:self.imagePicker]; 54 | [self.imagePicker didMoveToParentViewController:self]; 55 | 56 | [NSLayoutConstraint activateConstraints:@[ 57 | [_imagePicker.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 58 | [_imagePicker.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 59 | [_imagePicker.view.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], 60 | [_imagePicker.view.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor], 61 | ]]; 62 | } 63 | } else { 64 | [UIAlertController xy_showTitle:nil message:@"该设备不支持相机功能"]; 65 | } 66 | 67 | _overlayView = [[XYPhotoCameraOverlayView alloc] initWithFrame:self.view.bounds]; 68 | _overlayView.translatesAutoresizingMaskIntoConstraints = false; 69 | _overlayView.delegate = self; 70 | _overlayView.flashMode = UIImagePickerControllerCameraFlashModeAuto; 71 | _overlayView.cameraDeivce = UIImagePickerControllerCameraDeviceRear; 72 | [self.view addSubview:_overlayView]; 73 | 74 | [NSLayoutConstraint activateConstraints:@[ 75 | [_overlayView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 76 | [_overlayView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 77 | [_overlayView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 78 | [_overlayView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 79 | ]]; 80 | } 81 | 82 | - (void)viewWillAppear:(BOOL)animated 83 | { 84 | [super viewWillAppear:animated]; 85 | [self.navigationController setNavigationBarHidden:YES animated:animated]; 86 | } 87 | 88 | 89 | #pragma mark - UIImagePickerControllerDelegate 90 | 91 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 92 | { 93 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ 94 | [PHAssetChangeRequest creationRequestForAssetFromImage:[info valueForKey:UIImagePickerControllerOriginalImage]]; 95 | } completionHandler:^(BOOL success, NSError * _Nullable error) { 96 | if (success) { 97 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 98 | PHAsset *asset = [PHAsset xy_getTheCloestAsset]; 99 | dispatch_async(dispatch_get_main_queue(), ^{ 100 | XYPhotoSelectedAssetManager *sharedManager = XYPhotoSelectedAssetManager.sharedManager; 101 | if (sharedManager.maxNumberOfAssets == 1) { 102 | [sharedManager resetSelectedAsset:@[asset]]; 103 | [self finishedCameraOverlayView:nil]; 104 | } else { 105 | [sharedManager addSelectedAsset:asset]; 106 | } 107 | }); 108 | }); 109 | } else { 110 | dispatch_async(dispatch_get_main_queue(), ^{ 111 | [UIAlertController xy_showTitle:nil message:@"获取照片失败"]; 112 | }); 113 | } 114 | }]; 115 | } 116 | 117 | #pragma mark - XYPhotoCameraOverlayViewDelegate 118 | 119 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didShutted:(UIButton *)sender 120 | { 121 | // 限制保护 122 | XYPhotoSelectedAssetManager *manager = [XYPhotoSelectedAssetManager sharedManager]; 123 | if (manager.maxNumberOfAssets != 0 && manager.maxNumberOfAssets != 1 && manager.maxNumberOfAssets <= manager.selectedAssets.count) { 124 | NSString *tip = (manager.maxNumberLimitText ?: @"").xy_isEmpty ? @"已达到最大照片数限制" : manager.maxNumberLimitText; 125 | [UIAlertController xy_showTitle:@"友情提示" message:tip]; 126 | return; 127 | } 128 | 129 | [_imagePicker takePicture]; 130 | 131 | UIView *flashView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 132 | if (@available(iOS 13.0, *)) { 133 | [flashView setBackgroundColor:[UIColor systemBackgroundColor]]; 134 | } else { 135 | [flashView setBackgroundColor:[UIColor whiteColor]]; 136 | } 137 | [self.view insertSubview:flashView belowSubview:_overlayView]; 138 | [UIView animateWithDuration:.4f animations:^{ 139 | [flashView setAlpha:0.f]; 140 | } completion:^(BOOL finished){ 141 | [flashView removeFromSuperview]; 142 | }]; 143 | } 144 | 145 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didSwitchedCameraDeivce:(UIImagePickerControllerCameraDevice)cameraDeivce 146 | { 147 | [_imagePicker setCameraDevice:cameraDeivce]; 148 | } 149 | 150 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didSwitchedFlashMode:(UIImagePickerControllerCameraFlashMode)flashMode 151 | { 152 | [_imagePicker setCameraFlashMode:flashMode]; 153 | } 154 | 155 | - (void)canceledCameraOverlayView:(XYPhotoCameraOverlayView *)overlayView 156 | { 157 | XYPhotoMultiCameraPicker *picker = (XYPhotoMultiCameraPicker *)self.navigationController; 158 | 159 | if ([picker.pickerDelegate respondsToSelector:@selector(multiCameraPickerDidCancel:)]) { 160 | [picker.pickerDelegate multiCameraPickerDidCancel:picker]; 161 | } else { 162 | [self dismissViewControllerAnimated:YES completion:^{ 163 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 164 | }]; 165 | } 166 | } 167 | 168 | - (void)finishedCameraOverlayView:(XYPhotoCameraOverlayView *)overlayView 169 | { 170 | if ([[XYPhotoSelectedAssetManager sharedManager].delegate respondsToSelector:@selector(doneSelectingAssets)]) { 171 | [[XYPhotoSelectedAssetManager sharedManager].delegate doneSelectingAssets]; 172 | } 173 | } 174 | 175 | - (void)assetsSelectedPreviewView:(XYPhotoSelectedAssetPreviewView *)previewView didClickWithIndex:(NSInteger)index asset:(PHAsset *)asset 176 | { 177 | XYPhotoPreviewViewController *previewController = [[XYPhotoPreviewViewController alloc] init]; 178 | previewController.photos = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 179 | previewController.selectedIndex = index; 180 | [self.navigationController pushViewController:previewController animated:YES]; 181 | } 182 | 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoMultiCameraPicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoMultiCameraPicker.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 08/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import Photos; 10 | @import UIKit; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class XYPhotoMultiCameraPicker; 15 | @protocol XYPhotoMultiCameraPickerDelegate 16 | 17 | @required 18 | /** 19 | 代理完成选择Asset 20 | 21 | @param multiImagePicker 导航栏控制器 22 | @param assets 已选中的Asset 23 | */ 24 | - (void)multiCameraPicker:(XYPhotoMultiCameraPicker *)multiImagePicker selectedAssets:(NSArray *)assets; 25 | 26 | @optional 27 | /** 28 | 选择Asset的时候,如果delegate有定义此方法,则导航栏右侧点击cancel时回调此函数 29 | **注意:** 需要对 `XYPhotoSelectedAssetManager` 进行 resetManager 操作 30 | 可以选择不实现该函数,则自动完成dimiss和resetManager 31 | 32 | @param multiImagePicker 导航栏控制器 33 | */ 34 | - (void)multiCameraPickerDidCancel:(XYPhotoMultiCameraPicker *)multiImagePicker; 35 | 36 | @end 37 | 38 | @protocol XYPhotoMultiCameraPickerDataSource 39 | 40 | /** 41 | 提前载入预设选中的Asset 42 | 43 | @return 已选中的Asset 44 | */ 45 | - (nullable NSArray *)multiCameraPickerLoadPresetSelectedAssets; 46 | @end 47 | 48 | 49 | @interface XYPhotoMultiCameraPicker : UINavigationController 50 | 51 | /** 52 | 最大选择数量 53 | Defaults 0 54 | */ 55 | @property (nonatomic) NSUInteger maxNumberOfAssets; 56 | 57 | /** 58 | 是否允许网络请求从iCloud下载 59 | Default NO 60 | */ 61 | @property (nonatomic) BOOL allowNetRequestIfiCloud; 62 | 63 | @property (nonatomic, weak, nullable) idpickerDataSource; 64 | @property (nonatomic, weak, nullable) idpickerDelegate; 65 | 66 | @end 67 | 68 | NS_ASSUME_NONNULL_END 69 | 70 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoMultiCameraPicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoMultiCameraPicker.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 08/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoMultiCameraPicker.h" 10 | #import "XYPhotoSelectedAssetManager.h" 11 | #import "XYPhotoCameraController.h" 12 | 13 | @interface XYPhotoMultiCameraPicker () 14 | 15 | @end 16 | 17 | @implementation XYPhotoMultiCameraPicker 18 | 19 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 20 | { 21 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 22 | self.allowNetRequestIfiCloud = false; 23 | self.modalPresentationStyle = UIModalPresentationFullScreen; 24 | [self setViewControllers:@[[[XYPhotoCameraController alloc] init]] animated:NO]; 25 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 26 | [XYPhotoSelectedAssetManager sharedManager].mediaType = PHAssetMediaTypeImage; 27 | [XYPhotoSelectedAssetManager sharedManager].delegate = self; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | 35 | // 不能在初始化的时候调用此方法,需XYPhotoSelectedAssetManager的各个参数设置完毕后执行,即set方法执行完毕 36 | if ([self.pickerDataSource respondsToSelector:@selector(multiCameraPickerLoadPresetSelectedAssets)]) { 37 | [[XYPhotoSelectedAssetManager sharedManager] resetSelectedAsset:[self.pickerDataSource multiCameraPickerLoadPresetSelectedAssets]]; 38 | } 39 | } 40 | 41 | - (void)setMaxNumberOfAssets:(NSUInteger)maxNumberOfAssets 42 | { 43 | _maxNumberOfAssets = maxNumberOfAssets; 44 | [XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets = maxNumberOfAssets; 45 | } 46 | 47 | - (void)setAllowNetRequestIfiCloud:(BOOL)allowNetRequestIfiCloud 48 | { 49 | _allowNetRequestIfiCloud = allowNetRequestIfiCloud; 50 | [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud = allowNetRequestIfiCloud; 51 | } 52 | 53 | #pragma mark - XYPhotoSelectedAssetManagerDelegate 54 | 55 | - (void)doneSelectingAssets 56 | { 57 | if ([self.pickerDelegate respondsToSelector:@selector(multiCameraPicker:selectedAssets:)]) { 58 | [self.pickerDelegate multiCameraPicker:self selectedAssets:[XYPhotoSelectedAssetManager sharedManager].selectedAssets]; 59 | } 60 | 61 | [self dismissViewControllerAnimated:YES completion:^{ 62 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 63 | }]; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoMultiImagePicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoKitDemo.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger, CLPhotoMultiPickerStartPosition){ 15 | // 展示相册列表 tableView,可以push到collectionView 16 | CLPhotoMultiPickerStartPositionAlbums, 17 | // 展示全部照片 collectionView,可以pop到 tableview 18 | CLPhotoMultiPickerStartPositionCameraRoll, 19 | }; 20 | 21 | @class XYPhotoMultiImagePicker; 22 | @protocol XYPhotoMultiImagePickerDelegate 23 | 24 | @required 25 | 26 | /** 27 | 代理完成选择Asset 28 | 29 | @param multiImagePicker 导航栏控制器 30 | @param assets 已选中的Asset 31 | */ 32 | - (void)multiImagePicker:(XYPhotoMultiImagePicker *)multiImagePicker selectedAssets:(NSArray *)assets; 33 | 34 | @optional 35 | 36 | /** 37 | 选择Asset的时候,如果delegate有定义此方法,则导航栏右侧点击cancel时回调此函数 38 | **注意:** 需要对 `XYPhotoSelectedAssetManager` 进行 resetManager 操作 39 | 可以选择不实现该函数,则自动完成dimiss和resetManager 40 | 41 | @param multiImagePicker 导航栏控制器 42 | */ 43 | - (void)multiImagePickerDidCancel:(XYPhotoMultiImagePicker *)multiImagePicker; 44 | @end 45 | 46 | @protocol XYPhotoMultiImagePickerDataSource 47 | 48 | /** 49 | 提前载入预设选中的Asset 50 | 51 | @return 已选中的Asset 52 | */ 53 | - (NSArray *)multiImagePickerLoadPresetSelectedAssets; 54 | 55 | @optional 56 | /** 57 | 没有相册授权时,展示的提示图片 58 | 59 | @return 展示的图片 60 | */ 61 | - (nullable UIImage *)multiImagePickerUnauthorizedPlaceHolderImage; 62 | 63 | @end 64 | 65 | @interface XYPhotoMultiImagePicker : UINavigationController 66 | 67 | /** 68 | 代理回调已选中的asset 69 | */ 70 | @property (nonatomic, weak, nullable) id pickerDelegate; 71 | 72 | /** 73 | 传入默认的初始化资源 74 | */ 75 | @property (nonatomic, weak, nullable) id pickerDataSource; 76 | 77 | /** 78 | * PHAssetMediaTypeUnknown - 全部展示. 79 | * PHAssetMediaTypeImage - 只展示图片. 80 | * PHAssetMediaTypeVideo - 只展示视频. 81 | * PHAssetMediaTypeAudio - 只展示音频. 82 | */ 83 | @property (nonatomic) PHAssetMediaType mediaType; 84 | 85 | /** 86 | 是否允许网络请求从iCloud下载 87 | Default NO 88 | */ 89 | @property (nonatomic) BOOL allowNetRequestIfiCloud; 90 | 91 | /** 92 | 两种选择,一种直接选择照片,一种进入相册集列表再选择 93 | */ 94 | @property (nonatomic) CLPhotoMultiPickerStartPosition startPosition; 95 | 96 | /** 97 | collectionView一行的collectionCell数量 98 | Default 3. 99 | */ 100 | @property (nonatomic) NSInteger assetCollectionViewColumnCount; 101 | 102 | /** 103 | 最大选择数量 104 | Defaults 0 105 | */ 106 | @property (nonatomic) NSUInteger maxNumberOfAssets; 107 | 108 | //+ (void)showImagePickerWithConfiger:(void(^)(XYPhotoMultiImagePicker *picker))configer successBlock:(void(^)(NSArray *selectedAsset))successBlock; 109 | 110 | @end 111 | 112 | NS_ASSUME_NONNULL_END 113 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoMultiImagePicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoKitDemo.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoMultiImagePicker.h" 10 | 11 | #import "XYPhotoDataCategory.h" 12 | #import "XYPhotoAlbumListController.h" 13 | #import "XYPhotoCollectionViewCell.h" 14 | #import "XYPhotoAlbumDetailController.h" 15 | #import "XYPhotoKitHelper.h" 16 | #import "XYPhotoSelectedAssetManager.h" 17 | 18 | @interface XYPhotoMultiImagePicker () 19 | 20 | @end 21 | 22 | @implementation XYPhotoMultiImagePicker 23 | 24 | #pragma mark - Initialization 25 | 26 | - (instancetype)init 27 | { 28 | if (self = [super init]) { 29 | self.modalPresentationStyle = UIModalPresentationFullScreen; 30 | self.allowNetRequestIfiCloud = false; 31 | [self setViewControllers:@[[[XYPhotoAlbumListController alloc] init]] animated:NO]; 32 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 33 | [XYPhotoSelectedAssetManager sharedManager].delegate = self; 34 | } 35 | return self; 36 | } 37 | 38 | #pragma mark - View Lifecycle 39 | 40 | - (void)viewDidLoad 41 | { 42 | [super viewDidLoad]; 43 | 44 | if ([XYPhotoKitHelper isAvailableAccessPhoto]) { 45 | [self setupView]; 46 | } 47 | 48 | // 不能在初始化的时候调用此方法,需XYPhotoSelectedAssetManager的各个参数设置完毕后执行,即set方法执行完毕 49 | if ([self.pickerDataSource respondsToSelector:@selector(multiImagePickerLoadPresetSelectedAssets)]) { 50 | [[XYPhotoSelectedAssetManager sharedManager] resetSelectedAsset:[self.pickerDataSource multiImagePickerLoadPresetSelectedAssets]]; 51 | } 52 | } 53 | 54 | - (void)dealloc 55 | { 56 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 57 | [XYPhotoSelectedAssetManager sharedManager].delegate = nil; 58 | } 59 | 60 | #pragma mark - Setup 61 | 62 | - (void)setupView 63 | { 64 | if (self.startPosition == CLPhotoMultiPickerStartPositionCameraRoll) { 65 | PHFetchResult *fetchResult = [PHFetchResult xy_fetchResultWithAssetsOfType:self.mediaType]; 66 | XYPhotoAlbumDetailController *cameraRollViewController = [[XYPhotoAlbumDetailController alloc] initWithFetchResult:fetchResult]; 67 | cameraRollViewController.title = XYPhotoImagePickerName.cameraRoll; 68 | NSMutableArray *vcs = self.viewControllers.mutableCopy; 69 | [vcs addObject:cameraRollViewController]; 70 | [self setViewControllers:vcs animated:NO]; 71 | } 72 | } 73 | 74 | #pragma mark - Values passed to the manager. 75 | 76 | - (void)setMaxNumberOfAssets:(NSUInteger)maxNumberOfAssets 77 | { 78 | _maxNumberOfAssets = maxNumberOfAssets; 79 | [XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets = maxNumberOfAssets; 80 | } 81 | 82 | - (void)setMediaType:(PHAssetMediaType)mediaType 83 | { 84 | _mediaType = mediaType; 85 | [XYPhotoSelectedAssetManager sharedManager].mediaType = mediaType; 86 | } 87 | 88 | - (void)setAssetCollectionViewColumnCount:(NSInteger)assetCollectionViewColumnCount 89 | { 90 | _assetCollectionViewColumnCount = assetCollectionViewColumnCount; 91 | [XYPhotoSelectedAssetManager sharedManager].assetCollectionViewColumnCount = assetCollectionViewColumnCount; 92 | } 93 | 94 | - (void)setAllowNetRequestIfiCloud:(BOOL)allowNetRequestIfiCloud 95 | { 96 | _allowNetRequestIfiCloud = allowNetRequestIfiCloud; 97 | [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud = allowNetRequestIfiCloud; 98 | } 99 | 100 | #pragma mark - XYPhotoSelectedAssetManagerDelegate 101 | 102 | - (void)doneSelectingAssets 103 | { 104 | if ([self.pickerDelegate respondsToSelector:@selector(multiImagePicker:selectedAssets:)]) { 105 | [self.pickerDelegate multiImagePicker:self selectedAssets:[[XYPhotoSelectedAssetManager sharedManager] selectedAssets]]; 106 | } 107 | [self dismissViewControllerAnimated:YES completion:^{ 108 | [[XYPhotoSelectedAssetManager sharedManager] resetManager]; 109 | }]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoPreviewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoPreviewViewController.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 31/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | 10 | @import Photos; 11 | @import UIKit; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface XYPhotoPreviewViewController : UIViewController 16 | 17 | /** 18 | 查看已选择的asset时,使用该方法 19 | */ 20 | @property (nonatomic, strong, nullable) NSArray *photos; 21 | 22 | /** 23 | 大图查看某个相册集时,使用该方法; 24 | 同时,监听原生相册资源变化时更新fetchResult 25 | */ 26 | @property (nonatomic, strong, nullable) PHFetchResult *fetchResult; 27 | // 横向scrollview的内容 28 | @property (nonatomic, assign) NSInteger selectedIndex; 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /XYPhotoKit/ViewControllers/XYPhotoPreviewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoPreviewViewController.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 31/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoPreviewViewController.h" 10 | #import "XYPhotoPreviewOverlayView.h" 11 | #import "XYPhotoHorizontalScrollItemView.h" 12 | 13 | @interface XYPhotoPreviewViewController () 14 | 15 | @property (nonatomic, strong) XYPhotoHorizontalScrollView *scrollView; 16 | @property (nonatomic, strong) XYPhotoPreviewOverlayView *overLayView; 17 | @property (nonatomic, assign) BOOL navigationBarHiddenBeforeEnter; 18 | 19 | @end 20 | 21 | @implementation XYPhotoPreviewViewController 22 | 23 | - (void)dealloc 24 | { 25 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 26 | } 27 | 28 | - (void)setFetchResult:(PHFetchResult *)fetchResult 29 | { 30 | _fetchResult = fetchResult; 31 | // 查看某个相册大图,则需要观察lib变化 32 | if (fetchResult) { 33 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 34 | } 35 | } 36 | 37 | - (void)setSelectedIndex:(NSInteger)selectedIndex 38 | { 39 | _selectedIndex = selectedIndex; 40 | if (_scrollView) { 41 | [_scrollView setCurrentIndex:_selectedIndex animated:NO]; 42 | [_overLayView updateSelectedAsset:_fetchResult ? _fetchResult[selectedIndex]:_photos[selectedIndex]]; 43 | [_overLayView updateTitleAtIndex:_selectedIndex sum:_fetchResult ? _fetchResult.count:_photos.count]; 44 | } 45 | } 46 | 47 | - (BOOL)shouldAutorotate 48 | { 49 | return NO; 50 | } 51 | 52 | - (BOOL)prefersStatusBarHidden { 53 | return YES; 54 | } 55 | 56 | - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { 57 | return UIStatusBarAnimationFade; 58 | } 59 | 60 | - (void)loadView 61 | { 62 | [super loadView]; 63 | self.view.backgroundColor = [UIColor blackColor]; 64 | self.view.clipsToBounds = true; 65 | 66 | _scrollView = [[XYPhotoHorizontalScrollView alloc] initWithFrame:CGRectMake(-5, 0, self.view.frame.size.width+10, self.view.frame.size.height)]; 67 | _scrollView.translatesAutoresizingMaskIntoConstraints = false; 68 | _scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight; 69 | _scrollView.backgroundColor = [UIColor blackColor]; 70 | _scrollView.horizontalDelegate = self; 71 | _scrollView.horizontalDataSource = self; 72 | [self.view addSubview:_scrollView]; 73 | 74 | _overLayView = [[XYPhotoPreviewOverlayView alloc] initWithFrame:self.view.bounds]; 75 | _overLayView.translatesAutoresizingMaskIntoConstraints = false; 76 | _overLayView.delegate = self; 77 | [self.view addSubview:_overLayView]; 78 | 79 | [NSLayoutConstraint activateConstraints:@[ 80 | [_scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:-5], 81 | [_scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:5], 82 | [_scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 83 | [_scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 84 | [_overLayView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], 85 | [_overLayView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], 86 | [_overLayView.topAnchor constraintEqualToAnchor:self.view.topAnchor], 87 | [_overLayView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], 88 | ]]; 89 | } 90 | 91 | - (void)viewWillAppear:(BOOL)animated 92 | { 93 | [super viewWillAppear:animated]; 94 | _navigationBarHiddenBeforeEnter = self.navigationController.isNavigationBarHidden; 95 | [self.navigationController setNavigationBarHidden:YES animated:animated]; 96 | } 97 | 98 | - (void)viewWillDisappear:(BOOL)animated 99 | { 100 | [super viewWillDisappear:animated]; 101 | [self.navigationController setNavigationBarHidden:_navigationBarHiddenBeforeEnter animated:animated]; 102 | } 103 | 104 | - (void)viewDidLoad 105 | { 106 | [super viewDidLoad]; 107 | [_scrollView reloadData]; 108 | [self setSelectedIndex:_selectedIndex]; 109 | } 110 | 111 | #pragma mark - BPHorizontalScrollViewDataSource & BPHorizontalScrollViewDelegate 112 | - (XYHorizontalScrollItemView *)horizontalScrollView:(XYPhotoHorizontalScrollView *)scroller itemViewForIndex:(NSInteger)index 113 | { 114 | XYPhotoHorizontalScrollItemView *view = (XYPhotoHorizontalScrollItemView *)[scroller dequeueReusableItemView]; 115 | if (!view) { 116 | view = [[XYPhotoHorizontalScrollItemView alloc] initWithFrame:scroller.bounds]; 117 | view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 118 | view.photokitDelegate = self; 119 | } 120 | view.frame = scroller.bounds; 121 | view.asset = _fetchResult ? _fetchResult[index] : _photos[index]; 122 | return (XYHorizontalScrollItemView *)view; 123 | } 124 | 125 | - (NSInteger)numberOfItems 126 | { 127 | return _fetchResult ? _fetchResult.count : _photos.count; 128 | } 129 | 130 | - (void)horizontalScrollView:(XYPhotoHorizontalScrollView *)scroller didSelectIndex:(NSInteger)index 131 | { 132 | _selectedIndex = index; 133 | PHAsset *asset = _fetchResult ? _fetchResult[index] : _photos[index]; 134 | [_overLayView updateSelectedAsset:asset]; 135 | [_overLayView updateTitleAtIndex:_selectedIndex sum:_fetchResult ? _fetchResult.count:_photos.count]; 136 | } 137 | 138 | #pragma mark - PHPhotoLibraryChangeObserver 139 | 140 | - (void)photoLibraryDidChange:(PHChange *)changeInstance 141 | { 142 | // 后台线程更新检查 143 | dispatch_async(dispatch_get_main_queue(), ^{ 144 | 145 | // assets 被修改了 (增、删、改) 146 | PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.fetchResult]; 147 | if (collectionChanges) { 148 | self.fetchResult = [collectionChanges fetchResultAfterChanges]; 149 | if (self.selectedIndex>=self.fetchResult.count) { 150 | self.selectedIndex = self.fetchResult.count-1; 151 | } 152 | [self.overLayView updateSelectedAsset:self.fetchResult[self.selectedIndex]]; 153 | [self.scrollView reloadData]; 154 | self.scrollView.currentIndex = self.selectedIndex; 155 | } 156 | }); 157 | } 158 | 159 | #pragma mark - XYPhotoPreviewOverlayViewDelegate 160 | 161 | - (void)previewOverlayView:(XYPhotoPreviewOverlayView *)view closeBarButtonItemClick:(UIBarButtonItem *)barbuttonItem 162 | { 163 | [self.navigationController popViewControllerAnimated:YES]; 164 | } 165 | 166 | #pragma mark - XYPhotoSelectedAssetPreviewViewDelegate 167 | 168 | - (void)assetsSelectedPreviewView:(XYPhotoSelectedAssetPreviewView *)previewView didClickWithIndex:(NSInteger)index asset:(PHAsset *)asset 169 | { 170 | if (_fetchResult) { 171 | [_fetchResult enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 172 | if ([obj.localIdentifier isEqualToString:asset.localIdentifier]) { 173 | [self setSelectedIndex:idx]; 174 | *stop = true; 175 | } 176 | }]; 177 | } else if ([_photos containsObject:asset]) { 178 | [self setSelectedIndex:[_photos indexOfObject:asset]]; 179 | } 180 | } 181 | 182 | #pragma mark - XYPhotoHorizontalScrollItemView 183 | 184 | - (void)didTapped:(XYPhotoHorizontalScrollItemView *)scrollItemView; 185 | { 186 | [UIView animateWithDuration:0.3 animations:^{ 187 | self.overLayView.alpha = self.overLayView.alpha<=0 ? 1:0; 188 | }]; 189 | } 190 | 191 | @end 192 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoBottomPreviewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoBottomPreviewCell.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 01/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface XYPhotoBottomPreviewCell : UICollectionViewCell 15 | 16 | @property (nonatomic, readonly) PHAsset *asset; 17 | @property(nonatomic, readonly) UIImageView *imageView; 18 | 19 | - (void)setAsset:(PHAsset *)asset indexPath:(NSIndexPath *)indexPath collectionView:(UICollectionView *)collectionView; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoBottomPreviewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoBottomPreviewCell.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 01/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoBottomPreviewCell.h" 10 | #import "XYPhotoKitHelper.h" 11 | #import "XYPhotoSelectedAssetManager.h" 12 | #import "XYPhotoDataCategory.h" 13 | 14 | @interface XYPhotoBottomPreviewCell() 15 | 16 | @property (nonatomic, strong) PHImageManager *imageManager; 17 | 18 | @property (nonatomic, assign) PHImageRequestID imageRequestId; 19 | 20 | @end 21 | 22 | @implementation XYPhotoBottomPreviewCell 23 | 24 | - (instancetype)initWithFrame:(CGRect)frame 25 | { 26 | if (self = [super initWithFrame:frame]) { 27 | _imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds]; 28 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 29 | _imageView.image = [UIImage xy_imageWithName:@"cl_photokit_placeholder"]; 30 | _imageView.clipsToBounds = true; 31 | if (@available(iOS 13.0, *)) { 32 | _imageView.backgroundColor = [UIColor systemBackgroundColor]; 33 | } else { 34 | _imageView.backgroundColor = [UIColor whiteColor]; 35 | } 36 | [self.contentView addSubview:_imageView]; 37 | 38 | self.imageManager = [[PHCachingImageManager alloc] init]; 39 | self.layer.borderColor = [UIColor lightGrayColor].CGColor; 40 | self.layer.borderWidth = 1.0/[UIScreen mainScreen].scale; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)layoutSubviews 46 | { 47 | [super layoutSubviews]; 48 | _imageView.frame = self.contentView.bounds; 49 | } 50 | 51 | - (void)prepareForReuse 52 | { 53 | [super prepareForReuse]; 54 | self.highlighted = false; 55 | self.imageView.image = [UIImage xy_imageWithName:@"cl_photokit_placeholder"]; 56 | if (_imageRequestId!=0) { 57 | [self.imageManager cancelImageRequest:_imageRequestId]; 58 | } 59 | } 60 | 61 | - (void)setHighlighted:(BOOL)highlighted 62 | { 63 | [super setHighlighted:highlighted]; 64 | self.layer.borderColor = highlighted ? [UIColor systemBlueColor].CGColor : [UIColor lightGrayColor].CGColor; 65 | } 66 | 67 | - (void)setAsset:(PHAsset *)asset indexPath:(NSIndexPath *)indexPath collectionView:(UICollectionView *)collectionView 68 | { 69 | _asset = asset; 70 | 71 | UICollectionViewLayoutAttributes *cellAttributes = [collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath]; 72 | 73 | // 防止复用时加载延时图片展示多次 74 | if (_imageRequestId!=0) { 75 | [self.imageManager cancelImageRequest:_imageRequestId]; 76 | } 77 | 78 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 79 | options.networkAccessAllowed = [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud; 80 | 81 | _imageRequestId = [self.imageManager requestImageForAsset:_asset 82 | targetSize:CGSizeScale(cellAttributes.size, [UIScreen mainScreen].scale) 83 | contentMode:PHImageContentModeAspectFill 84 | options:options 85 | resultHandler:^(UIImage *result, NSDictionary *info) { 86 | NSInteger imageRequestId = [[info objectForKey:PHImageResultRequestIDKey] integerValue]; 87 | if (imageRequestId>0) { 88 | self.imageRequestId = 0; 89 | } 90 | self.imageView.image = result ?: [UIImage xy_imageWithName:@"cl_photokit_placeholder"]; 91 | }]; 92 | } 93 | 94 | @end 95 | 96 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoCameraOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCameraOverlayView.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 11/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoSelectedAssetPreviewView.h" 10 | 11 | @import Photos; 12 | 13 | @class XYPhotoCameraOverlayView; 14 | @protocol XYPhotoCameraOverlayViewDelegate 15 | 16 | /** 17 | 快门按键点击 18 | 19 | @param sender 快门按钮 20 | */ 21 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didShutted:(UIButton *)sender; 22 | 23 | /** 24 | 摄像头翻转切换前后镜头 25 | @param cameraDeivce 前后镜头枚举 26 | */ 27 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didSwitchedCameraDeivce:(UIImagePickerControllerCameraDevice)cameraDeivce; 28 | 29 | /** 30 | 闪光灯控制,自动、开、关 31 | 注意:关闭则照相时是都不会开启软件硬件闪光灯(软件指的6s以后的闪屏补光) 32 | @param flashMode 闪光灯枚举 33 | */ 34 | - (void)cameraOverlayView:(XYPhotoCameraOverlayView *)overlayView didSwitchedFlashMode:(UIImagePickerControllerCameraFlashMode)flashMode; 35 | 36 | /** 37 | 取消,关闭照相取图 38 | */ 39 | - (void)canceledCameraOverlayView:(XYPhotoCameraOverlayView *)overlayView; 40 | 41 | /** 42 | 完成照相去相片 43 | */ 44 | - (void)finishedCameraOverlayView:(XYPhotoCameraOverlayView *)overlayView; 45 | 46 | @end 47 | 48 | @interface XYPhotoCameraOverlayView : UIView 49 | 50 | /** 51 | 实现 UI 元素交互响应的代理方法 52 | */ 53 | @property (nonatomic, weak) id delegate; 54 | 55 | /** 56 | 默认选中的asset 57 | */ 58 | @property (nonatomic, strong) NSArray *defaultAssetArray; 59 | 60 | /** 61 | default: UIImagePickerControllerCameraFlashModeAuto 62 | */ 63 | @property (nonatomic, assign) UIImagePickerControllerCameraFlashMode flashMode; 64 | 65 | /** 66 | default: UIImagePickerControllerCameraDeviceRear 67 | */ 68 | @property (nonatomic, assign) UIImagePickerControllerCameraDevice cameraDeivce; 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoCameraOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCameraOverlayView.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 11/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoCameraOverlayView.h" 10 | #import "XYPhotoSelectedAssetManager.h" 11 | #import "XYPhotoDataCategory.h" 12 | #import "XYPhotoKitHelper.h" 13 | 14 | @interface XYPhotoCameraOverlayView() 15 | 16 | @property (nonatomic, strong) UIView *topBar; 17 | @property (nonatomic, strong) UIButton *flashSwitchButton; 18 | @property (nonatomic, strong) UIButton *deviceSwitchButton; 19 | 20 | @property (nonatomic, strong) UIView *bottomContentView; 21 | @property (nonatomic, strong) XYPhotoSelectedAssetPreviewView *bottomPreview; 22 | 23 | @property (nonatomic, strong) UIButton *shutter; 24 | @property (nonatomic, strong) UILabel *selectedScriptLabel; 25 | 26 | @property (nonatomic, strong) UIButton *closeButton; 27 | @property (nonatomic, strong) UIButton *doneButton; 28 | 29 | @end 30 | 31 | @implementation XYPhotoCameraOverlayView 32 | 33 | - (instancetype)initWithFrame:(CGRect)frame 34 | { 35 | self = [super initWithFrame:frame]; 36 | if (self) { 37 | [self initBaseUIElements]; 38 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedAssetsChanged) name:XYPhotoMultiImagePickerNotifications.assetsChanged object:nil]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)initBaseUIElements 44 | { 45 | // top 46 | _topBar = [[UIView alloc] init]; 47 | _topBar.translatesAutoresizingMaskIntoConstraints = false; 48 | _topBar.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; 49 | [self addSubview:_topBar]; 50 | 51 | _flashSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom]; 52 | _flashSwitchButton.translatesAutoresizingMaskIntoConstraints = false; 53 | [_flashSwitchButton setImage:[UIImage xy_imageWithName:@"cl_photo_picker_camera_flash"] forState:UIControlStateNormal]; 54 | [_flashSwitchButton setTitle:@"自动" forState:UIControlStateNormal]; 55 | _flashSwitchButton.titleLabel.font = [UIFont systemFontOfSize:14]; 56 | [_flashSwitchButton addTarget:self action:@selector(flashButtonClick:) forControlEvents:UIControlEventTouchUpInside]; 57 | [self addSubview:_flashSwitchButton]; 58 | 59 | _deviceSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom]; 60 | _deviceSwitchButton.translatesAutoresizingMaskIntoConstraints = false; 61 | [_deviceSwitchButton setImage:[UIImage xy_imageWithName:@"cl_photo_picker_camera_devicemode"] forState:UIControlStateNormal]; 62 | _deviceSwitchButton.frame = CGRectMake(_topBar.frame.size.width-60, 0, 60, 40); 63 | [_deviceSwitchButton addTarget:self action:@selector(cameraDeviceClick:) forControlEvents:UIControlEventTouchUpInside]; 64 | [self addSubview:_deviceSwitchButton]; 65 | 66 | // 前置摄像头是否可以闪光 67 | if (![UIImagePickerController isFlashAvailableForCameraDevice:UIImagePickerControllerCameraDeviceRear]) { 68 | _flashSwitchButton.hidden = true; 69 | } 70 | // 判断是否支持前置摄像头 71 | if (![UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]) { 72 | _deviceSwitchButton.hidden = true; 73 | } 74 | 75 | // bottom 76 | _bottomContentView = [[UIView alloc] init]; 77 | _bottomContentView.translatesAutoresizingMaskIntoConstraints = false; 78 | _bottomContentView.backgroundColor = [UIColor blackColor]; 79 | [self addSubview:_bottomContentView]; 80 | 81 | _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 82 | _closeButton.translatesAutoresizingMaskIntoConstraints = false; 83 | _closeButton.frame = CGRectMake(0, _bottomContentView.frame.size.height-64, 120, 64); 84 | [_closeButton setTitle:@"取消" forState:UIControlStateNormal]; 85 | [_closeButton addTarget:self action:@selector(cancelButtonClick:) forControlEvents:UIControlEventTouchUpInside]; 86 | [_bottomContentView addSubview:_closeButton]; 87 | 88 | _shutter = [UIButton buttonWithType:UIButtonTypeCustom]; 89 | _shutter.translatesAutoresizingMaskIntoConstraints = false; 90 | _shutter.frame = CGRectMake((_bottomContentView.frame.size.width-64)/2.0, _bottomContentView.frame.size.height-64, 64, 64); 91 | [_shutter setImage:[UIImage xy_imageWithName:@"cl_photo_picker_shutter"] forState:UIControlStateNormal]; 92 | [_shutter setImage:[UIImage xy_imageWithName:@"cl_photo_picker_shutter_h"] forState:UIControlStateHighlighted]; 93 | [_shutter addTarget:self action:@selector(shutterClick:) forControlEvents:UIControlEventTouchUpInside]; 94 | [_bottomContentView addSubview:_shutter]; 95 | 96 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 97 | _doneButton.translatesAutoresizingMaskIntoConstraints = false; 98 | _doneButton.frame = CGRectMake(_bottomContentView.frame.size.width-120, _bottomContentView.frame.size.height-64, 120, 64); 99 | [_doneButton setTitle:@"完成" forState:UIControlStateNormal]; 100 | [_doneButton addTarget:self action:@selector(finishedButtonClick:) forControlEvents:UIControlEventTouchUpInside]; 101 | [_bottomContentView addSubview:_doneButton]; 102 | 103 | 104 | // UIEdgeInsets insets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; 105 | [NSLayoutConstraint activateConstraints:@[ 106 | [_topBar.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], 107 | [_topBar.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], 108 | [_topBar.topAnchor constraintEqualToAnchor:self.topAnchor], 109 | [_topBar.bottomAnchor constraintEqualToAnchor:_flashSwitchButton.bottomAnchor], 110 | 111 | [_flashSwitchButton.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], 112 | [_flashSwitchButton.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor], 113 | [_flashSwitchButton.heightAnchor constraintEqualToConstant:40], 114 | [_flashSwitchButton.widthAnchor constraintEqualToConstant:80], 115 | 116 | [_deviceSwitchButton.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor], 117 | [_deviceSwitchButton.topAnchor constraintEqualToAnchor:_flashSwitchButton.topAnchor], 118 | [_deviceSwitchButton.heightAnchor constraintEqualToConstant:40], 119 | [_deviceSwitchButton.widthAnchor constraintEqualToConstant:60], 120 | 121 | [_bottomContentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], 122 | [_bottomContentView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], 123 | [_bottomContentView.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor], 124 | [_bottomContentView.heightAnchor constraintEqualToConstant:140], 125 | 126 | [_closeButton.leadingAnchor constraintEqualToAnchor:_bottomContentView.leadingAnchor], 127 | [_closeButton.bottomAnchor constraintEqualToAnchor:_bottomContentView.bottomAnchor], 128 | [_closeButton.widthAnchor constraintEqualToConstant:120], 129 | [_closeButton.heightAnchor constraintEqualToConstant:64], 130 | 131 | [_shutter.centerXAnchor constraintEqualToAnchor:_bottomContentView.centerXAnchor], 132 | [_shutter.bottomAnchor constraintEqualToAnchor:_bottomContentView.bottomAnchor], 133 | [_shutter.widthAnchor constraintEqualToConstant:64], 134 | [_shutter.heightAnchor constraintEqualToConstant:64], 135 | 136 | [_doneButton.trailingAnchor constraintEqualToAnchor:_bottomContentView.trailingAnchor], 137 | [_doneButton.bottomAnchor constraintEqualToAnchor:_bottomContentView.bottomAnchor], 138 | [_doneButton.widthAnchor constraintEqualToConstant:120], 139 | [_doneButton.heightAnchor constraintEqualToConstant:64], 140 | ]]; 141 | 142 | dispatch_block_t block = ^{ 143 | self.bottomPreview = [[XYPhotoSelectedAssetPreviewView alloc] init]; 144 | self.bottomPreview.translatesAutoresizingMaskIntoConstraints = false; 145 | self.bottomPreview.hideControls = true; 146 | self.bottomPreview.backgroundColor = [UIColor blackColor]; 147 | self.bottomPreview.delegate = self.delegate; 148 | [self.bottomContentView addSubview:self.bottomPreview]; 149 | 150 | self.selectedScriptLabel = [[UILabel alloc] init]; 151 | self.selectedScriptLabel.translatesAutoresizingMaskIntoConstraints = false; 152 | if (@available(iOS 13.0, *)) { 153 | self.selectedScriptLabel.textColor = [UIColor systemBackgroundColor]; 154 | } else { 155 | self.selectedScriptLabel.textColor = [UIColor whiteColor]; 156 | } 157 | self.selectedScriptLabel.font = [UIFont systemFontOfSize:14]; 158 | self.selectedScriptLabel.textAlignment = NSTextAlignmentRight; 159 | [self.bottomContentView addSubview:self.selectedScriptLabel]; 160 | 161 | [NSLayoutConstraint activateConstraints:@[ 162 | [self.bottomPreview.trailingAnchor constraintEqualToAnchor:self.bottomContentView.trailingAnchor], 163 | [self.bottomPreview.leadingAnchor constraintEqualToAnchor:self.bottomContentView.leadingAnchor], 164 | [self.bottomPreview.topAnchor constraintEqualToAnchor:self.bottomContentView.topAnchor], 165 | [self.bottomPreview.heightAnchor constraintEqualToConstant:60], 166 | 167 | [self.selectedScriptLabel.topAnchor constraintEqualToAnchor:self.bottomPreview.bottomAnchor], 168 | [self.selectedScriptLabel.trailingAnchor constraintEqualToAnchor:self.bottomContentView.trailingAnchor constant:-8] 169 | ]]; 170 | [self selectedAssetsChanged]; 171 | }; 172 | 173 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 174 | dispatch_async(dispatch_get_main_queue(), ^{ 175 | if (status == PHAuthorizationStatusAuthorized) { 176 | block(); 177 | } else { 178 | [UIAlertController xy_showAlertPhotoSettingIfUnauthorized]; 179 | } 180 | }); 181 | }]; 182 | } 183 | 184 | - (void)didMoveToWindow 185 | { 186 | [super didMoveToWindow]; 187 | _bottomPreview.assetArray = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 188 | } 189 | 190 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 191 | { 192 | UIView *view = [super hitTest:point withEvent:event]; 193 | return view==self ? nil:view; 194 | } 195 | 196 | #pragma mark - set 197 | 198 | - (void)setFlashMode:(UIImagePickerControllerCameraFlashMode)flashMode 199 | { 200 | _flashMode = flashMode; 201 | switch (_flashMode) { 202 | case UIImagePickerControllerCameraFlashModeAuto: { 203 | [_flashSwitchButton setTitle:@"自动" forState:UIControlStateNormal]; 204 | } 205 | break; 206 | case UIImagePickerControllerCameraFlashModeOn: { 207 | [_flashSwitchButton setTitle:@"打开" forState:UIControlStateNormal]; 208 | } 209 | break; 210 | case UIImagePickerControllerCameraFlashModeOff: { 211 | [_flashSwitchButton setTitle:@"关闭" forState:UIControlStateNormal]; 212 | } 213 | break; 214 | default: 215 | break; 216 | } 217 | } 218 | 219 | - (void)setCameraDeivce:(UIImagePickerControllerCameraDevice)cameraDeivce 220 | { 221 | _cameraDeivce = cameraDeivce; 222 | _flashSwitchButton.hidden = ![UIImagePickerController isFlashAvailableForCameraDevice:_cameraDeivce]; 223 | } 224 | 225 | - (void)setDelegate:(id)delegate 226 | { 227 | _delegate = delegate; 228 | _bottomPreview.delegate = delegate; 229 | } 230 | 231 | #pragma mark - actions 232 | 233 | - (void)flashButtonClick:(UIButton *)sender 234 | { 235 | if ([_delegate respondsToSelector:@selector(cameraOverlayView:didSwitchedFlashMode:)] && [UIAlertController xy_showAlertCameraSettingIfUnauthorized]) { 236 | 237 | switch (_flashMode) { 238 | case UIImagePickerControllerCameraFlashModeAuto: { 239 | self.flashMode = UIImagePickerControllerCameraFlashModeOn; 240 | } 241 | break; 242 | case UIImagePickerControllerCameraFlashModeOn: { 243 | self.flashMode = UIImagePickerControllerCameraFlashModeOff; 244 | } 245 | break; 246 | case UIImagePickerControllerCameraFlashModeOff: { 247 | self.flashMode = UIImagePickerControllerCameraFlashModeAuto; 248 | } 249 | break; 250 | default: 251 | break; 252 | } 253 | [_delegate cameraOverlayView:self didSwitchedFlashMode:_flashMode]; 254 | } 255 | } 256 | 257 | - (void)cameraDeviceClick:(UIButton *)sender 258 | { 259 | if ([_delegate respondsToSelector:@selector(cameraOverlayView:didSwitchedCameraDeivce:)] && [UIAlertController xy_showAlertCameraSettingIfUnauthorized]) { 260 | self.cameraDeivce = _cameraDeivce == UIImagePickerControllerCameraDeviceRear ? UIImagePickerControllerCameraDeviceFront : UIImagePickerControllerCameraDeviceRear; 261 | self.flashSwitchButton.hidden = ![UIImagePickerController isFlashAvailableForCameraDevice:_cameraDeivce]; 262 | [_delegate cameraOverlayView:self didSwitchedCameraDeivce:_cameraDeivce]; 263 | } 264 | } 265 | 266 | - (void)cancelButtonClick:(UIButton *)sender 267 | { 268 | if ([_delegate respondsToSelector:@selector(canceledCameraOverlayView:)]) { 269 | [_delegate canceledCameraOverlayView:self]; 270 | } 271 | } 272 | 273 | - (void)finishedButtonClick:(UIButton *)sender 274 | { 275 | if ([_delegate respondsToSelector:@selector(finishedCameraOverlayView:)]) { 276 | [_delegate finishedCameraOverlayView:self]; 277 | } 278 | } 279 | 280 | - (void)shutterClick:(UIButton *)sender 281 | { 282 | if ([_delegate respondsToSelector:@selector(cameraOverlayView:didShutted:)] && [UIAlertController xy_showAlertCameraSettingIfUnauthorized]) { 283 | [_delegate cameraOverlayView:self didShutted:sender]; 284 | } 285 | } 286 | 287 | 288 | #pragma mark - Notification 289 | 290 | - (void)selectedAssetsChanged 291 | { 292 | NSArray *assets = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 293 | NSInteger max = (NSInteger)[XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets; 294 | if (max==0) { 295 | _selectedScriptLabel.text = @(assets.count).stringValue; 296 | } else { 297 | _selectedScriptLabel.text = [NSString stringWithFormat:@"(%zd / %zd)",assets.count,max]; 298 | } 299 | } 300 | 301 | 302 | @end 303 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionViewCell.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface XYPhotoCollectionViewCell : UICollectionViewCell 15 | 16 | - (void)setAsset:(PHAsset *)asset atIndexPath:(NSIndexPath *)indexPath inCollectionView:(UICollectionView *)collectionView withCacheManager:(PHCachingImageManager *)cacheManager; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionViewCell.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 12/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoCollectionViewCell.h" 10 | #import "XYPhotoDataCategory.h" 11 | #import "XYPhotoKitHelper.h" 12 | #import "XYPhotoSelectedAssetManager.h" 13 | 14 | @interface XYPhotoCollectionViewCell () 15 | 16 | @property (nonatomic) PHImageRequestID imageRequestId; 17 | 18 | @property (nonatomic, strong) PHAsset *asset; 19 | 20 | @property (nonatomic, strong) UIImageView *imageView; 21 | 22 | // 是否选中 23 | @property (nonatomic, strong) UIButton *markIconButon; 24 | 25 | @end 26 | 27 | @implementation XYPhotoCollectionViewCell 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame 30 | { 31 | if (self = [super initWithFrame:frame]) { 32 | _imageView = [[UIImageView alloc] init]; 33 | _imageView.translatesAutoresizingMaskIntoConstraints = false; 34 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 35 | _imageView.clipsToBounds = true; 36 | [self.contentView addSubview:_imageView]; 37 | 38 | _markIconButon = [UIButton buttonWithType:UIButtonTypeCustom]; 39 | _markIconButon.translatesAutoresizingMaskIntoConstraints = false; 40 | [_markIconButon setImage:[UIImage xy_imageWithName:@"cl_photo_picker_unpicked"] forState:UIControlStateNormal]; 41 | [_markIconButon setImage:[UIImage xy_imageWithName:@"cl_photo_picker_picked"] forState:UIControlStateSelected]; 42 | _markIconButon.imageView.contentMode = UIViewContentModeCenter; 43 | _markIconButon.selected = false; 44 | [_markIconButon addTarget:self action:@selector(markButtonClick) forControlEvents:UIControlEventTouchUpInside]; 45 | [self.contentView addSubview:_markIconButon]; 46 | 47 | _markIconButon.hidden = [XYPhotoSelectedAssetManager sharedManager].maxNumberOfAssets == 1; 48 | 49 | [NSLayoutConstraint activateConstraints:@[ 50 | [_imageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], 51 | [_imageView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], 52 | [_imageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], 53 | [_imageView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor], 54 | 55 | [_markIconButon.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], 56 | [_markIconButon.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], 57 | [_markIconButon.heightAnchor constraintEqualToConstant:35], 58 | [_markIconButon.widthAnchor constraintEqualToConstant:35], 59 | ]]; 60 | } 61 | return self; 62 | } 63 | 64 | - (void)prepareForReuse 65 | { 66 | [super prepareForReuse]; 67 | _markIconButon.selected = false; 68 | self.imageView.image = nil; 69 | } 70 | 71 | - (void)setAsset:(PHAsset *)asset atIndexPath:(NSIndexPath *)indexPath inCollectionView:(UICollectionView *)collectionView withCacheManager:(PHCachingImageManager *)cacheManager 72 | { 73 | _asset = asset; 74 | 75 | _markIconButon.selected = [[XYPhotoSelectedAssetManager sharedManager].selectedAssets containsObject:asset]; 76 | 77 | CGSize itemSize = [(UICollectionViewFlowLayout *)collectionView.collectionViewLayout itemSize]; 78 | 79 | // 防止复用时加载延时图片展示多次 80 | if (_imageRequestId!=0) { 81 | [cacheManager cancelImageRequest:_imageRequestId]; 82 | } 83 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 84 | // options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat; 85 | options.networkAccessAllowed = [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud; 86 | _imageRequestId = [cacheManager requestImageForAsset:asset 87 | targetSize:CGSizeScale(itemSize, [UIScreen mainScreen].scale) 88 | contentMode:PHImageContentModeAspectFill 89 | options:options 90 | resultHandler:^(UIImage *result, NSDictionary *info) { 91 | // NSLog(@"%@ %@ %d", result, info, self.imageRequestId); 92 | // PHImageRequestID requestID = [info[@"PHImageResultRequestIDKey"] intValue]; 93 | // if (result && requestID == self.imageRequestId) { 94 | // self.imageView.image = result; 95 | // } 96 | if (result) { self.imageView.image = result; } 97 | }]; 98 | } 99 | 100 | - (void)markButtonClick 101 | { 102 | if (_markIconButon.selected) { 103 | [[XYPhotoSelectedAssetManager sharedManager] removeSelectedAsset:_asset]; 104 | _markIconButon.selected = false; 105 | } else if ([[XYPhotoSelectedAssetManager sharedManager] addSelectedAsset:_asset]) { 106 | _markIconButon.selected = true; 107 | } 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoHorizontalScrollItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoHorizontalScrollItemView.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 07/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoHorizontalScrollView.h" 10 | 11 | @class XYPhotoHorizontalScrollItemView; 12 | @protocol XYPhotoHorizontalScrollItemViewDelegate 13 | @optional 14 | - (void)didTapped:(XYPhotoHorizontalScrollItemView *)scrollItemView; 15 | @end 16 | 17 | @import Photos; 18 | @interface XYPhotoHorizontalScrollItemView : UIScrollView 19 | 20 | @property (nonatomic) int index; 21 | @property (nonatomic, strong) PHAsset *asset; 22 | @property (nonatomic, strong) UIImageView *imageView; 23 | @property (nonatomic, weak) id photokitDelegate; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoHorizontalScrollItemView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoHorizontalScrollItemView.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 07/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoHorizontalScrollItemView.h" 10 | #import "XYPhotoKitHelper.h" 11 | #import "XYPhotoSelectedAssetManager.h" 12 | #import "XYPhotoDataCategory.h" 13 | 14 | @import AVKit; 15 | @interface XYPhotoHorizontalScrollItemView() 16 | 17 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 18 | @property (nonatomic, strong) UIButton *playVideoButton; 19 | @property (nonatomic, assign) PHImageRequestID imageRequestId; 20 | 21 | @end 22 | 23 | @implementation XYPhotoHorizontalScrollItemView 24 | 25 | - (instancetype)initWithFrame:(CGRect)frame 26 | { 27 | self = [super initWithFrame:frame]; 28 | if (self) { 29 | self.delegate = self; 30 | self.minimumZoomScale = 1.f; 31 | self.maximumZoomScale = 2.f; 32 | self.showsHorizontalScrollIndicator = false; 33 | self.showsVerticalScrollIndicator = false; 34 | self.decelerationRate = UIScrollViewDecelerationRateFast; 35 | self.backgroundColor = [UIColor blackColor]; 36 | 37 | _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 0, self.frame.size.width-10, self.frame.size.height)]; 38 | _imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 39 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 40 | _imageView.backgroundColor = [UIColor blackColor]; 41 | [self addSubview:_imageView]; 42 | 43 | _playVideoButton = [UIButton buttonWithType:UIButtonTypeCustom]; 44 | [_playVideoButton setImage:[UIImage xy_imageWithName:@"cl_photokit_play"] forState:UIControlStateNormal]; 45 | _playVideoButton.frame = CGRectMake((self.frame.size.width-100)/2.f, (self.frame.size.height-100)/2.f, 100, 100); 46 | _playVideoButton.hidden = true; 47 | [_playVideoButton addTarget:self action:@selector(playAction) forControlEvents:UIControlEventTouchUpInside]; 48 | [self addSubview:_playVideoButton]; 49 | 50 | _imageManager = [[PHCachingImageManager alloc] init]; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 56 | { 57 | [super touchesEnded:touches withEvent:event]; 58 | UITouch *touch = [touches anyObject]; 59 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(viewWasTapped) object:nil]; 60 | if (touch.tapCount==1) { 61 | [self performSelector:@selector(viewWasTapped) withObject:nil afterDelay:0.2f]; 62 | } else if (touch.tapCount==2) { 63 | [self viewWasDoubleTapped:touch]; 64 | } 65 | } 66 | 67 | - (void)setAsset:(PHAsset *)asset 68 | { 69 | _asset = asset; 70 | 71 | // 防止复用时加载延时图片展示多次 72 | if (_imageRequestId!=0) { 73 | [self.imageManager cancelImageRequest:_imageRequestId]; 74 | } 75 | 76 | CGSize size = [UIScreen mainScreen].bounds.size; 77 | CGFloat scale = [UIScreen mainScreen].scale; 78 | 79 | if (asset.mediaType == PHAssetMediaTypeVideo) { 80 | self.maximumZoomScale = 1; 81 | } else if (asset.mediaType == PHAssetMediaTypeImage) { 82 | self.maximumZoomScale = MAX(asset.pixelWidth/(scale*size.width), asset.pixelHeight/(scale*size.height)); 83 | self.maximumZoomScale = MAX(self.maximumZoomScale, 2); 84 | } 85 | self.playVideoButton.hidden = asset.mediaType != PHAssetMediaTypeVideo; 86 | 87 | // 允许网络请求icould 88 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 89 | options.networkAccessAllowed = [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud; 90 | 91 | _imageRequestId = [self.imageManager requestImageForAsset:asset 92 | targetSize:CGSizeMake(asset.pixelWidth, asset.pixelHeight) 93 | contentMode:PHImageContentModeAspectFit 94 | options:options 95 | resultHandler:^(UIImage *result, NSDictionary *info) { 96 | BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]; 97 | if (downloadFinined) { 98 | self.imageRequestId = 0; 99 | } 100 | if (result) { self.imageView.image = result; } 101 | }]; 102 | } 103 | 104 | #pragma mark - actions 105 | 106 | - (void)viewWasTapped 107 | { 108 | if ([_photokitDelegate respondsToSelector:@selector(didTapped:)]) { 109 | [_photokitDelegate didTapped:self]; 110 | } 111 | } 112 | 113 | - (void)viewWasDoubleTapped:(UITouch *)touch 114 | { 115 | if (_asset.mediaType == PHAssetMediaTypeImage) { 116 | [self zoomToLocation:[touch locationInView:self]]; 117 | } 118 | } 119 | 120 | - (void)playAction 121 | { 122 | if (_asset.mediaType != PHAssetMediaTypeVideo) { 123 | return; 124 | } 125 | 126 | PHAsset *videoAsset = _asset; 127 | PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; 128 | options.networkAccessAllowed = [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud; 129 | options.deliveryMode = PHVideoRequestOptionsDeliveryModeFastFormat; 130 | 131 | [[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) { 132 | dispatch_async(dispatch_get_main_queue(), ^{ 133 | AVPlayer *player = [AVPlayer playerWithPlayerItem:[AVPlayerItem playerItemWithAsset:asset]]; 134 | AVPlayerViewController *playerViewController = [AVPlayerViewController new]; 135 | playerViewController.player = player; 136 | [[UIApplication sharedApplication].xy_topViewController presentViewController:playerViewController animated:NO completion:^{ 137 | [player play]; 138 | }]; 139 | }); 140 | }]; 141 | } 142 | 143 | #pragma mark - private 144 | 145 | - (void)zoomToLocation:(CGPoint)location 146 | { 147 | float newScale; 148 | CGRect zoomRect; 149 | if ([self isZoomed]) { 150 | zoomRect = self.bounds; 151 | } else { 152 | newScale = [self maximumZoomScale]; 153 | zoomRect = [self zoomRectForScale:newScale withCenter:location]; 154 | } 155 | [self zoomToRect:zoomRect animated:YES]; 156 | } 157 | 158 | - (BOOL)isZoomed 159 | { 160 | return !([self zoomScale] == [self minimumZoomScale]); 161 | } 162 | 163 | - (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center 164 | { 165 | CGRect zoomRect; 166 | zoomRect.size.height = _imageView.frame.size.height / scale; 167 | zoomRect.size.width = _imageView.frame.size.width / scale; 168 | zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0); 169 | zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0)-30; 170 | 171 | return zoomRect; 172 | } 173 | 174 | #pragma mark - UIScrollViewDelegate 175 | 176 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 177 | { 178 | if (_imageView.image) { 179 | return _imageView; 180 | } 181 | return nil; 182 | } 183 | 184 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView 185 | { 186 | CGSize size = _imageView.frame.size; 187 | CGFloat top = floor((MAX(scrollView.contentSize.height, scrollView.frame.size.height)-_imageView.frame.size.height)/2); 188 | CGFloat left = floor((MAX(scrollView.contentSize.width, scrollView.frame.size.width)-_imageView.frame.size.width)/2); 189 | _imageView.frame = CGRectMake(top, left, size.width, size.height); 190 | } 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoHorizontalScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoHorizontalScrollView.h 3 | // YHHB 4 | // 5 | // Created by Hunter Huang on 11/30/11. 6 | // Copyright (c) 2011 vge design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol XYPhotoHorizontalScrollViewDataSource; 12 | @protocol XYPhotoHorizontalScrollViewDelegate; 13 | @class XYHorizontalScrollItemView; 14 | 15 | @protocol XYHorizontalScrollItemInterface 16 | 17 | @property (nonatomic) int index; 18 | 19 | @end 20 | 21 | @interface XYPhotoHorizontalScrollView : UIScrollView 22 | 23 | @property (nonatomic, weak) id horizontalDataSource; 24 | @property (nonatomic, weak) id horizontalDelegate; 25 | @property (nonatomic, assign) NSInteger currentIndex; 26 | @property (nonatomic, assign) CGFloat viewPadding; 27 | 28 | - (id)dequeueReusableItemView; 29 | 30 | - (void)clearMemory; 31 | - (void)reloadData; 32 | 33 | - (void)resizeToFrame:(CGRect)rect animated:(BOOL)animated duration:(float)duration; 34 | 35 | - (void)setCurrentIndex:(NSInteger)index animated:(BOOL)animated; 36 | - (void)setCurrentIndex:(NSInteger)index animated:(BOOL)animated event:(BOOL)event; //event 表示是否最终调用 horizontalScrollView:didSelectIndex: 37 | 38 | - (id)currentItemView; 39 | 40 | @end 41 | 42 | @protocol XYPhotoHorizontalScrollViewDataSource 43 | 44 | - (NSInteger)numberOfItems; 45 | - (id)horizontalScrollView:(XYPhotoHorizontalScrollView *)scroller itemViewForIndex:(NSInteger)index; 46 | 47 | @end 48 | 49 | @protocol XYPhotoHorizontalScrollViewDelegate 50 | 51 | @optional 52 | 53 | - (void)horizontalScrollView:(XYPhotoHorizontalScrollView *)scroller willSelectIndex:(NSInteger)index; 54 | - (void)horizontalScrollView:(XYPhotoHorizontalScrollView *)scroller didSelectIndex:(NSInteger)index; 55 | 56 | - (void)horizontalScrollViewWillBeginDragging:(XYPhotoHorizontalScrollView *)scroller; //类比 scrollViewWillBeginDragging: 57 | - (void)horizontalScrollViewDidEndDragging:(XYPhotoHorizontalScrollView *)scroller; //类比 scrollViewDidEndDragging:willDecelerate: 58 | - (void)horizontalScrollViewDidScroll:(XYPhotoHorizontalScrollView *)scroller; //类比 scrollViewDidScroll: 59 | 60 | @end 61 | 62 | @interface XYHorizontalScrollItemView : UIView 63 | 64 | @property (nonatomic) int index; 65 | 66 | - (void)viewWillResize; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoHorizontalScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoHorizontalScrollView.m 3 | // YHHB 4 | // 5 | // Created by Hunter Huang on 11/30/11. 6 | // Copyright (c) 2011 vge design. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoHorizontalScrollView.h" 10 | 11 | static const NSInteger preloadanswerNum = 1; 12 | 13 | @interface XYPhotoHorizontalScrollView() 14 | 15 | @property (nonatomic, strong) NSMutableSet *reusableViews; 16 | @property (nonatomic, assign) NSInteger firstVisibleIndex; 17 | @property (nonatomic, assign) NSInteger lastVisibleIndex; 18 | 19 | @end 20 | 21 | @interface UINavigationController () 22 | 23 | - (BOOL)horizontalScrollView:(UIScrollView *)scrollView gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; 24 | 25 | @end 26 | 27 | @implementation XYPhotoHorizontalScrollView 28 | 29 | @synthesize horizontalDelegate, horizontalDataSource, reusableViews, currentIndex; 30 | @synthesize firstVisibleIndex, lastVisibleIndex, viewPadding; 31 | 32 | - (id)initWithFrame:(CGRect)frame { 33 | self = [super initWithFrame:frame]; 34 | if (self) { 35 | // Initialization code 36 | self.backgroundColor = [UIColor clearColor]; 37 | self.pagingEnabled = true; 38 | self.scrollsToTop = false; 39 | self.showsVerticalScrollIndicator = false; 40 | self.showsHorizontalScrollIndicator = false; 41 | self.reusableViews = [NSMutableSet set]; 42 | self.delegate = self; 43 | 44 | firstVisibleIndex = NSIntegerMax; 45 | lastVisibleIndex = NSIntegerMin; 46 | } 47 | return self; 48 | } 49 | 50 | #pragma mark - public 51 | 52 | //强制固定contentInset为zero 53 | - (void)setContentInset:(UIEdgeInsets)contentInset 54 | { 55 | [super setContentInset:UIEdgeInsetsZero]; 56 | } 57 | 58 | //强制固定contentOffset为zero 59 | - (void)setContentOffset:(CGPoint)contentOffset 60 | { 61 | [super setContentOffset:CGPointMake(contentOffset.x, 0)]; 62 | } 63 | 64 | - (void)layoutSubviews 65 | { 66 | [super layoutSubviews]; 67 | 68 | CGRect visibleBounds = [self bounds]; 69 | CGFloat visibleWidth = visibleBounds.size.width; 70 | CGFloat visibleHeight = visibleBounds.size.height; 71 | 72 | NSInteger answerNum = horizontalDataSource?[horizontalDataSource numberOfItems]:0; 73 | self.contentSize = CGSizeMake(answerNum*visibleWidth, visibleHeight); 74 | 75 | CGFloat leftDelta = visibleBounds.origin.x-visibleWidth*preloadanswerNum; 76 | CGFloat extendedVisibleWidth = (preloadanswerNum*2+1)*visibleWidth; 77 | if (leftDelta < 0) { 78 | extendedVisibleWidth += leftDelta; 79 | } 80 | CGRect extendedVisibleBounds = CGRectMake(MAX(0, visibleBounds.origin.x-preloadanswerNum*visibleWidth), 0, extendedVisibleWidth, visibleHeight); 81 | // Recycle all views that are no longer visible 82 | for (UIView *view in [self subviews]) { 83 | if ([view isKindOfClass:[XYHorizontalScrollItemView class]]||[view conformsToProtocol:@protocol(XYHorizontalScrollItemInterface)]) { 84 | CGRect viewFrame = [view frame]; 85 | // If the view doesn't intersect, it's not visible, so we can recycle it 86 | if (! CGRectIntersectsRect(viewFrame, extendedVisibleBounds)) { 87 | [view removeFromSuperview]; 88 | if (reusableViews.count<5) { 89 | [reusableViews addObject:view]; 90 | } 91 | } 92 | } 93 | } 94 | 95 | int startIndex = MAX(0, floorf((extendedVisibleBounds.origin.x)/visibleWidth)); 96 | int endIndex = MIN(answerNum, ceilf((extendedVisibleBounds.origin.x+extendedVisibleBounds.size.width)/visibleWidth));// 预加载2张 97 | 98 | for (int index = startIndex; index < endIndex; index++) { 99 | BOOL isItemViewMissing = index < firstVisibleIndex || index >= lastVisibleIndex; 100 | if (isItemViewMissing) { 101 | id itemView = nil; 102 | if (horizontalDataSource) { 103 | itemView = [horizontalDataSource horizontalScrollView:self itemViewForIndex:index]; 104 | } 105 | // Set the frame so the view is inserted into the correct position. 106 | itemView.index = index; 107 | [(UIView *)itemView setTag:index+1000]; 108 | [(UIView *)itemView setFrame:CGRectMake(index*visibleWidth+viewPadding, 0, visibleWidth-viewPadding*2, visibleHeight)]; 109 | [self addSubview:(UIView *)itemView]; 110 | } 111 | } 112 | 113 | // Remember which thumb view indexes are visible. 114 | firstVisibleIndex = startIndex; 115 | lastVisibleIndex = endIndex; 116 | } 117 | 118 | 119 | #pragma mark - public 复用 120 | 121 | - (id)dequeueReusableItemView { 122 | XYHorizontalScrollItemView *view = [reusableViews anyObject]; 123 | if (view) { 124 | [reusableViews removeObject:view]; 125 | } 126 | return view; 127 | } 128 | 129 | //清空复用 130 | - (void)clearMemory 131 | { 132 | [reusableViews removeAllObjects]; 133 | reusableViews = [NSMutableSet set]; 134 | } 135 | 136 | - (void)reloadData 137 | { 138 | [self enqueueReusableItemViews]; 139 | // force to call layoutSubviews 140 | [self layoutSubviews]; 141 | } 142 | 143 | - (void)resizeToFrame:(CGRect)rect animated:(BOOL)animated duration:(float)duration 144 | { 145 | if (animated) { 146 | [UIView animateWithDuration:duration animations:^{ 147 | self.frame = rect; 148 | }]; 149 | } else { 150 | self.frame = rect; 151 | } 152 | 153 | self.contentOffset = CGPointMake(currentIndex*self.frame.size.width, 0); 154 | 155 | [UIView animateWithDuration:animated?duration:0 animations:^{ 156 | for (NSInteger i=0, c=[self.subviews count]; ihorizontalDelegate respondsToSelector:@selector(horizontalScrollView:didSelectIndex:)]) { 215 | [self->horizontalDelegate horizontalScrollView:self didSelectIndex:self->currentIndex]; 216 | } 217 | } 218 | }]; 219 | } else { 220 | [self scrollRectToVisible:rect animated:NO]; 221 | if (event) { 222 | if ([horizontalDelegate respondsToSelector:@selector(horizontalScrollView:didSelectIndex:)]) { 223 | [horizontalDelegate horizontalScrollView:self didSelectIndex:currentIndex]; 224 | } 225 | } 226 | } 227 | 228 | if (!animated) { 229 | [self layoutSubviews]; 230 | } 231 | 232 | } 233 | 234 | - (id)currentItemView 235 | { 236 | return [self findViewWithTag:1000+currentIndex]; 237 | } 238 | 239 | #pragma mark - private 240 | 241 | - (void)enqueueReusableItemViews { 242 | for (UIView *view in [self subviews]) { 243 | if ([view isKindOfClass:[XYHorizontalScrollItemView class]]||[view conformsToProtocol:@protocol(XYHorizontalScrollItemInterface)]) { 244 | [view removeFromSuperview]; 245 | if (reusableViews.count<5) { 246 | [reusableViews addObject:view]; 247 | } 248 | } 249 | } 250 | 251 | firstVisibleIndex = NSIntegerMax; 252 | lastVisibleIndex = NSIntegerMin; 253 | } 254 | 255 | 256 | - (id)findViewWithTag:(NSUInteger)tag 257 | { 258 | for (UIView *sub in self.subviews) { 259 | if (([sub isKindOfClass:[XYHorizontalScrollItemView class]]||[sub conformsToProtocol:@protocol(XYHorizontalScrollItemInterface)]) && sub.tag==tag) { 260 | return (id)sub; 261 | } 262 | } 263 | return nil; 264 | } 265 | 266 | - (void)updateCurrentIndex 267 | { 268 | int index = floorf((self.contentOffset.x+self.frame.size.width/2)/self.frame.size.width); 269 | if (index != currentIndex && index >= 0 && index<(horizontalDataSource?[horizontalDataSource numberOfItems]:0)) { 270 | currentIndex = index; 271 | if ([horizontalDelegate respondsToSelector:@selector(horizontalScrollView:willSelectIndex:)]) { 272 | [horizontalDelegate horizontalScrollView:self willSelectIndex:currentIndex]; 273 | } 274 | } 275 | } 276 | 277 | #pragma mark - UIScrollViewDelegate 278 | 279 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 280 | { 281 | if (scrollView.isDragging) { 282 | [self updateCurrentIndex]; 283 | } 284 | if ([self.horizontalDelegate respondsToSelector:@selector(horizontalScrollViewDidScroll:)]) { 285 | [self.horizontalDelegate horizontalScrollViewDidScroll:self]; 286 | } 287 | } 288 | 289 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 290 | { 291 | if (!decelerate) { 292 | [self updateCurrentIndex]; 293 | if ([self.horizontalDelegate respondsToSelector:@selector(horizontalScrollViewDidEndDragging:)]) { 294 | [self.horizontalDelegate horizontalScrollViewDidEndDragging:self]; 295 | } 296 | } 297 | } 298 | 299 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 300 | { 301 | [self updateCurrentIndex]; 302 | //停止滚动的代理 303 | if ([horizontalDelegate respondsToSelector:@selector(horizontalScrollView:didSelectIndex:)]) { 304 | [horizontalDelegate horizontalScrollView:self didSelectIndex:currentIndex]; 305 | } 306 | } 307 | 308 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 309 | { 310 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 311 | if ([self.horizontalDelegate respondsToSelector:@selector(horizontalScrollViewWillBeginDragging:)]) { 312 | [self.horizontalDelegate horizontalScrollViewWillBeginDragging:self]; 313 | } 314 | } 315 | 316 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 317 | { 318 | return NO; 319 | } 320 | 321 | @end 322 | 323 | @implementation XYHorizontalScrollItemView 324 | 325 | - (void)viewDidShow 326 | { 327 | 328 | } 329 | 330 | - (void)viewDidHidden 331 | { 332 | 333 | } 334 | 335 | - (void)viewWillResize 336 | { 337 | 338 | } 339 | 340 | @end 341 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoPreviewOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoPreviewOverlayView.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 06/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoSelectedAssetPreviewView.h" 10 | 11 | @class XYPhotoPreviewOverlayView; 12 | @protocol XYPhotoPreviewOverlayViewDelegate 13 | 14 | @required 15 | /** 16 | 关闭预览大图 17 | @param barbuttonItem closeItem 18 | */ 19 | - (void)previewOverlayView:(XYPhotoPreviewOverlayView *)view closeBarButtonItemClick:(UIBarButtonItem *)barbuttonItem; 20 | 21 | @end 22 | 23 | 24 | @interface XYPhotoPreviewOverlayView : UIView 25 | 26 | @property (nonatomic, weak) id delegate; 27 | 28 | /** 29 | 更新相关UI,如果当前asset的选中状态及滚动到当前asset对应的小图预览 30 | 31 | @param asset 当前asset需要更新 32 | */ 33 | - (void)updateSelectedAsset:(PHAsset *)asset; 34 | 35 | /** 36 | 更新头部标题 37 | 38 | @param index 当前展示的索引 39 | @param sum 全部asset个数 40 | */ 41 | - (void)updateTitleAtIndex:(NSInteger)index sum:(NSInteger)sum; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoPreviewOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoPreviewOverlayView.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 06/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoPreviewOverlayView.h" 10 | #import "XYPhotoSelectedAssetManager.h" 11 | #import "XYPhotoDataCategory.h" 12 | 13 | @interface XYPhotoPreviewOverlayView() 14 | { 15 | UIButton *_rightButton; 16 | UINavigationItem *_navBarItem; 17 | PHAsset *_asset; 18 | } 19 | @property (nonatomic, strong) UINavigationBar *navBar; 20 | @property (nonatomic, strong) XYPhotoSelectedAssetPreviewView *previewView; 21 | 22 | @end 23 | 24 | @implementation XYPhotoPreviewOverlayView 25 | 26 | - (instancetype)initWithFrame:(CGRect)frame 27 | { 28 | self = [super initWithFrame:frame]; 29 | if (self) { 30 | self.backgroundColor = [UIColor clearColor]; 31 | 32 | UIImageView *topView = [[UIImageView alloc] init]; 33 | topView.image = [UIImage xy_imageWithColor:[UIColor colorWithWhite:0 alpha:0.5]]; 34 | topView.translatesAutoresizingMaskIntoConstraints = false; 35 | [self addSubview:topView]; 36 | 37 | _rightButton = [UIButton buttonWithType:UIButtonTypeCustom]; 38 | [_rightButton setImage:[UIImage xy_imageWithName:@"cl_photo_picker_unpicked"] forState:UIControlStateNormal]; 39 | [_rightButton setImage:[UIImage xy_imageWithName:@"cl_photo_picker_picked"] forState:UIControlStateSelected]; 40 | [_rightButton addTarget:self action:@selector(selectedStateChanged) forControlEvents:UIControlEventTouchUpInside]; 41 | _rightButton.selected = false; 42 | 43 | _navBarItem = [[UINavigationItem alloc] initWithTitle:@"照片预览"]; 44 | _navBarItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:_rightButton]; 45 | _navBarItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:@selector(close:)]; 46 | 47 | _navBar = [[UINavigationBar alloc] init]; 48 | _navBar.translatesAutoresizingMaskIntoConstraints = false; 49 | _navBar.barTintColor = [UIColor whiteColor]; 50 | [_navBar setBackgroundImage:[UIImage xy_imageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault]; 51 | [_navBar setShadowImage:[UIImage xy_imageWithColor:[UIColor clearColor]]]; 52 | [_navBar setItems:@[_navBarItem]]; 53 | _navBar.tintColor = [UIColor whiteColor]; 54 | [_navBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}]; 55 | [self addSubview:_navBar]; 56 | 57 | _previewView = [[XYPhotoSelectedAssetPreviewView alloc] init]; 58 | _previewView.translatesAutoresizingMaskIntoConstraints = false; 59 | _previewView.delegate = _delegate; 60 | _previewView.backgroundColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.8]; 61 | [self addSubview:_previewView]; 62 | 63 | [NSLayoutConstraint activateConstraints:@[ 64 | [_navBar.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor], 65 | [_navBar.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor], 66 | [_navBar.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor], 67 | [_navBar.heightAnchor constraintEqualToConstant:44], 68 | [topView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], 69 | [topView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], 70 | [topView.topAnchor constraintEqualToAnchor:self.topAnchor], 71 | [topView.bottomAnchor constraintEqualToAnchor:_navBar.bottomAnchor], 72 | [_previewView.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor], 73 | [_previewView.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor], 74 | [_previewView.heightAnchor constraintEqualToConstant:60], 75 | [_previewView.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor], 76 | ]]; 77 | } 78 | return self; 79 | } 80 | 81 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 82 | { 83 | UIView *touchView = [super hitTest:point withEvent:event]; 84 | return touchView==self ? nil : touchView; 85 | } 86 | 87 | - (void)setDelegate:(id)delegate 88 | { 89 | _delegate = delegate; 90 | _previewView.delegate = delegate; 91 | } 92 | 93 | - (void)updateTitleAtIndex:(NSInteger)index sum:(NSInteger)sum 94 | { 95 | _navBarItem.title = [NSString stringWithFormat:@"%zd / %zd",index+1,sum]; 96 | NSMutableAttributedString *firstAtt = [[NSMutableAttributedString alloc] initWithString:@(index+1).stringValue 97 | attributes:@{ 98 | NSForegroundColorAttributeName:[UIColor colorWithWhite:0.9 alpha:1], 99 | NSFontAttributeName:[UIFont boldSystemFontOfSize:17] 100 | }]; 101 | NSAttributedString *secondAtt = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" / %zd",sum] 102 | attributes:@{ 103 | NSForegroundColorAttributeName:[UIColor colorWithWhite:0.9 alpha:1], 104 | NSFontAttributeName:[UIFont systemFontOfSize:14] 105 | }]; 106 | [firstAtt appendAttributedString:secondAtt]; 107 | 108 | UILabel *label = [[UILabel alloc] init]; 109 | label.attributedText = firstAtt; 110 | _navBarItem.titleView = label; 111 | } 112 | 113 | #pragma amrk - actions 114 | 115 | - (void)selectedStateChanged 116 | { 117 | if (_rightButton.selected) { 118 | [[XYPhotoSelectedAssetManager sharedManager] removeSelectedAsset:_asset]; 119 | _rightButton.selected = false; 120 | } else if ([[XYPhotoSelectedAssetManager sharedManager] addSelectedAsset:_asset]) { 121 | _rightButton.selected = true; 122 | } 123 | } 124 | 125 | - (void)close:(UIBarButtonItem *)item 126 | { 127 | if ([self.delegate respondsToSelector:@selector(previewOverlayView:closeBarButtonItemClick:)]) { 128 | [self.delegate previewOverlayView:self closeBarButtonItemClick:item]; 129 | } 130 | } 131 | 132 | #pragma mark - private 133 | 134 | - (void)updateSelectedAsset:(PHAsset *)asset 135 | { 136 | _asset = asset; 137 | _rightButton.selected = [[XYPhotoSelectedAssetManager sharedManager].selectedAssets containsObject:asset]; 138 | [_previewView scrollToAssetIfNeed:asset]; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoSelectedAssetPreviewView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoSelectedAssetPreviewView.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | @class XYPhotoSelectedAssetPreviewView; 13 | 14 | @protocol XYPhotoSelectedAssetPreviewViewDelegate 15 | 16 | /** 17 | preview 被点击回调 18 | 19 | @param previewView self 20 | @param index 被点击的asset索引位置 21 | @param asset 被点击的asset 22 | */ 23 | - (void)assetsSelectedPreviewView:(XYPhotoSelectedAssetPreviewView *)previewView didClickWithIndex:(NSInteger)index asset:(PHAsset *)asset; 24 | 25 | @end 26 | 27 | 28 | @interface XYPhotoSelectedAssetPreviewView : UIView 29 | 30 | /** 31 | 已选择的asset数组,并reload collectionView,监听系统的添加删除已选择的asset并做修改 32 | default: [XYPhotoSelectedAssetManager sharedManager].selectedAssets 33 | */ 34 | @property (nonatomic, strong) NSArray *assetArray; 35 | @property (nonatomic, weak) id delegate; 36 | 37 | /** 38 | 隐藏右侧完成按钮及徽标 39 | default:NO 40 | */ 41 | @property (nonatomic, assign) BOOL hideControls; 42 | 43 | /** 44 | 滚动到指定的asset,如果已选中的asset数组没有包含asset则不滚动 45 | 46 | @param asset 滚动到指定的照片asset 47 | */ 48 | - (void)scrollToAssetIfNeed:(PHAsset *)asset; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoSelectedAssetPreviewView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoSelectedAssetPreviewView..m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoSelectedAssetPreviewView.h" 10 | #import "XYPhotoCollectionFlowLayout.h" 11 | #import "XYPhotoSelectedAssetManager.h" 12 | #import "XYPhotoBottomPreviewCell.h" 13 | #import "XYPhotoKitHelper.h" 14 | #import "XYPhotoDataCategory.h" 15 | 16 | @interface XYPhotoSelectedAssetPreviewView () 17 | { 18 | NSInteger _currentIndex; 19 | } 20 | @property (nonatomic, strong) UICollectionView *collectionView; 21 | 22 | @property (nonatomic, strong) UIButton *doneButton; 23 | 24 | @property (nonatomic, strong) UILabel *badgeLabel; 25 | 26 | 27 | @property (nonatomic, strong) UIView *backView; 28 | @property (nonatomic, strong) NSLayoutConstraint *backViewBottomConstraint; 29 | @property (nonatomic, strong) NSLayoutConstraint *doneTrailConstraint; 30 | 31 | @end 32 | 33 | @implementation XYPhotoSelectedAssetPreviewView 34 | 35 | - (void)dealloc 36 | { 37 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 38 | } 39 | 40 | - (id)initWithFrame:(CGRect)frame 41 | { 42 | self = [super initWithFrame:frame]; 43 | if (self) { 44 | [self initBaseViews]; 45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedAssetsChanged) name:XYPhotoMultiImagePickerNotifications.assetsChanged object:nil]; 46 | // 用于初始化 47 | self.assetArray = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 48 | // 用于调整collection的cell布局(意义性不大,本可以忽略但是这边要保留,等同于`setAssetArray:`**不要去除这片脏代码**) 49 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 50 | self.assetArray = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 51 | self.doneButton.enabled = self.assetArray.count>0; 52 | self.badgeLabel.text = @(self.assetArray.count).stringValue; 53 | [self.collectionView reloadData]; 54 | }); 55 | } 56 | return self; 57 | } 58 | 59 | - (void)initBaseViews 60 | { 61 | self.backgroundColor = [UIColor clearColor]; 62 | self.clipsToBounds = false; 63 | self.layer.masksToBounds = false; 64 | 65 | _backView = [UIView new]; 66 | _backView.translatesAutoresizingMaskIntoConstraints = false; 67 | _backView.backgroundColor = [UIColor darkGrayColor]; 68 | [self addSubview:_backView]; 69 | 70 | // 预览已选择的assets 71 | XYPhotoCollectionFlowLayout *layout = [[XYPhotoCollectionFlowLayout alloc] init]; 72 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 73 | self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout]; 74 | self.collectionView.translatesAutoresizingMaskIntoConstraints = false; 75 | [self.collectionView registerClass:[XYPhotoBottomPreviewCell class] forCellWithReuseIdentifier:NSStringFromClass([XYPhotoBottomPreviewCell class])]; 76 | self.collectionView.backgroundColor = [UIColor clearColor]; 77 | self.collectionView.showsHorizontalScrollIndicator = false; 78 | self.collectionView.alwaysBounceHorizontal = true; 79 | self.collectionView.dataSource = self; 80 | self.collectionView.delegate = self; 81 | [self addSubview:self.collectionView]; 82 | 83 | // 完成按钮 84 | self.doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 85 | self.doneButton.translatesAutoresizingMaskIntoConstraints = false; 86 | [self.doneButton setBackgroundImage:[UIImage xy_imageWithColor:[UIColor systemBlueColor]] forState:UIControlStateNormal]; 87 | [self.doneButton setBackgroundImage:[UIImage xy_imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateDisabled]; 88 | self.doneButton.enabled = false; 89 | self.doneButton.layer.cornerRadius = 4; 90 | self.doneButton.clipsToBounds = true; 91 | self.doneButton.titleLabel.font = [UIFont systemFontOfSize:14]; 92 | [self.doneButton setTitle:@"完成" forState:UIControlStateNormal]; 93 | [self.doneButton addTarget:[XYPhotoSelectedAssetManager sharedManager].delegate action:@selector(doneSelectingAssets) forControlEvents:UIControlEventTouchUpInside]; 94 | [self addSubview:self.doneButton]; 95 | 96 | // 徽标 97 | self.badgeLabel = [[UILabel alloc] init]; 98 | self.badgeLabel.translatesAutoresizingMaskIntoConstraints = false; 99 | self.badgeLabel.text = @"0"; 100 | self.badgeLabel.backgroundColor = [UIColor whiteColor]; 101 | self.badgeLabel.textAlignment = NSTextAlignmentCenter; 102 | self.badgeLabel.font = [UIFont systemFontOfSize:8]; 103 | self.badgeLabel.textColor = [UIColor systemBlueColor]; 104 | self.badgeLabel.layer.cornerRadius = 9; 105 | self.badgeLabel.clipsToBounds = true; 106 | [self addSubview:self.badgeLabel]; 107 | 108 | UIEdgeInsets safeInsets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; 109 | _backViewBottomConstraint = [_backView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:safeInsets.bottom]; 110 | _doneTrailConstraint = [_doneButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]; 111 | 112 | [NSLayoutConstraint activateConstraints:@[ 113 | [_backView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:-safeInsets.left], 114 | [_backView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:safeInsets.right], 115 | [_backView.topAnchor constraintEqualToAnchor:self.topAnchor], 116 | _backViewBottomConstraint, 117 | 118 | [_collectionView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:8], 119 | [_collectionView.topAnchor constraintEqualToAnchor:self.topAnchor constant:8], 120 | [_collectionView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-8], 121 | [_collectionView.trailingAnchor constraintEqualToAnchor:_doneButton.leadingAnchor constant:-15], 122 | 123 | [_doneButton.widthAnchor constraintEqualToConstant:55], 124 | [_doneButton.heightAnchor constraintEqualToConstant:30], 125 | [_doneButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor], 126 | _doneTrailConstraint, 127 | 128 | [_badgeLabel.widthAnchor constraintEqualToConstant:18], 129 | [_badgeLabel.heightAnchor constraintEqualToConstant:18], 130 | [_badgeLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-6], 131 | [_badgeLabel.centerYAnchor constraintEqualToAnchor:self.centerYAnchor constant:-12], 132 | ]]; 133 | 134 | self.hideControls = false; 135 | } 136 | 137 | - (void)setHideControls:(BOOL)hideControls 138 | { 139 | _hideControls = hideControls; 140 | _doneButton.hidden = hideControls; 141 | _badgeLabel.hidden = hideControls; 142 | _doneTrailConstraint.constant = hideControls ? (55+15-8) : -10; 143 | 144 | UIEdgeInsets safeInsets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; 145 | _backViewBottomConstraint.constant = hideControls ? 0 : safeInsets.bottom; 146 | } 147 | 148 | - (void)setAssetArray:(NSArray *)assetArray 149 | { 150 | _assetArray = assetArray; 151 | _doneButton.enabled = assetArray.count>0; 152 | _badgeLabel.text = @(assetArray.count).stringValue; 153 | [_collectionView reloadData]; 154 | 155 | // 添加完成滚动到最后一个 156 | if (assetArray.count>0) { 157 | [self setCurrentIndex:assetArray.count - 1]; 158 | } 159 | } 160 | 161 | - (void)setCurrentIndex:(NSInteger)currentIndex 162 | { 163 | // 无符号与有符号比较出现bug ((NSInteger)-1) > ((NSUInteger)2) 164 | _currentIndex = currentIndex >= (NSInteger)_assetArray.count ? (_assetArray.count-1) : currentIndex; 165 | [_collectionView reloadData]; 166 | 167 | if (_currentIndex >= 0) { 168 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0] 169 | atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally 170 | animated:YES]; 171 | } 172 | } 173 | 174 | - (void)scrollToAssetIfNeed:(PHAsset *)asset 175 | { 176 | if ([[XYPhotoSelectedAssetManager sharedManager].selectedAssets containsObject:asset]) { 177 | NSInteger index = [[XYPhotoSelectedAssetManager sharedManager].selectedAssets indexOfObject:asset]; 178 | [self setCurrentIndex:index]; 179 | } else { 180 | [self setCurrentIndex:-1]; 181 | } 182 | } 183 | 184 | #pragma mark - UICollectionViewDataSource 185 | 186 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 187 | { 188 | XYPhotoSelectedAssetManager *manager = [XYPhotoSelectedAssetManager sharedManager]; 189 | if (manager.maxNumberOfAssets==0 || self.assetArray.count= self.assetArray.count) { 218 | return; 219 | } 220 | 221 | if ([self.delegate respondsToSelector:@selector(assetsSelectedPreviewView:didClickWithIndex:asset:)]) { 222 | [self.delegate assetsSelectedPreviewView:self didClickWithIndex:indexPath.row asset:self.assetArray[indexPath.row]]; 223 | } 224 | _currentIndex = indexPath.row; 225 | [_collectionView reloadData]; 226 | } 227 | 228 | #pragma mark - Notification 229 | 230 | - (void)selectedAssetsChanged 231 | { 232 | self.assetArray = [XYPhotoSelectedAssetManager sharedManager].selectedAssets; 233 | [self.collectionView reloadData]; 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoTableCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoTableCell.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import Photos; 10 | @import UIKit; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | FOUNDATION_EXPORT NSString *const XYPhotoTableCellReuseIdentifier; 15 | 16 | static CGFloat const CLPhotoCollectionCellRowHeight = 86.0f; 17 | 18 | @interface XYPhotoTableCell : UITableViewCell 19 | 20 | @property (nonatomic, strong) PHAssetCollection *collection; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /XYPhotoKit/Views/XYPhotoTableCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoTableCell.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoTableCell.h" 10 | #import "XYPhotoKitHelper.h" 11 | #import "XYPhotoSelectedAssetManager.h" 12 | #import "XYPhotoDataCategory.h" 13 | 14 | NSString *const XYPhotoTableCellReuseIdentifier = @"XYPhotoTableCellReuseIdentifier"; 15 | 16 | @interface XYPhotoTableCell () 17 | 18 | @property (nonatomic, strong) UIImageView *mainImageView; 19 | @property (nonatomic, strong) UIImageView *centerImageView; 20 | @property (nonatomic, strong) UIImageView *backImageView; 21 | 22 | @property (nonatomic, strong) UILabel *titleLabel; 23 | @property (nonatomic, strong) UILabel *subtitleLabel; 24 | 25 | @end 26 | 27 | @implementation XYPhotoTableCell 28 | 29 | - (instancetype)init 30 | { 31 | if (self = [super init]) { 32 | [self commonSetup]; 33 | } 34 | return self; 35 | } 36 | 37 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 38 | { 39 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 40 | [self commonSetup]; 41 | } 42 | return self; 43 | } 44 | 45 | - (id)initWithCoder:(NSCoder *)aDecoder 46 | { 47 | if (self = [super initWithCoder:aDecoder]) { 48 | [self commonSetup]; 49 | } 50 | return self; 51 | } 52 | 53 | - (instancetype)initWithFrame:(CGRect)frame { 54 | if (self = [super initWithFrame:frame]) { 55 | [self commonSetup]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)commonSetup 61 | { 62 | self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 63 | 64 | UIImageView *(^creatImageBlock)(CGRect frame) = ^(CGRect frame){ 65 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; 66 | imageView.contentMode = UIViewContentModeScaleAspectFill; 67 | imageView.clipsToBounds = true; 68 | imageView.layer.borderColor = [UIColor whiteColor].CGColor; 69 | imageView.layer.borderWidth = 1.0/[UIScreen mainScreen].scale; 70 | [self.contentView addSubview:imageView]; 71 | return imageView; 72 | }; 73 | 74 | CGFloat width = 68, left = 16, top = 11; 75 | self.backImageView = creatImageBlock(CGRectMake(left+2*2, top-2*2, width-4*2, width-4*2)); 76 | self.centerImageView = creatImageBlock(CGRectMake(left+2, top-2, width-4, width-4)); 77 | self.mainImageView = creatImageBlock(CGRectMake(left, top, width, width)); 78 | 79 | self.titleLabel = [[UILabel alloc] init]; 80 | [self.contentView addSubview:self.titleLabel]; 81 | 82 | self.subtitleLabel = [[UILabel alloc] init]; 83 | self.subtitleLabel.font = [UIFont systemFontOfSize:12]; 84 | [self.contentView addSubview:self.subtitleLabel]; 85 | } 86 | 87 | - (void)layoutSubviews 88 | { 89 | [super layoutSubviews]; 90 | 91 | self.titleLabel.frame = CGRectMake(100, 20, self.contentView.frame.size.width-100, 20); 92 | self.subtitleLabel.frame = CGRectMake(100, CGRectGetMaxY(self.titleLabel.frame)+4, self.contentView.frame.size.width-100, 15); 93 | } 94 | 95 | - (void)setCollection:(PHAssetCollection *)collection 96 | { 97 | _collection = collection; 98 | 99 | PHFetchOptions *fetchOptions = [PHFetchOptions new]; 100 | fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 101 | 102 | PHAssetMediaType type = [XYPhotoSelectedAssetManager sharedManager].mediaType; 103 | if (type != PHAssetMediaTypeUnknown) { 104 | fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType == %@", @(type)]; 105 | } 106 | 107 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:fetchOptions]; 108 | 109 | if (fetchResult.count<=0) { 110 | UIImage *placeholderImage = [UIImage xy_imageWithName:@"cl_photokit_placeholder"]; 111 | [@[_mainImageView, _centerImageView, _backImageView] enumerateObjectsUsingBlock:^(UIImageView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 112 | obj.hidden = false; 113 | obj.image = placeholderImage; 114 | }]; 115 | } else { 116 | [self setImageView:_mainImageView withAsset:fetchResult.firstObject]; 117 | 118 | _centerImageView.hidden = fetchResult.count<2; 119 | if (!_centerImageView.hidden) { 120 | [self setImageView:_centerImageView withAsset:fetchResult[1]]; 121 | } 122 | 123 | _backImageView.hidden = fetchResult.count<3; 124 | if (!_backImageView.hidden) { 125 | [self setImageView:_backImageView withAsset:fetchResult[2]]; 126 | } 127 | } 128 | 129 | self.titleLabel.text = collection.localizedTitle; 130 | self.subtitleLabel.text = [NSString stringWithFormat:@"%lu", (long)fetchResult.count]; 131 | } 132 | 133 | - (void)setImageView:(UIImageView *)imageView withAsset:(PHAsset *)asset 134 | { 135 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 136 | options.networkAccessAllowed = [XYPhotoSelectedAssetManager sharedManager].allowNetRequestIfiCloud; 137 | 138 | [[PHImageManager defaultManager] requestImageForAsset:asset 139 | targetSize:CGSizeScale(imageView.frame.size, [UIScreen mainScreen].scale) 140 | contentMode:PHImageContentModeAspectFill 141 | options:options 142 | resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 143 | imageView.image = result; 144 | }]; 145 | } 146 | 147 | + (CGFloat)cellHeightWithItem:(id)item tableView:(UITableView *)tableView 148 | { 149 | return CLPhotoCollectionCellRowHeight; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoCollectionFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionFlowLayout.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | 10 | @import UIKit; 11 | 12 | FOUNDATION_EXPORT NSUInteger const XYPhotoCollectionFlowLayoutDefaultColumns; 13 | 14 | @interface XYPhotoCollectionFlowLayout : UICollectionViewFlowLayout 15 | 16 | @end 17 | 18 | 19 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoCollectionFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoCollectionFlowLayout.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoCollectionFlowLayout.h" 10 | 11 | #import "XYPhotoSelectedAssetManager.h" 12 | 13 | NSUInteger const XYPhotoCollectionFlowLayoutDefaultColumns = 3; 14 | 15 | @interface XYPhotoCollectionFlowLayout () 16 | 17 | @property (nonatomic) NSUInteger numberOfColumns; 18 | 19 | @end 20 | 21 | @implementation XYPhotoCollectionFlowLayout 22 | 23 | static CGFloat const XYPhotoCollectionFlowLayoutDefaultSpacing = 2.0f; 24 | 25 | - (instancetype)init 26 | { 27 | if (self = [super init]) { 28 | self.scrollDirection = UICollectionViewScrollDirectionVertical; 29 | [self setupDefaults]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)awakeFromNib 35 | { 36 | [super awakeFromNib]; 37 | [self setupDefaults]; 38 | } 39 | 40 | - (void)setupDefaults 41 | { 42 | _numberOfColumns = [XYPhotoSelectedAssetManager sharedManager].assetCollectionViewColumnCount; 43 | 44 | self.minimumInteritemSpacing = XYPhotoCollectionFlowLayoutDefaultSpacing; 45 | self.minimumLineSpacing = XYPhotoCollectionFlowLayoutDefaultSpacing; 46 | } 47 | 48 | - (void)setScrollDirection:(UICollectionViewScrollDirection)scrollDirection 49 | { 50 | if (scrollDirection == UICollectionViewScrollDirectionVertical) { 51 | self.minimumInteritemSpacing = XYPhotoCollectionFlowLayoutDefaultSpacing; 52 | self.minimumLineSpacing = XYPhotoCollectionFlowLayoutDefaultSpacing; 53 | } else { 54 | self.minimumInteritemSpacing = 0; 55 | self.minimumLineSpacing = XYPhotoCollectionFlowLayoutDefaultSpacing*3; 56 | } 57 | [super setScrollDirection:scrollDirection]; 58 | } 59 | 60 | - (CGSize)itemSize 61 | { 62 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 63 | CGFloat totalWidth = CGRectGetWidth(self.collectionView.frame); 64 | totalWidth -= (self.minimumInteritemSpacing * (self.numberOfColumns - 1)); 65 | CGFloat cellDimension = totalWidth / self.numberOfColumns; 66 | return CGSizeMake(cellDimension, cellDimension); 67 | } else { 68 | CGFloat totalHeight = CGRectGetHeight(self.collectionView.frame); 69 | totalHeight -= self.minimumInteritemSpacing * 2; 70 | return CGSizeMake(totalHeight, totalHeight); 71 | } 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoDataCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoDataCategory.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import Photos; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface NSString (CLPhotoKit) 15 | 16 | @property (nonatomic, readonly) BOOL xy_isEmpty; 17 | 18 | @end 19 | 20 | @interface NSIndexSet (CLPhotoKit) 21 | 22 | - (NSArray *)xy_indexPathsFromIndexesWithSection:(NSUInteger)section; 23 | 24 | @end 25 | 26 | @interface PHFetchResult (CLPhotoKit) 27 | 28 | + (instancetype)xy_fetchResultWithAssetCollection:(PHAssetCollection *)assetCollection mediaType:(PHAssetMediaType)type; 29 | 30 | + (instancetype)xy_fetchResultWithAssetsOfType:(PHAssetMediaType)type; 31 | 32 | @end 33 | 34 | 35 | @interface UICollectionView (CLPhotoKit) 36 | 37 | - (nullable NSArray *)xy_indexPathsForElementsInRect:(CGRect)rect; 38 | 39 | @end 40 | 41 | @interface UIImage (CLPhotoKit) 42 | 43 | + (nullable UIImage *)xy_imageWithName:(NSString *)imageName; 44 | 45 | + (UIImage *)xy_imageWithColor:(UIColor *)color; 46 | 47 | 48 | @end 49 | 50 | @interface UIAlertController (CLPhotoKit) 51 | 52 | + (BOOL)xy_showAlertPhotoSettingIfUnauthorized; 53 | 54 | + (BOOL)xy_showAlertCameraSettingIfUnauthorized; 55 | 56 | + (void)xy_showTitle:(nullable NSString *)title message:(nullable NSString *)msg; 57 | 58 | @end 59 | 60 | @interface UIApplication (CLPhotoKit) 61 | 62 | @property (nonatomic, readonly, nullable) UIViewController *xy_topViewController; 63 | 64 | @end 65 | 66 | 67 | @interface PHAsset (CLPhotoKit) 68 | 69 | + (nullable PHAsset *)xy_getTheCloestAsset; 70 | 71 | // 72 | ///** 73 | // 同步请求指定大小图片 74 | // 75 | // @param size 目标尺寸 76 | // @return 图片 77 | // */ 78 | //- (UIImage *)xy_getSynchImageSize:(CGSize)size; 79 | // 80 | ///** 81 | // 同步请求指定大小缩略图 82 | // 83 | // @param size 目标尺寸 84 | // @return 图片 85 | // */ 86 | //- (UIImage *)xy_getSynchThumbnailWithSize:(CGSize)size; 87 | // 88 | ///** 89 | // 同步请求原图 90 | // 91 | // @return 图片 92 | // */ 93 | //- (UIImage *)xy_getSynchOriginImage; 94 | // 95 | 96 | @end 97 | 98 | NS_ASSUME_NONNULL_END 99 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoDataCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLPhotoDataCategory.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoDataCategory.h" 10 | 11 | @import AVFoundation; 12 | @import Photos; 13 | 14 | @implementation NSString (CLPhotoKit) 15 | 16 | - (BOOL)xy_isEmpty 17 | { 18 | NSString *tempStr = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 19 | if ([tempStr length] == 0 || [self isEqualToString:@"NULL"] || [self isEqualToString:@"null"] || [self isEqualToString:@"(null)"]) { 20 | return YES; 21 | } 22 | return NO; 23 | } 24 | 25 | @end 26 | 27 | @implementation NSIndexSet (CLPhotoKit) 28 | 29 | - (NSArray *)xy_indexPathsFromIndexesWithSection:(NSUInteger)section 30 | { 31 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count]; 32 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 33 | [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]]; 34 | }]; 35 | return indexPaths; 36 | } 37 | 38 | @end 39 | 40 | 41 | @implementation PHFetchResult (CLPhotoKit) 42 | 43 | + (instancetype)xy_fetchResultWithAssetCollection:(PHAssetCollection *)assetCollection mediaType:(PHAssetMediaType)type 44 | { 45 | PHFetchOptions *fetchOptions = [PHFetchOptions new]; 46 | 47 | if (type != PHAssetMediaTypeUnknown) { 48 | fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType == %@", @(type)]; 49 | } 50 | 51 | fetchOptions.sortDescriptors = @[[self xy_creationDateSortDescriptor]]; 52 | return [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions]; 53 | } 54 | 55 | + (instancetype)xy_fetchResultWithAssetsOfType:(PHAssetMediaType)type 56 | { 57 | PHFetchOptions *fetchOptions = [PHFetchOptions new]; 58 | fetchOptions.sortDescriptors = @[[self xy_creationDateSortDescriptor]]; 59 | 60 | if (type != PHAssetMediaTypeUnknown) { 61 | return [PHAsset fetchAssetsWithMediaType:type options:fetchOptions]; 62 | } 63 | return [PHAsset fetchAssetsWithOptions:fetchOptions]; 64 | } 65 | 66 | + (NSSortDescriptor *)xy_creationDateSortDescriptor 67 | { 68 | return [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]; 69 | } 70 | 71 | @end 72 | 73 | 74 | @implementation UICollectionView (CLPhotoKit) 75 | 76 | - (NSArray *)xy_indexPathsForElementsInRect:(CGRect)rect 77 | { 78 | NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect]; 79 | if (!allLayoutAttributes.count) { 80 | return nil; 81 | } 82 | 83 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count]; 84 | for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) { 85 | NSIndexPath *indexPath = layoutAttributes.indexPath; 86 | [indexPaths addObject:indexPath]; 87 | } 88 | return indexPaths; 89 | } 90 | 91 | @end 92 | 93 | @implementation UIAlertController (CLPhotoKit) 94 | 95 | + (BOOL)xy_showAlertPhotoSettingIfUnauthorized 96 | { 97 | PHAuthorizationStatus photoStatus = PHPhotoLibrary.authorizationStatus; 98 | if (photoStatus == PHAuthorizationStatusRestricted || photoStatus == PHAuthorizationStatusDenied) { 99 | NSString *mes = photoStatus==PHAuthorizationStatusDenied ? @"该功能需要相册服务,你可以在设置中打开对App的相册服务":@"该设备的相册功能被限制了"; 100 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:mes preferredStyle:UIAlertControllerStyleAlert]; 101 | [alertController addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:nil]]; 102 | if (photoStatus == PHAuthorizationStatusDenied) { 103 | [alertController addAction:[UIAlertAction actionWithTitle:@"去开启" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 104 | [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; 105 | }]]; 106 | } 107 | [[UIApplication sharedApplication].xy_topViewController presentViewController:alertController animated:YES completion:nil]; 108 | return NO; 109 | } 110 | return YES; 111 | } 112 | 113 | + (BOOL)xy_showAlertCameraSettingIfUnauthorized 114 | { 115 | AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 116 | if (videoStatus==AVAuthorizationStatusDenied || videoStatus==AVAuthorizationStatusRestricted) { 117 | NSString *mes = videoStatus==AVAuthorizationStatusDenied ? @"该功能需要相机服务,你可以在设置中打开对App的相机服务":@"该应用的相机功能被限制了"; 118 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:mes preferredStyle:UIAlertControllerStyleAlert]; 119 | [alertController addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:nil]]; 120 | if (videoStatus == AVAuthorizationStatusDenied) { 121 | [alertController addAction:[UIAlertAction actionWithTitle:@"去开启" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 122 | [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; 123 | }]]; 124 | } 125 | [[UIApplication sharedApplication].xy_topViewController presentViewController:alertController animated:YES completion:nil]; 126 | return NO; 127 | } 128 | return YES; 129 | } 130 | 131 | + (void)xy_showTitle:(NSString *)title message:(NSString *)msg 132 | { 133 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; 134 | [alert addAction:[UIAlertAction actionWithTitle:@"Sure" style:UIAlertActionStyleCancel handler:nil]]; 135 | [[UIApplication sharedApplication].xy_topViewController presentViewController:alert animated:YES completion:nil]; 136 | } 137 | 138 | @end 139 | 140 | @implementation UIImage (CLPhotoKit) 141 | 142 | + (UIImage *)xy_imageWithName:(NSString *)imageName 143 | { 144 | UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"XYPhotoKit.bundle/image/%@",imageName]]; 145 | return image; 146 | } 147 | 148 | + (UIImage *)xy_imageWithColor:(UIColor *)color 149 | { 150 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 151 | UIGraphicsBeginImageContext(rect.size); 152 | CGContextRef context = UIGraphicsGetCurrentContext(); 153 | 154 | CGContextSetFillColorWithColor(context, [color CGColor]); 155 | CGContextFillRect(context, rect); 156 | 157 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 158 | UIGraphicsEndImageContext(); 159 | 160 | return image; 161 | } 162 | 163 | @end 164 | 165 | @implementation UIApplication (CLPhotoKit) 166 | 167 | - (UIViewController *)xy_topViewController 168 | { 169 | UIViewController *rootController = [[[UIApplication sharedApplication].delegate window] rootViewController]; 170 | 171 | while (rootController.presentedViewController!=nil || [rootController isKindOfClass:[UINavigationController class]]) { 172 | if (rootController.presentedViewController!=nil) { 173 | rootController = rootController.presentedViewController; 174 | } 175 | if ([rootController isKindOfClass:[UINavigationController class]]) { 176 | rootController = [[(UINavigationController *)rootController viewControllers] lastObject]; 177 | } 178 | } 179 | return rootController; 180 | } 181 | 182 | @end 183 | 184 | @implementation PHAsset (CLPhotoKit) 185 | 186 | + (nullable PHAsset *)xy_getTheCloestAsset 187 | { 188 | PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init]; 189 | fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; 190 | PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions]; 191 | return [fetchResult lastObject]; 192 | } 193 | 194 | //- (UIImage *)xy_getImageSize:(CGSize)size 195 | //{ 196 | // return nil; 197 | //} 198 | // 199 | //- (UIImage *)xy_getThumbnailWithSize:(CGSize)size 200 | //{ 201 | // return nil; 202 | //} 203 | // 204 | //- (UIImage *)xy_getOriginImage 205 | //{ 206 | // return nil; 207 | //} 208 | // 209 | //- (UIImage *)xy_getImageWithSize:(CGSize)size iCloud:(BOOL)iCould 210 | //{ 211 | // return nil; 212 | //} 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_camera_devicemode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_camera_devicemode@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_camera_flash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_camera_flash@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_inaccessible@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_inaccessible@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_picked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_picked@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_shutter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_shutter@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_shutter_h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_shutter_h@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_unpicked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photo_picker_unpicked@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_placeholder@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_play@2x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKit/XYPhotoKit.bundle/image/cl_photokit_play@3x.png -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKitHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoKitHelper.h 3 | // XYPhotoKitDemo-iOS 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | 12 | CG_INLINE CGSize 13 | CGSizeScale(CGSize size, CGFloat scale) { 14 | return CGSizeMake(size.width * scale, size.height * scale); 15 | } 16 | 17 | @interface XYPhotoKitHelper : NSObject 18 | 19 | //通知名字 20 | FOUNDATION_EXPORT const struct XYPhotoMultiImagePickerNotifications { 21 | // asset 添加或删除. 22 | __unsafe_unretained NSString *assetsChanged; 23 | // 成功删除 24 | __unsafe_unretained NSString *assetsDeleted; 25 | // 成功添加 26 | __unsafe_unretained NSString *assetsAdded; 27 | 28 | } XYPhotoMultiImagePickerNotifications; 29 | 30 | 31 | FOUNDATION_EXPORT const struct XYPhotoImagePickerName { 32 | __unsafe_unretained NSString *selectAnAlbum; 33 | __unsafe_unretained NSString *cameraRoll; 34 | } XYPhotoImagePickerName; 35 | 36 | 37 | + (BOOL)isAvailableAccessPhoto; 38 | 39 | + (BOOL)isAuthorizedAccessPhoto; 40 | 41 | + (BOOL)isAvailableAccessCamera; 42 | 43 | + (BOOL)isAuthorizedAccessCamera; 44 | 45 | 46 | @end 47 | 48 | 49 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoKitHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoKitHelper.m 3 | // XYPhotoKitDemo-iOS 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | #import "XYPhotoKitHelper.h" 9 | 10 | @import AVFoundation; 11 | @import Photos; 12 | 13 | @implementation XYPhotoKitHelper 14 | 15 | const struct XYPhotoMultiImagePickerNotifications XYPhotoMultiImagePickerNotifications = { 16 | .assetsChanged = @"XYPhotoMultiImagePickerNotificationAssetsChanged", 17 | .assetsDeleted = @"XYPhotoMultiImagePickerNotificationAssetsDeleted", 18 | .assetsAdded = @"XYPhotoMultiImagePickerNotificationAssetsAdded", 19 | }; 20 | 21 | const struct XYPhotoImagePickerName XYPhotoImagePickerName = { 22 | .selectAnAlbum = @"相册列表", 23 | .cameraRoll = @"Camera Roll", 24 | }; 25 | 26 | + (BOOL)isAvailableAccessPhoto 27 | { 28 | PHAuthorizationStatus status = PHPhotoLibrary.authorizationStatus; 29 | return status == PHAuthorizationStatusAuthorized || status == PHAuthorizationStatusNotDetermined; 30 | } 31 | 32 | + (BOOL)isAuthorizedAccessPhoto 33 | { 34 | PHAuthorizationStatus status = PHPhotoLibrary.authorizationStatus; 35 | return status == PHAuthorizationStatusAuthorized; 36 | } 37 | 38 | + (BOOL)isAvailableAccessCamera 39 | { 40 | AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 41 | BOOL hasCamera = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; 42 | return hasCamera && (videoStatus == AVAuthorizationStatusAuthorized || videoStatus == AVAuthorizationStatusNotDetermined); 43 | } 44 | 45 | + (BOOL)isAuthorizedAccessCamera 46 | { 47 | AVAuthorizationStatus videoStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 48 | BOOL hasCamera = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; 49 | return hasCamera && videoStatus == AVAuthorizationStatusAuthorized; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoSelectedAssetManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoSelectedAssetManager.h 3 | // XYPhotoKitDemo-iOS 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | @import Photos; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @protocol XYPhotoSelectedAssetManagerDelegate 15 | - (void)doneSelectingAssets; 16 | @end 17 | 18 | @interface XYPhotoSelectedAssetManager : NSObject 19 | 20 | + (instancetype)alloc __attribute__((unavailable("此方法已弃用,请使用 sharedManager"))); 21 | - (instancetype)init __attribute__((unavailable("此方法已弃用,请使用 sharedManager"))); 22 | + (instancetype)new __attribute__((unavailable("此方法已弃用,请使用 sharedManager"))); 23 | 24 | + (instancetype)sharedManager; 25 | 26 | /** 27 | 为了下次使用重置单例. 28 | */ 29 | - (void)resetManager; 30 | 31 | /** 32 | 代理回调,结束asset选择 33 | */ 34 | @property (nonatomic, weak, nullable) id delegate; 35 | 36 | /** 37 | Unknown = 0,Image,Video,Audio 38 | */ 39 | @property (nonatomic) PHAssetMediaType mediaType; 40 | 41 | /** 42 | 是否允许网络请求从iCloud下载 43 | */ 44 | @property (nonatomic) BOOL allowNetRequestIfiCloud; 45 | 46 | /** 47 | 横向列数 48 | */ 49 | @property (nonatomic) NSInteger assetCollectionViewColumnCount; 50 | 51 | /** 52 | Defaults=0,可以无限制选择asset个数 53 | */ 54 | @property (nonatomic) NSUInteger maxNumberOfAssets; 55 | 56 | /** 57 | 达到最大限制后的文案说明 58 | */ 59 | @property (nonatomic, copy, nullable) NSString *maxNumberLimitText; 60 | 61 | /** 62 | 添加资源,要求asset类型要匹配 63 | @param asset 添加的asset 64 | @return yes成功,no失败(根据最大个数和asset类型判断) 65 | */ 66 | - (BOOL)addSelectedAsset:(PHAsset *)asset; 67 | 68 | /** 69 | 删除资源,无限制 70 | @param asset 被删除asset 71 | */ 72 | - (void)removeSelectedAsset:(PHAsset *)asset; 73 | 74 | /** 75 | 重置已选中的asset 76 | */ 77 | - (void)resetSelectedAsset:(nullable NSArray *)assets; 78 | 79 | /** 80 | 已选择的asset列表 81 | @return 浅拷贝返回 82 | */ 83 | - (NSArray *)selectedAssets; 84 | 85 | @end 86 | 87 | NS_ASSUME_NONNULL_END 88 | -------------------------------------------------------------------------------- /XYPhotoKit/XYPhotoSelectedAssetManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYPhotoSelectedAssetManager.m 3 | // XYPhotoKitDemo-iOS 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYPhotoSelectedAssetManager.h" 10 | #import "XYPhotoDataCategory.h" 11 | #import "XYPhotoCollectionViewCell.h" 12 | #import "XYPhotoCollectionFlowLayout.h" 13 | #import "XYPhotoKitHelper.h" 14 | 15 | @interface XYPhotoSelectedAssetManager () 16 | 17 | @property (nonatomic) NSMutableArray *selectedAssetsMutableArray; 18 | 19 | @end 20 | 21 | @implementation XYPhotoSelectedAssetManager 22 | 23 | + (instancetype)sharedManager 24 | { 25 | static XYPhotoSelectedAssetManager *SharedManager = nil; 26 | 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | SharedManager = [[super alloc] initUniqueInstance]; 30 | [SharedManager resetManager]; 31 | }); 32 | 33 | return SharedManager; 34 | } 35 | 36 | -(instancetype)initUniqueInstance; 37 | { 38 | return [super init]; 39 | } 40 | 41 | - (void)resetManager 42 | { 43 | self.selectedAssetsMutableArray = [NSMutableArray array]; 44 | self.mediaType = PHAssetMediaTypeUnknown; 45 | self.assetCollectionViewColumnCount = XYPhotoCollectionFlowLayoutDefaultColumns; 46 | self.maxNumberOfAssets = 0; 47 | } 48 | 49 | - (BOOL)addSelectedAsset:(PHAsset *)asset 50 | { 51 | if ([self assetIsInMediaType:asset] && [self canAddMoreAssets]) { 52 | if (![self.selectedAssetsMutableArray containsObject:asset]) { 53 | [self.selectedAssetsMutableArray addObject:asset]; 54 | } 55 | 56 | [[NSNotificationCenter defaultCenter] postNotificationName:XYPhotoMultiImagePickerNotifications.assetsChanged object:asset]; 57 | return YES; 58 | } 59 | return NO; 60 | } 61 | 62 | - (BOOL)canAddMoreAssets 63 | { 64 | if (self.maxNumberOfAssets == 0) { 65 | return YES; 66 | } else if (self.selectedAssetsMutableArray.count < self.maxNumberOfAssets) { 67 | return YES; 68 | } else { 69 | NSString *tip = (self.maxNumberLimitText ?: @"").xy_isEmpty ? @"已达到最大照片数限制" : self.maxNumberLimitText; 70 | [UIAlertController xy_showTitle:@"友情提示" message:tip]; 71 | return NO; 72 | } 73 | } 74 | 75 | - (void)removeSelectedAsset:(PHAsset *)asset 76 | { 77 | [self.selectedAssetsMutableArray removeObject:asset]; 78 | 79 | [[NSNotificationCenter defaultCenter] postNotificationName:XYPhotoMultiImagePickerNotifications.assetsChanged object:asset]; 80 | } 81 | 82 | - (void)resetSelectedAsset:(NSArray *)assets 83 | { 84 | [self.selectedAssetsMutableArray removeAllObjects]; 85 | 86 | NSMutableArray *deleteAssets = @[].mutableCopy; 87 | [assets enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 88 | if (![self assetIsInMediaType:obj]) { 89 | [deleteAssets addObject:obj]; 90 | } 91 | }]; 92 | NSMutableArray *newAssets = assets.mutableCopy; 93 | [newAssets removeObjectsInArray:deleteAssets]; 94 | 95 | if (self.maxNumberOfAssets <= 0 || (self.maxNumberOfAssets>0 && self.maxNumberOfAssets>newAssets.count)) { 96 | // 个数无限制 或者 未达到最大个数限制 97 | [self.selectedAssetsMutableArray addObjectsFromArray:newAssets]; 98 | } else { 99 | // 超出限制个数,裁剪 100 | [self.selectedAssetsMutableArray addObjectsFromArray:[newAssets subarrayWithRange:NSMakeRange(0, self.maxNumberOfAssets)]]; 101 | } 102 | 103 | [[NSNotificationCenter defaultCenter] postNotificationName:XYPhotoMultiImagePickerNotifications.assetsChanged object:nil]; 104 | } 105 | 106 | - (NSArray *)selectedAssets 107 | { 108 | return [self.selectedAssetsMutableArray copy]; 109 | } 110 | 111 | #pragma mark - Helpers 112 | 113 | - (BOOL)assetIsInMediaType:(PHAsset *)asset; 114 | { 115 | if (![asset isKindOfClass:[PHAsset class]]) { 116 | return NO; 117 | } 118 | 119 | if (self.mediaType == PHAssetMediaTypeUnknown) { 120 | return YES; 121 | } else if (asset.mediaType == self.mediaType) { 122 | return YES; 123 | } 124 | return NO; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 651B29681F6EC1730085BCE3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 651B29671F6EC1730085BCE3 /* AppDelegate.m */; }; 11 | 651B296E1F6EC1730085BCE3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 651B296C1F6EC1730085BCE3 /* Main.storyboard */; }; 12 | 651B29701F6EC1730085BCE3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 651B296F1F6EC1730085BCE3 /* Assets.xcassets */; }; 13 | 651B29731F6EC1730085BCE3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 651B29711F6EC1730085BCE3 /* LaunchScreen.storyboard */; }; 14 | 651B29761F6EC1730085BCE3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 651B29751F6EC1730085BCE3 /* main.m */; }; 15 | 658075301F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075051F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.m */; }; 16 | 658075311F6EDB62002408A5 /* XYPhotoDataCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075071F6EDB62002408A5 /* XYPhotoDataCategory.m */; }; 17 | 658075321F6EDB62002408A5 /* XYPhotoKitHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075091F6EDB62002408A5 /* XYPhotoKitHelper.m */; }; 18 | 658075331F6EDB62002408A5 /* XYPhotoSelectedAssetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6580750B1F6EDB62002408A5 /* XYPhotoSelectedAssetManager.m */; }; 19 | 658075341F6EDB62002408A5 /* XYPhotoKit.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6580750C1F6EDB62002408A5 /* XYPhotoKit.bundle */; }; 20 | 658075351F6EDB62002408A5 /* XYPhotoCollectionDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 6580750F1F6EDB62002408A5 /* XYPhotoCollectionDataSource.m */; }; 21 | 658075361F6EDB62002408A5 /* XYPhotoTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075111F6EDB62002408A5 /* XYPhotoTableDataSource.m */; }; 22 | 658075371F6EDB62002408A5 /* XYPhotoAlbumDetailController.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075141F6EDB62002408A5 /* XYPhotoAlbumDetailController.m */; }; 23 | 658075381F6EDB62002408A5 /* XYPhotoAlbumListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075161F6EDB62002408A5 /* XYPhotoAlbumListController.m */; }; 24 | 658075391F6EDB62002408A5 /* XYPhotoCameraController.m in Sources */ = {isa = PBXBuildFile; fileRef = 658075181F6EDB62002408A5 /* XYPhotoCameraController.m */; }; 25 | 6580753A1F6EDB62002408A5 /* XYPhotoMultiCameraPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 6580751A1F6EDB62002408A5 /* XYPhotoMultiCameraPicker.m */; }; 26 | 6580753B1F6EDB62002408A5 /* XYPhotoMultiImagePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 6580751C1F6EDB62002408A5 /* XYPhotoMultiImagePicker.m */; }; 27 | 6580753C1F6EDB62002408A5 /* XYPhotoPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6580751E1F6EDB62002408A5 /* XYPhotoPreviewViewController.m */; }; 28 | DBFD4E3923FFDAC000C1A472 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E3523FFDABF00C1A472 /* RootViewController.m */; }; 29 | DBFD4E3A23FFDAC000C1A472 /* XYDemoCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E3723FFDABF00C1A472 /* XYDemoCollectionViewCell.m */; }; 30 | DBFD4E4B23FFE51000C1A472 /* XYPhotoBottomPreviewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E3C23FFE50F00C1A472 /* XYPhotoBottomPreviewCell.m */; }; 31 | DBFD4E4C23FFE51000C1A472 /* XYPhotoPreviewOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4123FFE51000C1A472 /* XYPhotoPreviewOverlayView.m */; }; 32 | DBFD4E4D23FFE51000C1A472 /* XYPhotoHorizontalScrollItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4323FFE51000C1A472 /* XYPhotoHorizontalScrollItemView.m */; }; 33 | DBFD4E4E23FFE51000C1A472 /* XYPhotoCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4423FFE51000C1A472 /* XYPhotoCollectionViewCell.m */; }; 34 | DBFD4E4F23FFE51000C1A472 /* XYPhotoSelectedAssetPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4523FFE51000C1A472 /* XYPhotoSelectedAssetPreviewView.m */; }; 35 | DBFD4E5023FFE51000C1A472 /* XYPhotoTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4623FFE51000C1A472 /* XYPhotoTableCell.m */; }; 36 | DBFD4E5123FFE51000C1A472 /* XYPhotoCameraOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4823FFE51000C1A472 /* XYPhotoCameraOverlayView.m */; }; 37 | DBFD4E5223FFE51000C1A472 /* XYPhotoHorizontalScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFD4E4A23FFE51000C1A472 /* XYPhotoHorizontalScrollView.m */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 651B29631F6EC1730085BCE3 /* XYPhotoKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XYPhotoKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 651B29661F6EC1730085BCE3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 651B29671F6EC1730085BCE3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 651B296D1F6EC1730085BCE3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 651B296F1F6EC1730085BCE3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 651B29721F6EC1730085BCE3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 651B29741F6EC1730085BCE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 651B29751F6EC1730085BCE3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | 658075041F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoCollectionFlowLayout.h; sourceTree = ""; }; 50 | 658075051F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoCollectionFlowLayout.m; sourceTree = ""; }; 51 | 658075061F6EDB62002408A5 /* XYPhotoDataCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoDataCategory.h; sourceTree = ""; }; 52 | 658075071F6EDB62002408A5 /* XYPhotoDataCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoDataCategory.m; sourceTree = ""; }; 53 | 658075081F6EDB62002408A5 /* XYPhotoKitHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoKitHelper.h; sourceTree = ""; }; 54 | 658075091F6EDB62002408A5 /* XYPhotoKitHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoKitHelper.m; sourceTree = ""; }; 55 | 6580750A1F6EDB62002408A5 /* XYPhotoSelectedAssetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoSelectedAssetManager.h; sourceTree = ""; }; 56 | 6580750B1F6EDB62002408A5 /* XYPhotoSelectedAssetManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoSelectedAssetManager.m; sourceTree = ""; }; 57 | 6580750C1F6EDB62002408A5 /* XYPhotoKit.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = XYPhotoKit.bundle; sourceTree = ""; }; 58 | 6580750E1F6EDB62002408A5 /* XYPhotoCollectionDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoCollectionDataSource.h; sourceTree = ""; }; 59 | 6580750F1F6EDB62002408A5 /* XYPhotoCollectionDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoCollectionDataSource.m; sourceTree = ""; }; 60 | 658075101F6EDB62002408A5 /* XYPhotoTableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoTableDataSource.h; sourceTree = ""; }; 61 | 658075111F6EDB62002408A5 /* XYPhotoTableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoTableDataSource.m; sourceTree = ""; }; 62 | 658075131F6EDB62002408A5 /* XYPhotoAlbumDetailController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoAlbumDetailController.h; sourceTree = ""; }; 63 | 658075141F6EDB62002408A5 /* XYPhotoAlbumDetailController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoAlbumDetailController.m; sourceTree = ""; }; 64 | 658075151F6EDB62002408A5 /* XYPhotoAlbumListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoAlbumListController.h; sourceTree = ""; }; 65 | 658075161F6EDB62002408A5 /* XYPhotoAlbumListController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoAlbumListController.m; sourceTree = ""; }; 66 | 658075171F6EDB62002408A5 /* XYPhotoCameraController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoCameraController.h; sourceTree = ""; }; 67 | 658075181F6EDB62002408A5 /* XYPhotoCameraController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoCameraController.m; sourceTree = ""; }; 68 | 658075191F6EDB62002408A5 /* XYPhotoMultiCameraPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoMultiCameraPicker.h; sourceTree = ""; }; 69 | 6580751A1F6EDB62002408A5 /* XYPhotoMultiCameraPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoMultiCameraPicker.m; sourceTree = ""; }; 70 | 6580751B1F6EDB62002408A5 /* XYPhotoMultiImagePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoMultiImagePicker.h; sourceTree = ""; }; 71 | 6580751C1F6EDB62002408A5 /* XYPhotoMultiImagePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoMultiImagePicker.m; sourceTree = ""; }; 72 | 6580751D1F6EDB62002408A5 /* XYPhotoPreviewViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoPreviewViewController.h; sourceTree = ""; }; 73 | 6580751E1F6EDB62002408A5 /* XYPhotoPreviewViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoPreviewViewController.m; sourceTree = ""; }; 74 | DBFD4E3523FFDABF00C1A472 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; }; 75 | DBFD4E3623FFDABF00C1A472 /* RootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootViewController.h; sourceTree = ""; }; 76 | DBFD4E3723FFDABF00C1A472 /* XYDemoCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYDemoCollectionViewCell.m; sourceTree = ""; }; 77 | DBFD4E3823FFDAC000C1A472 /* XYDemoCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYDemoCollectionViewCell.h; sourceTree = ""; }; 78 | DBFD4E3B23FFE50F00C1A472 /* XYPhotoTableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoTableCell.h; sourceTree = ""; }; 79 | DBFD4E3C23FFE50F00C1A472 /* XYPhotoBottomPreviewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoBottomPreviewCell.m; sourceTree = ""; }; 80 | DBFD4E3D23FFE50F00C1A472 /* XYPhotoSelectedAssetPreviewView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoSelectedAssetPreviewView.h; sourceTree = ""; }; 81 | DBFD4E3E23FFE50F00C1A472 /* XYPhotoCameraOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoCameraOverlayView.h; sourceTree = ""; }; 82 | DBFD4E3F23FFE50F00C1A472 /* XYPhotoBottomPreviewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoBottomPreviewCell.h; sourceTree = ""; }; 83 | DBFD4E4023FFE50F00C1A472 /* XYPhotoHorizontalScrollItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoHorizontalScrollItemView.h; sourceTree = ""; }; 84 | DBFD4E4123FFE51000C1A472 /* XYPhotoPreviewOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoPreviewOverlayView.m; sourceTree = ""; }; 85 | DBFD4E4223FFE51000C1A472 /* XYPhotoCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoCollectionViewCell.h; sourceTree = ""; }; 86 | DBFD4E4323FFE51000C1A472 /* XYPhotoHorizontalScrollItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoHorizontalScrollItemView.m; sourceTree = ""; }; 87 | DBFD4E4423FFE51000C1A472 /* XYPhotoCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoCollectionViewCell.m; sourceTree = ""; }; 88 | DBFD4E4523FFE51000C1A472 /* XYPhotoSelectedAssetPreviewView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoSelectedAssetPreviewView.m; sourceTree = ""; }; 89 | DBFD4E4623FFE51000C1A472 /* XYPhotoTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoTableCell.m; sourceTree = ""; }; 90 | DBFD4E4723FFE51000C1A472 /* XYPhotoHorizontalScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoHorizontalScrollView.h; sourceTree = ""; }; 91 | DBFD4E4823FFE51000C1A472 /* XYPhotoCameraOverlayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoCameraOverlayView.m; sourceTree = ""; }; 92 | DBFD4E4923FFE51000C1A472 /* XYPhotoPreviewOverlayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYPhotoPreviewOverlayView.h; sourceTree = ""; }; 93 | DBFD4E4A23FFE51000C1A472 /* XYPhotoHorizontalScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYPhotoHorizontalScrollView.m; sourceTree = ""; }; 94 | /* End PBXFileReference section */ 95 | 96 | /* Begin PBXFrameworksBuildPhase section */ 97 | 651B29601F6EC1730085BCE3 /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXFrameworksBuildPhase section */ 105 | 106 | /* Begin PBXGroup section */ 107 | 651B295A1F6EC1730085BCE3 = { 108 | isa = PBXGroup; 109 | children = ( 110 | 658075031F6EDB62002408A5 /* XYPhotoKit */, 111 | 651B29651F6EC1730085BCE3 /* XYPhotoKitDemo */, 112 | 651B29641F6EC1730085BCE3 /* Products */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 651B29641F6EC1730085BCE3 /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 651B29631F6EC1730085BCE3 /* XYPhotoKitDemo.app */, 120 | ); 121 | name = Products; 122 | sourceTree = ""; 123 | }; 124 | 651B29651F6EC1730085BCE3 /* XYPhotoKitDemo */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 651B29661F6EC1730085BCE3 /* AppDelegate.h */, 128 | 651B29671F6EC1730085BCE3 /* AppDelegate.m */, 129 | DBFD4E3823FFDAC000C1A472 /* XYDemoCollectionViewCell.h */, 130 | DBFD4E3723FFDABF00C1A472 /* XYDemoCollectionViewCell.m */, 131 | DBFD4E3623FFDABF00C1A472 /* RootViewController.h */, 132 | DBFD4E3523FFDABF00C1A472 /* RootViewController.m */, 133 | 651B296C1F6EC1730085BCE3 /* Main.storyboard */, 134 | 651B296F1F6EC1730085BCE3 /* Assets.xcassets */, 135 | 651B29711F6EC1730085BCE3 /* LaunchScreen.storyboard */, 136 | 651B29741F6EC1730085BCE3 /* Info.plist */, 137 | 651B29751F6EC1730085BCE3 /* main.m */, 138 | ); 139 | path = XYPhotoKitDemo; 140 | sourceTree = ""; 141 | }; 142 | 658075031F6EDB62002408A5 /* XYPhotoKit */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 658075041F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.h */, 146 | 658075051F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.m */, 147 | 658075061F6EDB62002408A5 /* XYPhotoDataCategory.h */, 148 | 658075071F6EDB62002408A5 /* XYPhotoDataCategory.m */, 149 | 658075081F6EDB62002408A5 /* XYPhotoKitHelper.h */, 150 | 658075091F6EDB62002408A5 /* XYPhotoKitHelper.m */, 151 | 6580750A1F6EDB62002408A5 /* XYPhotoSelectedAssetManager.h */, 152 | 6580750B1F6EDB62002408A5 /* XYPhotoSelectedAssetManager.m */, 153 | 6580750C1F6EDB62002408A5 /* XYPhotoKit.bundle */, 154 | 6580750D1F6EDB62002408A5 /* DataSources */, 155 | 658075121F6EDB62002408A5 /* ViewControllers */, 156 | 6580751F1F6EDB62002408A5 /* Views */, 157 | ); 158 | name = XYPhotoKit; 159 | path = ../XYPhotoKit; 160 | sourceTree = ""; 161 | }; 162 | 6580750D1F6EDB62002408A5 /* DataSources */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 6580750E1F6EDB62002408A5 /* XYPhotoCollectionDataSource.h */, 166 | 6580750F1F6EDB62002408A5 /* XYPhotoCollectionDataSource.m */, 167 | 658075101F6EDB62002408A5 /* XYPhotoTableDataSource.h */, 168 | 658075111F6EDB62002408A5 /* XYPhotoTableDataSource.m */, 169 | ); 170 | path = DataSources; 171 | sourceTree = ""; 172 | }; 173 | 658075121F6EDB62002408A5 /* ViewControllers */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 658075131F6EDB62002408A5 /* XYPhotoAlbumDetailController.h */, 177 | 658075141F6EDB62002408A5 /* XYPhotoAlbumDetailController.m */, 178 | 658075151F6EDB62002408A5 /* XYPhotoAlbumListController.h */, 179 | 658075161F6EDB62002408A5 /* XYPhotoAlbumListController.m */, 180 | 658075171F6EDB62002408A5 /* XYPhotoCameraController.h */, 181 | 658075181F6EDB62002408A5 /* XYPhotoCameraController.m */, 182 | 658075191F6EDB62002408A5 /* XYPhotoMultiCameraPicker.h */, 183 | 6580751A1F6EDB62002408A5 /* XYPhotoMultiCameraPicker.m */, 184 | 6580751B1F6EDB62002408A5 /* XYPhotoMultiImagePicker.h */, 185 | 6580751C1F6EDB62002408A5 /* XYPhotoMultiImagePicker.m */, 186 | 6580751D1F6EDB62002408A5 /* XYPhotoPreviewViewController.h */, 187 | 6580751E1F6EDB62002408A5 /* XYPhotoPreviewViewController.m */, 188 | ); 189 | path = ViewControllers; 190 | sourceTree = ""; 191 | }; 192 | 6580751F1F6EDB62002408A5 /* Views */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | DBFD4E3F23FFE50F00C1A472 /* XYPhotoBottomPreviewCell.h */, 196 | DBFD4E3C23FFE50F00C1A472 /* XYPhotoBottomPreviewCell.m */, 197 | DBFD4E3E23FFE50F00C1A472 /* XYPhotoCameraOverlayView.h */, 198 | DBFD4E4823FFE51000C1A472 /* XYPhotoCameraOverlayView.m */, 199 | DBFD4E4223FFE51000C1A472 /* XYPhotoCollectionViewCell.h */, 200 | DBFD4E4423FFE51000C1A472 /* XYPhotoCollectionViewCell.m */, 201 | DBFD4E4023FFE50F00C1A472 /* XYPhotoHorizontalScrollItemView.h */, 202 | DBFD4E4323FFE51000C1A472 /* XYPhotoHorizontalScrollItemView.m */, 203 | DBFD4E4723FFE51000C1A472 /* XYPhotoHorizontalScrollView.h */, 204 | DBFD4E4A23FFE51000C1A472 /* XYPhotoHorizontalScrollView.m */, 205 | DBFD4E4923FFE51000C1A472 /* XYPhotoPreviewOverlayView.h */, 206 | DBFD4E4123FFE51000C1A472 /* XYPhotoPreviewOverlayView.m */, 207 | DBFD4E3D23FFE50F00C1A472 /* XYPhotoSelectedAssetPreviewView.h */, 208 | DBFD4E4523FFE51000C1A472 /* XYPhotoSelectedAssetPreviewView.m */, 209 | DBFD4E3B23FFE50F00C1A472 /* XYPhotoTableCell.h */, 210 | DBFD4E4623FFE51000C1A472 /* XYPhotoTableCell.m */, 211 | ); 212 | path = Views; 213 | sourceTree = ""; 214 | }; 215 | /* End PBXGroup section */ 216 | 217 | /* Begin PBXNativeTarget section */ 218 | 651B29621F6EC1730085BCE3 /* XYPhotoKitDemo */ = { 219 | isa = PBXNativeTarget; 220 | buildConfigurationList = 651B29791F6EC1730085BCE3 /* Build configuration list for PBXNativeTarget "XYPhotoKitDemo" */; 221 | buildPhases = ( 222 | 651B295F1F6EC1730085BCE3 /* Sources */, 223 | 651B29601F6EC1730085BCE3 /* Frameworks */, 224 | 651B29611F6EC1730085BCE3 /* Resources */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | ); 230 | name = XYPhotoKitDemo; 231 | productName = XYPhotoKitDemo; 232 | productReference = 651B29631F6EC1730085BCE3 /* XYPhotoKitDemo.app */; 233 | productType = "com.apple.product-type.application"; 234 | }; 235 | /* End PBXNativeTarget section */ 236 | 237 | /* Begin PBXProject section */ 238 | 651B295B1F6EC1730085BCE3 /* Project object */ = { 239 | isa = PBXProject; 240 | attributes = { 241 | LastUpgradeCheck = 1130; 242 | ORGANIZATIONNAME = Daniel; 243 | TargetAttributes = { 244 | 651B29621F6EC1730085BCE3 = { 245 | CreatedOnToolsVersion = 9.0; 246 | ProvisioningStyle = Automatic; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = 651B295E1F6EC1730085BCE3 /* Build configuration list for PBXProject "XYPhotoKitDemo" */; 251 | compatibilityVersion = "Xcode 8.0"; 252 | developmentRegion = en; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = 651B295A1F6EC1730085BCE3; 259 | productRefGroup = 651B29641F6EC1730085BCE3 /* Products */; 260 | projectDirPath = ""; 261 | projectRoot = ""; 262 | targets = ( 263 | 651B29621F6EC1730085BCE3 /* XYPhotoKitDemo */, 264 | ); 265 | }; 266 | /* End PBXProject section */ 267 | 268 | /* Begin PBXResourcesBuildPhase section */ 269 | 651B29611F6EC1730085BCE3 /* Resources */ = { 270 | isa = PBXResourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | 651B29731F6EC1730085BCE3 /* LaunchScreen.storyboard in Resources */, 274 | 651B29701F6EC1730085BCE3 /* Assets.xcassets in Resources */, 275 | 658075341F6EDB62002408A5 /* XYPhotoKit.bundle in Resources */, 276 | 651B296E1F6EC1730085BCE3 /* Main.storyboard in Resources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXResourcesBuildPhase section */ 281 | 282 | /* Begin PBXSourcesBuildPhase section */ 283 | 651B295F1F6EC1730085BCE3 /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | DBFD4E5123FFE51000C1A472 /* XYPhotoCameraOverlayView.m in Sources */, 288 | 6580753B1F6EDB62002408A5 /* XYPhotoMultiImagePicker.m in Sources */, 289 | DBFD4E3A23FFDAC000C1A472 /* XYDemoCollectionViewCell.m in Sources */, 290 | 658075381F6EDB62002408A5 /* XYPhotoAlbumListController.m in Sources */, 291 | 6580753C1F6EDB62002408A5 /* XYPhotoPreviewViewController.m in Sources */, 292 | DBFD4E5223FFE51000C1A472 /* XYPhotoHorizontalScrollView.m in Sources */, 293 | 651B29761F6EC1730085BCE3 /* main.m in Sources */, 294 | 658075361F6EDB62002408A5 /* XYPhotoTableDataSource.m in Sources */, 295 | DBFD4E4B23FFE51000C1A472 /* XYPhotoBottomPreviewCell.m in Sources */, 296 | 658075321F6EDB62002408A5 /* XYPhotoKitHelper.m in Sources */, 297 | DBFD4E4E23FFE51000C1A472 /* XYPhotoCollectionViewCell.m in Sources */, 298 | 658075311F6EDB62002408A5 /* XYPhotoDataCategory.m in Sources */, 299 | 658075351F6EDB62002408A5 /* XYPhotoCollectionDataSource.m in Sources */, 300 | DBFD4E3923FFDAC000C1A472 /* RootViewController.m in Sources */, 301 | 658075331F6EDB62002408A5 /* XYPhotoSelectedAssetManager.m in Sources */, 302 | 658075301F6EDB62002408A5 /* XYPhotoCollectionFlowLayout.m in Sources */, 303 | DBFD4E4F23FFE51000C1A472 /* XYPhotoSelectedAssetPreviewView.m in Sources */, 304 | 658075371F6EDB62002408A5 /* XYPhotoAlbumDetailController.m in Sources */, 305 | DBFD4E4C23FFE51000C1A472 /* XYPhotoPreviewOverlayView.m in Sources */, 306 | 651B29681F6EC1730085BCE3 /* AppDelegate.m in Sources */, 307 | DBFD4E4D23FFE51000C1A472 /* XYPhotoHorizontalScrollItemView.m in Sources */, 308 | 658075391F6EDB62002408A5 /* XYPhotoCameraController.m in Sources */, 309 | DBFD4E5023FFE51000C1A472 /* XYPhotoTableCell.m in Sources */, 310 | 6580753A1F6EDB62002408A5 /* XYPhotoMultiCameraPicker.m in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXSourcesBuildPhase section */ 315 | 316 | /* Begin PBXVariantGroup section */ 317 | 651B296C1F6EC1730085BCE3 /* Main.storyboard */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | 651B296D1F6EC1730085BCE3 /* Base */, 321 | ); 322 | name = Main.storyboard; 323 | sourceTree = ""; 324 | }; 325 | 651B29711F6EC1730085BCE3 /* LaunchScreen.storyboard */ = { 326 | isa = PBXVariantGroup; 327 | children = ( 328 | 651B29721F6EC1730085BCE3 /* Base */, 329 | ); 330 | name = LaunchScreen.storyboard; 331 | sourceTree = ""; 332 | }; 333 | /* End PBXVariantGroup section */ 334 | 335 | /* Begin XCBuildConfiguration section */ 336 | 651B29771F6EC1730085BCE3 /* Debug */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_NONNULL = YES; 341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 347 | CLANG_WARN_BOOL_CONVERSION = YES; 348 | CLANG_WARN_COMMA = YES; 349 | CLANG_WARN_CONSTANT_CONVERSION = YES; 350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 365 | CLANG_WARN_UNREACHABLE_CODE = YES; 366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 367 | CODE_SIGN_IDENTITY = "iPhone Developer"; 368 | COPY_PHASE_STRIP = NO; 369 | DEBUG_INFORMATION_FORMAT = dwarf; 370 | ENABLE_STRICT_OBJC_MSGSEND = YES; 371 | ENABLE_TESTABILITY = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu11; 373 | GCC_DYNAMIC_NO_PIC = NO; 374 | GCC_NO_COMMON_BLOCKS = YES; 375 | GCC_OPTIMIZATION_LEVEL = 0; 376 | GCC_PREPROCESSOR_DEFINITIONS = ( 377 | "DEBUG=1", 378 | "$(inherited)", 379 | ); 380 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 381 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 382 | GCC_WARN_UNDECLARED_SELECTOR = YES; 383 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 384 | GCC_WARN_UNUSED_FUNCTION = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 387 | MTL_ENABLE_DEBUG_INFO = YES; 388 | ONLY_ACTIVE_ARCH = YES; 389 | SDKROOT = iphoneos; 390 | }; 391 | name = Debug; 392 | }; 393 | 651B29781F6EC1730085BCE3 /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_ANALYZER_NONNULL = YES; 398 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_COMMA = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INFINITE_RECURSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 416 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 417 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 418 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 419 | CLANG_WARN_STRICT_PROTOTYPES = YES; 420 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 421 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 422 | CLANG_WARN_UNREACHABLE_CODE = YES; 423 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 424 | CODE_SIGN_IDENTITY = "iPhone Developer"; 425 | COPY_PHASE_STRIP = NO; 426 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 427 | ENABLE_NS_ASSERTIONS = NO; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu11; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 432 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 433 | GCC_WARN_UNDECLARED_SELECTOR = YES; 434 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 435 | GCC_WARN_UNUSED_FUNCTION = YES; 436 | GCC_WARN_UNUSED_VARIABLE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 438 | MTL_ENABLE_DEBUG_INFO = NO; 439 | SDKROOT = iphoneos; 440 | VALIDATE_PRODUCT = YES; 441 | }; 442 | name = Release; 443 | }; 444 | 651B297A1F6EC1730085BCE3 /* Debug */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | CODE_SIGN_IDENTITY = "Apple Development"; 449 | CODE_SIGN_STYLE = Automatic; 450 | DEVELOPMENT_TEAM = M932RC5J66; 451 | INFOPLIST_FILE = XYPhotoKitDemo/Info.plist; 452 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 453 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 454 | PRODUCT_BUNDLE_IDENTIFIER = com.xcodeyang.XYPhotoKitDemo; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | PROVISIONING_PROFILE = ""; 457 | PROVISIONING_PROFILE_SPECIFIER = ""; 458 | TARGETED_DEVICE_FAMILY = "1,2"; 459 | }; 460 | name = Debug; 461 | }; 462 | 651B297B1F6EC1730085BCE3 /* Release */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | CODE_SIGN_IDENTITY = "Apple Development"; 467 | CODE_SIGN_STYLE = Automatic; 468 | DEVELOPMENT_TEAM = M932RC5J66; 469 | INFOPLIST_FILE = XYPhotoKitDemo/Info.plist; 470 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 472 | PRODUCT_BUNDLE_IDENTIFIER = com.xcodeyang.XYPhotoKitDemo; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | PROVISIONING_PROFILE = ""; 475 | PROVISIONING_PROFILE_SPECIFIER = ""; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 651B295E1F6EC1730085BCE3 /* Build configuration list for PBXProject "XYPhotoKitDemo" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 651B29771F6EC1730085BCE3 /* Debug */, 487 | 651B29781F6EC1730085BCE3 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | 651B29791F6EC1730085BCE3 /* Build configuration list for PBXNativeTarget "XYPhotoKitDemo" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | 651B297A1F6EC1730085BCE3 /* Debug */, 496 | 651B297B1F6EC1730085BCE3 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | /* End XCConfigurationList section */ 502 | }; 503 | rootObject = 651B295B1F6EC1730085BCE3 /* Project object */; 504 | } 505 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XYPhotoKitDemo 4 | // 5 | // Created by Daniel on 17/09/2017. 6 | // Copyright © 2017 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by Daniel on 17/09/2017. 6 | // Copyright © 2017 Daniel. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Assets.xcassets/add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "add@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Assets.xcassets/add.imageset/add@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhipingYang/XYPhotoKit/c190f037d42e813eb64ec01fe1704894501c724a/XYPhotoKitDemo/XYPhotoKitDemo/Assets.xcassets/add.imageset/add@2x.png -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryUsageDescription 6 | XY调试需要 7 | NSCameraUsageDescription 8 | XY调试需要 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/RootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CLPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RootViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/RootViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // CLPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 30/08/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "RootViewController.h" 10 | #import "XYPhotoMultiCameraPicker.h" 11 | #import "XYPhotoMultiImagePicker.h" 12 | #import "XYDemoCollectionViewCell.h" 13 | 14 | @interface RootViewController () 17 | 18 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 19 | 20 | @property (weak, nonatomic) IBOutlet UISegmentedControl *mediaType; 21 | @property (weak, nonatomic) IBOutlet UITextField *maxNumber; 22 | @property (weak, nonatomic) IBOutlet UITextField *columnNumber; 23 | @property (weak, nonatomic) IBOutlet UISegmentedControl *startFrom; 24 | @property (weak, nonatomic) IBOutlet UISwitch *iCloudRequest; 25 | 26 | @property (nonatomic, strong) NSArray *selectedAssets; 27 | 28 | @end 29 | 30 | @implementation RootViewController 31 | 32 | - (void)showCameraPicker 33 | { 34 | XYPhotoMultiCameraPicker *camera = [[XYPhotoMultiCameraPicker alloc] init]; 35 | camera.pickerDelegate = self; 36 | camera.pickerDataSource = self; 37 | camera.maxNumberOfAssets = [self.maxNumber.text integerValue]; 38 | camera.allowNetRequestIfiCloud = self.iCloudRequest.on; 39 | [self presentViewController:camera animated:YES completion:nil]; 40 | } 41 | 42 | - (void)showImagePicker 43 | { 44 | XYPhotoMultiImagePicker *multiImagePicker = [[XYPhotoMultiImagePicker alloc] init]; 45 | multiImagePicker.pickerDelegate = self; 46 | multiImagePicker.pickerDataSource = self; 47 | 48 | switch (self.mediaType.selectedSegmentIndex) { 49 | case 0: multiImagePicker.mediaType = PHAssetMediaTypeImage; break; 50 | case 1: multiImagePicker.mediaType = PHAssetMediaTypeVideo; break; 51 | default: multiImagePicker.mediaType = PHAssetMediaTypeUnknown; break; 52 | } 53 | 54 | multiImagePicker.maxNumberOfAssets = [self.maxNumber.text integerValue]; 55 | multiImagePicker.assetCollectionViewColumnCount = self.columnNumber.text.integerValue; 56 | multiImagePicker.startPosition = self.startFrom.selectedSegmentIndex==0 ? CLPhotoMultiPickerStartPositionAlbums : CLPhotoMultiPickerStartPositionCameraRoll; 57 | multiImagePicker.allowNetRequestIfiCloud = self.iCloudRequest.on; 58 | [self presentViewController:multiImagePicker animated:YES completion:nil]; 59 | } 60 | 61 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 62 | { 63 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 64 | if (indexPath.row==0 && indexPath.section==0) { 65 | [self showImagePicker]; 66 | } else if(indexPath.row==1 && indexPath.section==0) { 67 | [self showCameraPicker]; 68 | } 69 | } 70 | 71 | #pragma mark - XYPhotoMultiCameraPickerDelegate, XYPhotoMultiCameraPickerDataSource 72 | 73 | - (void)multiCameraPicker:(XYPhotoMultiCameraPicker *)multiImagePicker selectedAssets:(NSArray *)assets 74 | { 75 | _selectedAssets = assets.copy; 76 | [self.collectionView reloadData]; 77 | NSLog(@"%@",assets); 78 | } 79 | 80 | - (NSArray *)multiCameraPickerLoadPresetSelectedAssets 81 | { 82 | return _selectedAssets; 83 | } 84 | 85 | #pragma mark - XYPhotoMultiImagePickerDelegate, XYPhotoMultiImagePickerDataSource 86 | 87 | - (void)multiImagePicker:(XYPhotoMultiImagePicker *)multiImagePicker selectedAssets:(NSArray *)assets 88 | { 89 | _selectedAssets = assets; 90 | [self.collectionView reloadData]; 91 | NSLog(@"%@", @(assets.count)); 92 | for (PHAsset *asset in assets) { 93 | NSLog(@"%@", asset); 94 | } 95 | } 96 | 97 | - (NSArray *)multiImagePickerLoadPresetSelectedAssets 98 | { 99 | return _selectedAssets; 100 | } 101 | 102 | #pragma mark - UICollectionViewDataSource 103 | 104 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 105 | { 106 | return _selectedAssets.count + 1; 107 | } 108 | 109 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 110 | { 111 | XYDemoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([XYDemoCollectionViewCell class]) forIndexPath:indexPath]; 112 | if (indexPath.row == _selectedAssets.count) { 113 | cell.imageVew.image = [UIImage imageNamed:@"add"]; 114 | } else { 115 | PHAsset *asset = _selectedAssets[indexPath.row]; 116 | [[PHImageManager defaultManager] requestImageForAsset:asset 117 | targetSize:CGSizeMake(160, 160) 118 | contentMode:PHImageContentModeAspectFill 119 | options:nil 120 | resultHandler:^(UIImage *result, NSDictionary *info) { 121 | cell.imageVew.image = result; 122 | }]; 123 | } 124 | return cell; 125 | } 126 | 127 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 128 | { 129 | [collectionView deselectItemAtIndexPath:indexPath animated:YES]; 130 | if (indexPath.row == _selectedAssets.count) { 131 | UIAlertController *alrt = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; 132 | [alrt addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]]; 133 | [alrt addAction:[UIAlertAction actionWithTitle:@"相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 134 | [self showCameraPicker]; 135 | }]]; 136 | [alrt addAction:[UIAlertAction actionWithTitle:@"相册" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 137 | [self showImagePicker]; 138 | }]]; 139 | [self presentViewController:alrt animated:YES completion:nil]; 140 | } 141 | } 142 | 143 | @end 144 | 145 | 146 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/XYDemoCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLDemoCollectionViewCell.h 3 | // CLPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 13/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XYDemoCollectionViewCell : UICollectionViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UIImageView *imageVew; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/XYDemoCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLDemoCollectionViewCell.m 3 | // CLPhotoKitDemo 4 | // 5 | // Created by XcodeYang on 13/09/2017. 6 | // Copyright © 2017 XcodeYang. All rights reserved. 7 | // 8 | 9 | #import "XYDemoCollectionViewCell.h" 10 | 11 | @implementation XYDemoCollectionViewCell 12 | 13 | - (void)awakeFromNib 14 | { 15 | [super awakeFromNib]; 16 | self.contentView.layer.borderWidth = 0.5; 17 | self.contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /XYPhotoKitDemo/XYPhotoKitDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XYPhotoKitDemo 4 | // 5 | // Created by Daniel on 17/09/2017. 6 | // Copyright © 2017 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------