├── DBAttachmentPickerController.podspec ├── Example ├── Classes │ ├── NSDateFormatter+DBLibrary.h │ ├── NSDateFormatter+DBLibrary.m │ ├── ViewController.h │ └── ViewController.m ├── DBAttachmentPickerControllerExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ └── denisbogatyrev.xcuserdatad │ │ └── xcschemes │ │ └── DBAttachmentPickerControllerExample.xcscheme ├── DBAttachmentPickerControllerExample │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Common │ │ ├── DBAssetImageView.h │ │ └── DBAssetImageView.m │ ├── DBAttachmentPickerControllerExample.entitlements │ ├── Info.plist │ ├── main.m │ └── ru.lproj │ │ └── LaunchScreen.strings └── Resources │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── AttachFileIcon.imageset │ │ ├── AttachFileIcon.png │ │ ├── AttachFileIcon@2x.png │ │ ├── AttachFileIcon@3x.png │ │ └── Contents.json │ └── Contents.json │ ├── AttachFileIcon.png │ ├── AttachFileIcon@2x.png │ └── AttachFileIcon@3x.png ├── LICENSE ├── README.md ├── Screenshots ├── Screenshot.jpg ├── ezgif.com-gif-maker.gif └── iCloudDocumentsCapability.jpg └── Source ├── Categories ├── NSBundle+DBLibrary.h ├── NSBundle+DBLibrary.m ├── NSIndexSet+DBLibrary.h ├── NSIndexSet+DBLibrary.m ├── UIImage+DBAssetIcons.h └── UIImage+DBAssetIcons.m ├── Cells ├── DBAssetGroupCell.h ├── DBAssetGroupCell.m ├── DBAssetGroupCell.xib ├── DBThumbnailPhotoCell.h ├── DBThumbnailPhotoCell.m └── DBThumbnailPhotoCell.xib ├── Common ├── DBAssetImageView.h └── DBAssetImageView.m ├── DBAssetPickerController ├── DBAssetGroupsViewController.h ├── DBAssetGroupsViewController.m ├── DBAssetGroupsViewController.xib ├── DBAssetItemsViewController.h ├── DBAssetItemsViewController.m ├── DBAssetItemsViewController.xib ├── DBAssetPickerController.h └── DBAssetPickerController.m ├── DBAttachmentAlertController ├── DBAttachmentAlertController.h └── DBAttachmentAlertController.m ├── DBAttachmentPickerController.h ├── DBAttachmentPickerController.m ├── Localization ├── de.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── en.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── es.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── fr.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── ja.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── pl-PL.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── pt-BR.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── ru.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── uk.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict ├── zh-Hans.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict └── zh-Hant.lproj │ ├── DBAttachmentPickerController.strings │ └── DBAttachmentPickerController.stringsdict └── Models ├── DBAttachment.h └── DBAttachment.m /DBAttachmentPickerController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DBAttachmentPickerController' 3 | s.version = '1.1.4' 4 | s.authors = { 'Denis Bogatyrev' => 'denis.bogatyrev@gmail.com' } 5 | s.summary = 'This powerful component allows to select different types of files from different sources on your device' 6 | s.homepage = 'https://github.com/d0ping/DBAttachmentPickerController' 7 | s.license = { :type => 'MIT' } 8 | s.requires_arc = true 9 | s.platform = :ios, '8.0' 10 | s.source = { :git => 'https://github.com/d0ping/DBAttachmentPickerController.git', :tag => "#{s.version}" } 11 | s.source_files = 'Source/**/*.{h,m}' 12 | s.resources = 'Source/**/*.{xib}' 13 | s.resource_bundle = { 'DBAttachmentPickerController' => ['Source/Localization/*.lproj'] } 14 | s.public_header_files = 'Source/**/*.h' 15 | end 16 | -------------------------------------------------------------------------------- /Example/Classes/NSDateFormatter+DBLibrary.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+DBLibrary.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 22.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSDateFormatter (DBLibrary) 12 | 13 | + (NSDateFormatter *)localizedDateTimeFormatter; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/Classes/NSDateFormatter+DBLibrary.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+DBLibrary.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 22.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import "NSDateFormatter+DBLibrary.h" 10 | 11 | @implementation NSDateFormatter (DBLibrary) 12 | 13 | + (NSDateFormatter *)localizedDateTimeFormatter { 14 | static dispatch_once_t onceToken; 15 | static NSDateFormatter *dateFormatter; 16 | dispatch_once(&onceToken, ^{ 17 | dateFormatter = [NSDateFormatter new]; 18 | [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; 19 | [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; 20 | [dateFormatter setLocale:[NSLocale currentLocale]]; 21 | }); 22 | return dateFormatter; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Example/Classes/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 18.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Example/Classes/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 18.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "DBAttachmentPickerController.h" 11 | #import "DBAttachment.h" 12 | #import "NSDateFormatter+DBLibrary.h" 13 | 14 | static NSString *const kAttachmentCellIdentifier = @"AttachmentCellID"; 15 | 16 | @interface AttachmentCell : UITableViewCell 17 | 18 | @property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView; 19 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 20 | @property (weak, nonatomic) IBOutlet UILabel *dateLabel; 21 | @property (weak, nonatomic) IBOutlet UILabel *sizeLabel; 22 | 23 | @end 24 | 25 | @implementation AttachmentCell 26 | 27 | @end 28 | 29 | @interface ViewController () 30 | 31 | @property (strong, nonatomic) NSMutableArray *attachmentArray; 32 | @property (strong, nonatomic) DBAttachmentPickerController *pickerController; 33 | 34 | @end 35 | 36 | @implementation ViewController 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | 41 | self.attachmentArray = [[NSMutableArray alloc] initWithCapacity:100]; 42 | } 43 | 44 | #pragma mark - 45 | 46 | - (IBAction)addAttachmentButtonDidSelect:(UIBarButtonItem *)sender { 47 | UIView *senderView = [sender valueForKey:@"view"]; 48 | __weak typeof(self) weakSelf = self; 49 | DBAttachmentPickerController *attachmentPickerController = [DBAttachmentPickerController attachmentPickerControllerFinishPickingBlock:^(NSArray * _Nonnull attachmentArray) 50 | { 51 | NSMutableArray *indexPathArray = [NSMutableArray arrayWithCapacity:attachmentArray.count]; 52 | NSUInteger currentIndex = weakSelf.attachmentArray.count; 53 | for (NSUInteger i = 0; i < attachmentArray.count; i++) { 54 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:currentIndex+i inSection:0]; 55 | [indexPathArray addObject:indexPath]; 56 | } 57 | [weakSelf.attachmentArray addObjectsFromArray:attachmentArray]; 58 | [weakSelf.tableView insertRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationAutomatic]; 59 | 60 | } cancelBlock:nil]; 61 | 62 | attachmentPickerController.mediaType = DBAttachmentMediaTypeImage | DBAttachmentMediaTypeVideo; 63 | attachmentPickerController.capturedVideoQulity = UIImagePickerControllerQualityTypeHigh; 64 | attachmentPickerController.senderView = senderView; 65 | attachmentPickerController.allowsMultipleSelection = YES; 66 | attachmentPickerController.allowsSelectionFromOtherApps = YES; 67 | 68 | [attachmentPickerController presentOnViewController:self]; 69 | } 70 | 71 | #pragma mark - UITableView DataSource && Delegate 72 | 73 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 74 | return self.attachmentArray.count; 75 | } 76 | 77 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 78 | AttachmentCell *cell = [tableView dequeueReusableCellWithIdentifier:kAttachmentCellIdentifier]; 79 | if (cell == nil) { 80 | cell = [[AttachmentCell alloc] init]; 81 | } 82 | [self configureCell:cell atIndexPath:indexPath]; 83 | return cell; 84 | } 85 | 86 | - (void)configureCell:(AttachmentCell *)cell atIndexPath:(NSIndexPath *)indexPath { 87 | DBAttachment *attachment = self.attachmentArray[indexPath.row]; 88 | 89 | cell.titleLabel.text = attachment.fileName; 90 | cell.sizeLabel.text = attachment.fileSizeStr; 91 | cell.dateLabel.text = [[NSDateFormatter localizedDateTimeFormatter] stringFromDate:attachment.creationDate]; 92 | 93 | CGFloat scale = [UIScreen mainScreen].scale; 94 | CGSize scaledThumbnailSize = CGSizeMake( 80.f * scale, 80.f * scale ); 95 | 96 | [attachment loadThumbnailImageWithTargetSize:scaledThumbnailSize completion:^(UIImage *resultImage) { 97 | cell.thumbnailImageView.image = resultImage; 98 | }]; 99 | } 100 | 101 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 102 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample.xcodeproj/xcuserdata/denisbogatyrev.xcuserdatad/xcschemes/DBAttachmentPickerControllerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 18.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. 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 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 18.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. 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 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/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 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 41 | 50 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/Common/DBAssetImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetImageView.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 30.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface DBAssetImageView : UIImageView 13 | 14 | - (void)configureWithAssetMediaType:(PHAssetMediaType)mediaType subtype:(PHAssetMediaSubtype)mediaSubtype; 15 | - (void)configureCollectionSubtype:(PHAssetCollectionSubtype)collectionSubtype; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/Common/DBAssetImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetImageView.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 30.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | @import Photos; 10 | #import "DBAssetImageView.h" 11 | #import "UIImage+DBAssetIcons.h" 12 | 13 | static const CGFloat kDefaultGradientHeight = 24.f; 14 | static const CGFloat kDefaultMediaTypeIconOffset = 4.f; 15 | static const CGSize kDefaultMediaTypeIconSize = {16.f, 16.f}; 16 | 17 | @interface DBAssetImageView () 18 | 19 | @property (strong, nonatomic) CAGradientLayer *gradient; 20 | @property (strong, nonatomic) UIImageView *mediaTypeImageView; 21 | 22 | @end 23 | 24 | @implementation DBAssetImageView 25 | 26 | - (void)awakeFromNib { 27 | self.mediaTypeImageView = [[UIImageView alloc] init]; 28 | [self addSubview:self.mediaTypeImageView]; 29 | 30 | self.gradient = [CAGradientLayer layer]; 31 | self.gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[[[UIColor blackColor] colorWithAlphaComponent:.75f] CGColor], nil]; 32 | } 33 | 34 | - (void)configureWithAssetMediaType:(PHAssetMediaType)mediaType subtype:(PHAssetMediaSubtype)mediaSubtype { 35 | UIImage *iconImage = nil; 36 | if (mediaSubtype & PHAssetMediaSubtypeVideoHighFrameRate) { 37 | iconImage =[UIImage imageOfVideoHighFrameRateIcon]; 38 | } else if (mediaSubtype & PHAssetMediaSubtypeVideoTimelapse) { 39 | iconImage =[UIImage imageOfVideoTimelapseIcon]; 40 | } else if (mediaSubtype == PHAssetMediaSubtypePhotoPanorama) { 41 | iconImage =[UIImage imageOfSmartAlbumPanoramasIcon]; 42 | } else if (mediaType == PHAssetMediaTypeVideo) { 43 | iconImage =[UIImage imageOfVideoIcon]; 44 | } 45 | 46 | self.mediaTypeImageView.image = iconImage; 47 | if (iconImage) { 48 | [self.layer insertSublayer:self.gradient atIndex:0]; 49 | } else { 50 | [self.gradient removeFromSuperlayer]; 51 | } 52 | } 53 | 54 | - (void)configureCollectionSubtype:(PHAssetCollectionSubtype)collectionSubtype { 55 | UIImage *iconImage = nil; 56 | switch (collectionSubtype) { 57 | case PHAssetCollectionSubtypeSmartAlbumVideos: 58 | iconImage =[UIImage imageOfSmartAlbumVideosIcon]; 59 | break; 60 | case PHAssetCollectionSubtypeSmartAlbumPanoramas: 61 | iconImage =[UIImage imageOfSmartAlbumPanoramasIcon]; 62 | break; 63 | case PHAssetCollectionSubtypeSmartAlbumFavorites: 64 | iconImage =[UIImage imageOfSmartAlbumFavoritesIcon]; 65 | break; 66 | case PHAssetCollectionSubtypeSmartAlbumSlomoVideos: 67 | iconImage =[UIImage imageOfSmartAlbumSlomoVideosIcon]; 68 | break; 69 | case PHAssetCollectionSubtypeSmartAlbumTimelapses: 70 | iconImage =[UIImage imageOfSmartAlbumTimelapsesIcon]; 71 | break; 72 | case PHAssetCollectionSubtypeSmartAlbumSelfPortraits: 73 | iconImage =[UIImage imageOfSmartAlbumSelfPortraitsIcon]; 74 | break; 75 | case PHAssetCollectionSubtypeSmartAlbumBursts: 76 | iconImage =[UIImage imageOfSmartAlbumBurstsIcon]; 77 | break; 78 | case PHAssetCollectionSubtypeSmartAlbumScreenshots: 79 | iconImage =[UIImage imageOfSmartAlbumScreenshotsIcon]; 80 | break; 81 | default: 82 | break; 83 | } 84 | 85 | self.mediaTypeImageView.image = iconImage; 86 | if (iconImage) { 87 | [self.layer insertSublayer:self.gradient atIndex:0]; 88 | } else { 89 | [self.gradient removeFromSuperlayer]; 90 | } 91 | } 92 | 93 | - (void)layoutSubviews { 94 | CGSize iconSize = kDefaultMediaTypeIconSize; 95 | self.mediaTypeImageView.frame = CGRectMake(kDefaultMediaTypeIconOffset, CGRectGetHeight(self.bounds) - iconSize.height - kDefaultMediaTypeIconOffset, iconSize.width, iconSize.height); 96 | self.gradient.frame = CGRectMake(.0f, CGRectGetHeight(self.bounds) - kDefaultGradientHeight, CGRectGetWidth(self.bounds), kDefaultGradientHeight); 97 | } 98 | 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/DBAttachmentPickerControllerExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.icloud-container-identifiers 6 | 7 | iCloud.$(CFBundleIdentifier) 8 | 9 | com.apple.developer.icloud-services 10 | 11 | CloudDocuments 12 | 13 | com.apple.developer.ubiquity-container-identifiers 14 | 15 | iCloud.$(CFBundleIdentifier) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.1.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeRight 37 | UIInterfaceOrientationLandscapeLeft 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 18.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. 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 | -------------------------------------------------------------------------------- /Example/DBAttachmentPickerControllerExample/ru.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon.png -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon@2x.png -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/Assets.xcassets/AttachFileIcon.imageset/AttachFileIcon@3x.png -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AttachFileIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "AttachFileIcon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "AttachFileIcon@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "AttachFileIcon@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Resources/AttachFileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/AttachFileIcon.png -------------------------------------------------------------------------------- /Example/Resources/AttachFileIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/AttachFileIcon@2x.png -------------------------------------------------------------------------------- /Example/Resources/AttachFileIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Example/Resources/AttachFileIcon@3x.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Denis Bogatyrev 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBAttachmentPickerController 2 | [![Version](https://img.shields.io/cocoapods/v/DBAttachmentPickerController.svg?style=flat)](http://cocoadocs.org/docsets/DBAttachmentPickerController) 3 | [![License](https://img.shields.io/cocoapods/l/DBAttachmentPickerController.svg?style=flat)](http://cocoadocs.org/docsets/DBAttachmentPickerController) 4 | [![Platform](https://img.shields.io/cocoapods/p/DBAttachmentPickerController.svg?style=flat)](http://cocoadocs.org/docsets/DBAttachmentPickerController) 5 | ![Language](https://img.shields.io/badge/Language-%20Objective%20C%20-blue.svg) 6 | 7 | This powerful component allows to select different types of files from different sources on your device. 8 | 9 | ![iCloud Documents Capability](Screenshots/Screenshot.jpg) 10 | 11 | ## Preview 12 | 13 | ![iCloud Documents Capability](Screenshots/ezgif.com-gif-maker.gif) 14 | 15 | ## Requirements 16 | 17 | - iOS 8.0+ 18 | 19 | ## Adding to your project 20 | 21 | ### Cocoapods 22 | 23 | To add DBAttachmentPickerController via [CocoaPods](http://cocoapods.org/) into your project: 24 | 25 | 1. Add a pod entry for DBAttachmentPickerController to your Podfile `pod 'DBAttachmentPickerController', '~> 1.1.0'` 26 | 2. Install the pod by running `pod install` 27 | 28 | ### Source Files 29 | 30 | To add DBAttachmentPickerController manually into your project: 31 | 32 | 1. Download the latest code, using `git clone` 33 | 2. Open your project in Xcode, then drag and drop entire contents of the `Source` folder into your project (Make sure to select Copy items when asked if you extracted the code archive outside of your project) 34 | 35 | ## Usage 36 | 37 | To use DBAttachmentPickerController in your project you should perform the following steps: 38 | 39 | 1. Initialize Attachment picker controller (see the [Constructors section](#constructors)) 40 | 2. Specify additional options if nedded (see the [Property list section](#property-list)) 41 | 3. Present Attachment picker controller (see the [Presentation section](#presentation)) 42 | 43 | ```objc 44 | - (void)addAttachment { 45 | // (1) 46 | DBAttachmentPickerController *attachmentPickerController = [DBAttachmentPickerController attachmentPickerControllerFinishPickingBlock:^(NSArray * _Nonnull attachmentArray) {...} cancelBlock:^{...}]; 47 | 48 | // (2) 49 | attachmentPickerController.mediaType = DBAttachmentMediaTypeImage | DBAttachmentMediaTypeVideo; 50 | attachmentPickerController.capturedVideoQulity = UIImagePickerControllerQualityTypeHigh; 51 | attachmentPickerController.allowsMultipleSelection = YES; 52 | attachmentPickerController.allowsSelectionFromOtherApps = YES; 53 | 54 | // (3) 55 | [attachmentPickerController presentOnViewController:self]; 56 | } 57 | ``` 58 | 59 | ### Constructors 60 | 61 | To initialize the attachment picker controller you have to call one of the following methods: 62 | 63 | - `+attachmentPickerControllerFinishPickingBlock:cancelBlock:` - Creates and returns an attachment picker controller. As a result in the finishPickingBlock will be returned array of DBAttachment objects. [DBAttachment](#dbattachment) class provides more opportunities to process selected files but required additional processing to get result; 64 | - `+imagePickerControllerFinishPickingBlock:cancelBlock:` - Creates and returns an attachment picker controller with constant media type (image). Other media types will be ignored. As a result in the finishPickingBlock will be returned array of UIImage objects; 65 | - `+videoPickerControllerFinishPickingBlock:cancelBlock:` - Creates and returns an attachment picker controller with constant media type (video). Other media types will be ignored. As a result in the finishPickingBlock will be returned array of different objects depending on the source. Required additional processing to get result. 66 | 67 | ### Property list 68 | 69 | You can change additional Attachment Picker Controller properties. Full properties list is shown below: 70 | 71 | - `UIView *senderView` - Used to provide opportunity to correctly calculate position popover view when app works on iPad. You can specify UIButton, UITableViewCell, etc. instance to which the user touched. ATTENTION: The parameter must contain only UIView subclass instance or nil; 72 | - `DBAttachmentMediaType mediaType` - It's determine the types of attachments that can be picked. Default is DBAttachmentMediaTypeMaskAll; 73 | - `UIImagePickerControllerQualityType capturedVideoQulity` - Used to determine the quality of the captured video from camera. Default is UIImagePickerControllerQualityTypeMedium; 74 | - `BOOL allowsSelectionFromOtherApps` - Used to add Other Apps button. ATTENTION: To correctly work this option you must select iCloud Documents 75 | capability on project settings. To view detail information, see [Usage Document Picker section](#usage-document-picker-(other-apps-button)). Default is NO. 76 | - `BOOL allowsMultipleSelection` - Used to allow multiple selection where it possible. Default is NO. 77 | 78 | ### Presentation 79 | 80 | After creation Attachment Picker Controller you should set additional options if needed (see the [Property list section](#property-list)) and present it. 81 | 82 | - `-presentOnViewController:` - Present attachment picker controller on specify UIViewController. 83 | 84 | ## Usage Document Picker (Other apps button) 85 | 86 | To usage Document Picker you must: 87 | 88 | - set YES value to `AllowsSelectionFromOtherApps` property 89 | - turn on the iCloud Documents capabilities in Xcode (see image later) 90 | 91 | 92 | 93 | 94 | ## DBAttachment 95 | 96 | The class contain metadata about selected item. 97 | 98 | You can use following properties to get metadata of file: 99 | 100 | - `NSString *fileName` - The name of the file. Can be empty; 101 | - `NSDate *creationDate` - Creation date of the file. Can be nil; 102 | - `NSUInteger fileSize` - Size of the file in byte. Available only for existing files. ATTENTION: If you want get file size for PHAsset or something like that, you should calculate it after getting file data; 103 | - `NSString *fileSizeStr` - Formatted string of file size. Can be empty. 104 | 105 | To get thumbnail image you should call folowing method: `-loadThumbnailImageWithTargetSize:completion:`. Or you can get original image through method `-loadOriginalImageWithCompletion:`. 106 | 107 | Also you can get original file data if call appropriate method `-originalFileResource`. 108 | 109 | ## Version history 110 | 111 | ### 1.1.0 112 | - Added localization. Available following languages: English (Default), Russian, Spanish, German, French, Ukrainian, Chinese (Simplified and Traditional) and Japanese. 113 | - Added description for methods and properties on DBAttachment class. 114 | 115 | ## Contact 116 | 117 | Denis Bogatyrev (maintainer) 118 | 119 | - https://github.com/d0ping 120 | - denis.bogatyrev@gmail.com 121 | 122 | ## License 123 | 124 | DBAttachmentPickerController - Copyright (c) 2016 Denis Bogatyrev 125 | 126 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 127 | 128 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 129 | 130 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 131 | -------------------------------------------------------------------------------- /Screenshots/Screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Screenshots/Screenshot.jpg -------------------------------------------------------------------------------- /Screenshots/ezgif.com-gif-maker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Screenshots/ezgif.com-gif-maker.gif -------------------------------------------------------------------------------- /Screenshots/iCloudDocumentsCapability.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Screenshots/iCloudDocumentsCapability.jpg -------------------------------------------------------------------------------- /Source/Categories/NSBundle+DBLibrary.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+DBLibrary.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 06.08.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | @interface NSBundle (DBLibrary) 25 | 26 | #undef NSLocalizedString 27 | #define NSLocalizedString(key, comment) \ 28 | [NSBundle localizedStringForKey:key value:nil table:@"DBAttachmentPickerController" backupBundle:[NSBundle dbAttachmentPickerResourceBundle]] 29 | 30 | + (instancetype)dbAttachmentPickerBundle; 31 | + (instancetype)dbAttachmentPickerResourceBundle; 32 | + (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName backupBundle:(NSBundle *)bundle; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Source/Categories/NSBundle+DBLibrary.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+DBLibrary.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 06.08.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import "NSBundle+DBLibrary.h" 23 | #import "DBAttachmentPickerController.h" 24 | 25 | @implementation NSBundle (DBLibrary) 26 | 27 | NSString * const kLocalizedStringNotFound = @"kLocalizedStringNotFound"; 28 | 29 | + (instancetype)dbAttachmentPickerBundle { 30 | return [NSBundle bundleForClass:[DBAttachmentPickerController class]]; 31 | } 32 | 33 | + (instancetype)dbAttachmentPickerResourceBundle { 34 | NSBundle *bundle = [NSBundle mainBundle]; 35 | NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"DBAttachmentPickerController" ofType:@"bundle"]; 36 | if (!bundlePath) { 37 | bundlePath = [[NSBundle bundleForClass:[DBAttachmentPickerController class]] pathForResource:@"DBAttachmentPickerController" ofType:@"bundle"]; 38 | } 39 | if (bundlePath) { 40 | bundle = [NSBundle bundleWithPath:bundlePath]; 41 | } 42 | return bundle; 43 | } 44 | 45 | + (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName backupBundle:(NSBundle *)bundle { 46 | // First try main bundle 47 | NSString * string = [[NSBundle mainBundle] localizedStringForKey:key value:kLocalizedStringNotFound table:tableName]; 48 | 49 | // Then try the backup bundle 50 | if ([string isEqualToString:kLocalizedStringNotFound]) { 51 | string = [bundle localizedStringForKey:key value:kLocalizedStringNotFound table:tableName]; 52 | } 53 | 54 | // Still not found? 55 | if ([string isEqualToString:kLocalizedStringNotFound]) { 56 | NSLog(@"No localized string for '%@' in '%@'", key, tableName); 57 | string = value.length > 0 ? value : key; 58 | } 59 | 60 | return string; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Source/Categories/NSIndexSet+DBLibrary.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet+DBLibrary.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 15.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | @interface NSIndexSet (DBLibrary) 25 | 26 | - (NSArray *)indexPathsFromIndexesWithSection:(NSUInteger)section; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Source/Categories/NSIndexSet+DBLibrary.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet+DBLibrary.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 15.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import "NSIndexSet+DBLibrary.h" 23 | #import 24 | 25 | @implementation NSIndexSet (DBLibrary) 26 | 27 | - (NSArray *)indexPathsFromIndexesWithSection:(NSUInteger)section { 28 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count]; 29 | [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 30 | [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]]; 31 | }]; 32 | return indexPaths; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Source/Categories/UIImage+DBAssetIcons.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+DBAssetIcons.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import UIKit; 23 | @import Photos; 24 | 25 | @interface UIImage (DBAssetIcons) 26 | 27 | + (UIImage*)imageOfSmartAlbumBurstsIcon; 28 | + (UIImage*)imageOfSmartAlbumSlomoVideosIcon; 29 | + (UIImage*)imageOfSmartAlbumTimelapsesIcon; 30 | + (UIImage*)imageOfSmartAlbumVideosIcon; 31 | + (UIImage*)imageOfSmartAlbumPanoramasIcon; 32 | + (UIImage*)imageOfSmartAlbumSelfPortraitsIcon; 33 | + (UIImage*)imageOfSmartAlbumFavoritesIcon; 34 | + (UIImage*)imageOfSmartAlbumScreenshotsIcon; 35 | + (UIImage*)imageOfVideoTimelapseIcon; 36 | + (UIImage*)imageOfVideoHighFrameRateIcon; 37 | + (UIImage*)imageOfVideoIcon; 38 | 39 | + (UIImage*)imageOfSelectorOnIconWithTintColor: (UIColor*)tintColor; 40 | + (UIImage*)imageOfSelectorOffIcon; 41 | 42 | + (UIImage*)imageOfFileIconWithExtensionText: (NSString*)text; 43 | 44 | + (UIImage *)placeholderImageWithSize:(CGSize)size; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Source/Cells/DBAssetGroupCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetGroupCell.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | #import "DBAssetImageView.h" 24 | 25 | @interface DBAssetGroupCell : UITableViewCell 26 | 27 | @property (weak, nonatomic) IBOutlet DBAssetImageView *imageViewFront; 28 | @property (weak, nonatomic) IBOutlet UIImageView *imageViewMid; 29 | @property (weak, nonatomic) IBOutlet UIImageView *imageViewBack; 30 | 31 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 32 | @property (weak, nonatomic) IBOutlet UILabel *countLabel; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Source/Cells/DBAssetGroupCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetGroupCell.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import "DBAssetGroupCell.h" 23 | 24 | @implementation DBAssetGroupCell 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Source/Cells/DBAssetGroupCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 64 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Source/Cells/DBThumbnailPhotoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBThumbnailPhotoCell.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | #import 24 | #import "DBAssetImageView.h" 25 | 26 | @interface DBThumbnailPhotoCell : UICollectionViewCell 27 | 28 | @property (weak, nonatomic, nullable) IBOutlet DBAssetImageView *assetImageView; 29 | @property (weak, nonatomic, nullable) IBOutlet UILabel *durationLabel; 30 | 31 | @property (assign, nonatomic) BOOL needsDisplayEmptySelectedIndicator; 32 | @property (copy, nonatomic, nullable) NSString *identifier; 33 | @property (nonatomic) PHImageRequestID phImageRequestID; 34 | 35 | @property (assign, nonatomic) CGFloat selectorOffset; 36 | 37 | + (_Nonnull instancetype)thumbnailImageCell; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Source/Cells/DBThumbnailPhotoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBThumbnailPhotoCell.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import "DBThumbnailPhotoCell.h" 23 | #import "UIImage+DBAssetIcons.h" 24 | #import "NSBundle+DBLibrary.h" 25 | 26 | static const CGFloat kDefaultSelectorImageViewOffset = 4.f; 27 | 28 | @interface DBThumbnailPhotoCell () 29 | 30 | @property (weak, nonatomic) IBOutlet UIImageView *selectorImageView; 31 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *selectorImageViewRightConstraint; 32 | 33 | @end 34 | 35 | @implementation DBThumbnailPhotoCell 36 | 37 | #pragma mark - Class methods 38 | 39 | + (instancetype)thumbnailImageCell { 40 | DBThumbnailPhotoCell *view = [[[NSBundle dbAttachmentPickerBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil] firstObject]; 41 | return view; 42 | } 43 | 44 | #pragma mark - Lifecycle 45 | 46 | - (void)awakeFromNib { 47 | [super awakeFromNib]; 48 | 49 | self.selectorImageView.image = [UIImage imageOfSelectorOffIcon]; 50 | self.selectorImageView.highlightedImage = [UIImage imageOfSelectorOnIconWithTintColor:self.tintColor]; 51 | } 52 | 53 | - (void)prepareForReuse { 54 | [super prepareForReuse]; 55 | self.selectorOffset = 0.f; 56 | } 57 | 58 | #pragma mark - Accessors 59 | 60 | - (void)setTintColor:(UIColor *)tintColor { 61 | [super setTintColor:tintColor]; 62 | [self.selectorImageView setTintColor:tintColor]; 63 | } 64 | 65 | - (void)setSelectorOffset:(CGFloat)selectorOffset { 66 | _selectorOffset = selectorOffset; 67 | 68 | const CGFloat maxOfsset = CGRectGetWidth(self.bounds) - kDefaultSelectorImageViewOffset - CGRectGetWidth(self.selectorImageView.frame); 69 | self.selectorImageViewRightConstraint.constant = MIN(maxOfsset, kDefaultSelectorImageViewOffset + selectorOffset); 70 | } 71 | 72 | - (void)setSelected:(BOOL)selected { 73 | [super setSelected:selected]; 74 | [self updateSelectorIndicatorStateIfNedded]; 75 | } 76 | 77 | - (void)setHighlighted:(BOOL)highlighted { 78 | [super setHighlighted:highlighted]; 79 | [self updateSelectorIndicatorStateIfNedded]; 80 | } 81 | 82 | - (void)setNeedsDisplayEmptySelectedIndicator:(BOOL)needsDisplayEmptySelectedIndicator { 83 | if (_needsDisplayEmptySelectedIndicator != needsDisplayEmptySelectedIndicator) { 84 | _needsDisplayEmptySelectedIndicator = needsDisplayEmptySelectedIndicator; 85 | [self updateSelectorIndicatorStateIfNedded]; 86 | } 87 | } 88 | 89 | #pragma mark Helpers 90 | 91 | - (void)updateSelectorIndicatorStateIfNedded { 92 | self.selectorImageView.highlighted = (self.selected || self.highlighted); 93 | self.selectorImageView.hidden = ( !self.selected && !self.needsDisplayEmptySelectedIndicator); 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /Source/Cells/DBThumbnailPhotoCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Source/Common/DBAssetImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetImageView.h 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 30.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface DBAssetImageView : UIImageView 13 | 14 | - (void)configureWithAssetMediaType:(PHAssetMediaType)mediaType subtype:(PHAssetMediaSubtype)mediaSubtype; 15 | - (void)configureCollectionSubtype:(PHAssetCollectionSubtype)collectionSubtype; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Source/Common/DBAssetImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetImageView.m 3 | // DBAttachmentPickerControllerExample 4 | // 5 | // Created by Denis Bogatyrev on 30.03.16. 6 | // Copyright © 2016 Denis Bogatyrev. All rights reserved. 7 | // 8 | 9 | @import Photos; 10 | #import "DBAssetImageView.h" 11 | #import "UIImage+DBAssetIcons.h" 12 | 13 | static const CGFloat kDefaultGradientHeight = 24.f; 14 | static const CGFloat kDefaultMediaTypeIconOffset = 4.f; 15 | static const CGSize kDefaultMediaTypeIconSize = {16.f, 16.f}; 16 | 17 | @interface DBAssetImageView () 18 | 19 | @property (strong, nonatomic) CAGradientLayer *gradient; 20 | @property (strong, nonatomic) UIImageView *mediaTypeImageView; 21 | 22 | @end 23 | 24 | @implementation DBAssetImageView 25 | 26 | - (void)awakeFromNib { 27 | self.mediaTypeImageView = [[UIImageView alloc] init]; 28 | [self addSubview:self.mediaTypeImageView]; 29 | 30 | self.gradient = [CAGradientLayer layer]; 31 | self.gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[[[UIColor blackColor] colorWithAlphaComponent:.75f] CGColor], nil]; 32 | } 33 | 34 | - (void)configureWithAssetMediaType:(PHAssetMediaType)mediaType subtype:(PHAssetMediaSubtype)mediaSubtype { 35 | UIImage *iconImage = nil; 36 | if (mediaSubtype & PHAssetMediaSubtypeVideoHighFrameRate) { 37 | iconImage =[UIImage imageOfVideoHighFrameRateIcon]; 38 | } else if (mediaSubtype & PHAssetMediaSubtypeVideoTimelapse) { 39 | iconImage =[UIImage imageOfVideoTimelapseIcon]; 40 | } else if (mediaSubtype == PHAssetMediaSubtypePhotoPanorama) { 41 | iconImage =[UIImage imageOfSmartAlbumPanoramasIcon]; 42 | } else if (mediaType == PHAssetMediaTypeVideo) { 43 | iconImage =[UIImage imageOfVideoIcon]; 44 | } 45 | 46 | self.mediaTypeImageView.image = iconImage; 47 | if (iconImage) { 48 | [self.layer insertSublayer:self.gradient atIndex:0]; 49 | } else { 50 | [self.gradient removeFromSuperlayer]; 51 | } 52 | } 53 | 54 | - (void)configureCollectionSubtype:(PHAssetCollectionSubtype)collectionSubtype { 55 | UIImage *iconImage = nil; 56 | switch (collectionSubtype) { 57 | case PHAssetCollectionSubtypeSmartAlbumVideos: 58 | iconImage =[UIImage imageOfSmartAlbumVideosIcon]; 59 | break; 60 | case PHAssetCollectionSubtypeSmartAlbumPanoramas: 61 | iconImage =[UIImage imageOfSmartAlbumPanoramasIcon]; 62 | break; 63 | case PHAssetCollectionSubtypeSmartAlbumFavorites: 64 | iconImage =[UIImage imageOfSmartAlbumFavoritesIcon]; 65 | break; 66 | case PHAssetCollectionSubtypeSmartAlbumSlomoVideos: 67 | iconImage =[UIImage imageOfSmartAlbumSlomoVideosIcon]; 68 | break; 69 | case PHAssetCollectionSubtypeSmartAlbumTimelapses: 70 | iconImage =[UIImage imageOfSmartAlbumTimelapsesIcon]; 71 | break; 72 | case PHAssetCollectionSubtypeSmartAlbumSelfPortraits: 73 | iconImage =[UIImage imageOfSmartAlbumSelfPortraitsIcon]; 74 | break; 75 | case PHAssetCollectionSubtypeSmartAlbumBursts: 76 | iconImage =[UIImage imageOfSmartAlbumBurstsIcon]; 77 | break; 78 | case PHAssetCollectionSubtypeSmartAlbumScreenshots: 79 | iconImage =[UIImage imageOfSmartAlbumScreenshotsIcon]; 80 | break; 81 | default: 82 | break; 83 | } 84 | 85 | self.mediaTypeImageView.image = iconImage; 86 | if (iconImage) { 87 | [self.layer insertSublayer:self.gradient atIndex:0]; 88 | } else { 89 | [self.gradient removeFromSuperlayer]; 90 | } 91 | } 92 | 93 | - (void)layoutSubviews { 94 | CGSize iconSize = kDefaultMediaTypeIconSize; 95 | self.mediaTypeImageView.frame = CGRectMake(kDefaultMediaTypeIconOffset, CGRectGetHeight(self.bounds) - iconSize.height - kDefaultMediaTypeIconOffset, iconSize.width, iconSize.height); 96 | self.gradient.frame = CGRectMake(.0f, CGRectGetHeight(self.bounds) - kDefaultGradientHeight, CGRectGetWidth(self.bounds), kDefaultGradientHeight); 97 | } 98 | 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetGroupsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetGroupsViewController.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | @class DBAssetGroupsViewController; 25 | @protocol DBAssetGroupsViewControllerDelegate 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | @optional 29 | - (void)DBAssetGroupsViewController:(DBAssetGroupsViewController *)controller didSelectAssetColoection:(PHAssetCollection *)assetCollection; 30 | - (void)DBAssetGroupsViewControllerDidCancel:(DBAssetGroupsViewController *)controller; 31 | NS_ASSUME_NONNULL_END 32 | 33 | @end 34 | 35 | @interface DBAssetGroupsViewController : UITableViewController 36 | 37 | @property (weak, nonatomic, nullable) id assetGroupsDelegate; 38 | @property (assign, nonatomic) PHAssetMediaType assetMediaType; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetGroupsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetGroupsViewController.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | #import "DBAssetGroupsViewController.h" 24 | #import "DBAssetItemsViewController.h" 25 | #import "DBAssetGroupCell.h" 26 | #import "UIImage+DBAssetIcons.h" 27 | #import "NSBundle+DBLibrary.h" 28 | 29 | static const CGSize kDefaultThumbnailSize = {68.f, 68.f}; 30 | static NSString *const kAssetGroupsCellIdentifier = @"DBAssetGroupCellID"; 31 | 32 | @interface DBAssetGroupsViewController () 33 | 34 | @property (nonatomic, copy) NSArray *fetchResults; 35 | @property (nonatomic, copy) NSArray *assetCollections; 36 | 37 | @end 38 | 39 | @implementation DBAssetGroupsViewController 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | 44 | self.navigationItem.title = NSLocalizedString(@"Albums", nil); 45 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonDidSelect:)]; 46 | 47 | [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([DBAssetGroupCell class]) bundle:[NSBundle dbAttachmentPickerBundle]] forCellReuseIdentifier:kAssetGroupsCellIdentifier]; 48 | 49 | PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil]; 50 | PHFetchResult *userAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:nil]; 51 | self.fetchResults = @[smartAlbums, userAlbums]; 52 | 53 | [self updateAssetCollections]; 54 | } 55 | 56 | #pragma mark - Actions 57 | 58 | - (void)cancelButtonDidSelect:(UIBarButtonItem *)sender { 59 | if ([self.assetGroupsDelegate respondsToSelector:@selector(DBAssetGroupsViewControllerDidCancel:)]) { 60 | [self.assetGroupsDelegate DBAssetGroupsViewControllerDidCancel:self]; 61 | } 62 | } 63 | 64 | #pragma mark - 65 | 66 | - (void)updateAssetCollections { 67 | // Filter albums 68 | NSArray *assetCollectionSubtypes = @[ 69 | // @(PHAssetCollectionSubtypeAlbumRegular), 70 | // @(PHAssetCollectionSubtypeAlbumImported), 71 | @(PHAssetCollectionSubtypeSmartAlbumUserLibrary), 72 | @(PHAssetCollectionSubtypeAlbumMyPhotoStream), 73 | @(PHAssetCollectionSubtypeSmartAlbumFavorites), 74 | @(PHAssetCollectionSubtypeSmartAlbumSelfPortraits), 75 | @(PHAssetCollectionSubtypeSmartAlbumPanoramas), 76 | @(PHAssetCollectionSubtypeSmartAlbumVideos), 77 | @(PHAssetCollectionSubtypeSmartAlbumSlomoVideos), 78 | @(PHAssetCollectionSubtypeSmartAlbumTimelapses), 79 | @(PHAssetCollectionSubtypeSmartAlbumBursts), 80 | @(PHAssetCollectionSubtypeSmartAlbumScreenshots), 81 | @(PHAssetCollectionSubtypeAlbumCloudShared) 82 | ]; 83 | NSMutableDictionary *smartAlbums = [NSMutableDictionary dictionaryWithCapacity:assetCollectionSubtypes.count]; 84 | NSMutableArray *userAlbums = [NSMutableArray array]; 85 | 86 | for (PHFetchResult *fetchResult in self.fetchResults) { 87 | [fetchResult enumerateObjectsUsingBlock:^(PHAssetCollection *assetCollection, NSUInteger index, BOOL *stop) { 88 | PHAssetCollectionSubtype subtype = assetCollection.assetCollectionSubtype; 89 | 90 | if (subtype == PHAssetCollectionSubtypeAlbumRegular) { 91 | [userAlbums addObject:assetCollection]; 92 | } else if ([assetCollectionSubtypes containsObject:@(subtype)]) { 93 | if (!smartAlbums[@(subtype)]) { 94 | smartAlbums[@(subtype)] = [NSMutableArray array]; 95 | } 96 | [smartAlbums[@(subtype)] addObject:assetCollection]; 97 | } 98 | }]; 99 | } 100 | 101 | NSMutableArray *assetCollections = [NSMutableArray array]; 102 | 103 | // Fetch smart albums 104 | for (NSNumber *assetCollectionSubtype in assetCollectionSubtypes) { 105 | NSArray *collections = smartAlbums[assetCollectionSubtype]; 106 | for (PHAssetCollection *assetCollection in collections) { 107 | 108 | PHFetchOptions *options = [PHFetchOptions new]; 109 | if (self.assetMediaType == PHAssetMediaTypeVideo || self.assetMediaType == PHAssetMediaTypeImage) { 110 | options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", self.assetMediaType]; 111 | } 112 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options]; 113 | 114 | if (fetchResult.count) { 115 | [assetCollections addObject:assetCollection]; 116 | } 117 | } 118 | } 119 | 120 | // Fetch user albums 121 | [assetCollections addObjectsFromArray:userAlbums]; 122 | 123 | self.assetCollections = assetCollections; 124 | } 125 | 126 | #pragma mark - PHPhotoLibraryChangeObserver 127 | 128 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 129 | dispatch_async(dispatch_get_main_queue(), ^{ 130 | // Update fetch results 131 | NSMutableArray *fetchResults = [self.fetchResults mutableCopy]; 132 | 133 | [self.fetchResults enumerateObjectsUsingBlock:^(PHFetchResult *fetchResult, NSUInteger index, BOOL *stop) { 134 | PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:fetchResult]; 135 | 136 | if (changeDetails) { 137 | [fetchResults replaceObjectAtIndex:index withObject:changeDetails.fetchResultAfterChanges]; 138 | } 139 | }]; 140 | 141 | if (![self.fetchResults isEqualToArray:fetchResults]) { 142 | self.fetchResults = fetchResults; 143 | 144 | // Reload albums 145 | [self updateAssetCollections]; 146 | [self.tableView reloadData]; 147 | } 148 | }); 149 | } 150 | 151 | #pragma mark - UITableView DataSource && Delegate 152 | 153 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 154 | return self.assetCollections.count; 155 | } 156 | 157 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 158 | DBAssetGroupCell *cell = [tableView dequeueReusableCellWithIdentifier:kAssetGroupsCellIdentifier]; 159 | if (cell == nil) { 160 | cell = [[DBAssetGroupCell alloc] init]; 161 | } 162 | [self configureCell:cell atIndexPath:indexPath]; 163 | return cell; 164 | } 165 | 166 | - (void)configureCell:(DBAssetGroupCell *)cell atIndexPath:(NSIndexPath *)indexPath { 167 | PHAssetCollection *assetCollection = self.assetCollections[indexPath.row]; 168 | 169 | PHFetchOptions *options = [PHFetchOptions new]; 170 | if (self.assetMediaType == PHAssetMediaTypeVideo || self.assetMediaType == PHAssetMediaTypeImage) { 171 | options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", self.assetMediaType]; 172 | } 173 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options]; 174 | 175 | cell.titleLabel.text = assetCollection.localizedTitle; 176 | cell.countLabel.text = [NSString stringWithFormat:@"%lu", (long)fetchResult.count]; 177 | [cell.imageViewFront configureCollectionSubtype:assetCollection.assetCollectionSubtype]; 178 | 179 | [self configureImageViewForCell:cell atIndexPath:indexPath fetchResult:fetchResult]; 180 | } 181 | 182 | - (void)configureImageViewForCell:(DBAssetGroupCell *)cell atIndexPath:(NSIndexPath *)indexPath fetchResult:(PHFetchResult *)fetchResult { 183 | cell.tag = indexPath.row; 184 | cell.imageViewBack.hidden = (fetchResult.count >= 1 && fetchResult.count <=2); 185 | cell.imageViewMid.hidden = (fetchResult.count == 1); 186 | 187 | PHImageManager *imageManager = [PHImageManager defaultManager]; 188 | CGFloat scale = [UIScreen mainScreen].scale; 189 | CGSize size = CGSizeMake( kDefaultThumbnailSize.width * scale, kDefaultThumbnailSize.height * scale ); 190 | PHImageContentMode contentMode = PHImageContentModeAspectFill; 191 | 192 | if (fetchResult.count >= 3) { 193 | [imageManager requestImageForAsset:fetchResult[fetchResult.count - 3] targetSize:size contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) { 194 | if (cell.tag == indexPath.row) { 195 | cell.imageViewBack.image = result; 196 | } 197 | }]; 198 | } 199 | if (fetchResult.count >= 2) { 200 | [imageManager requestImageForAsset:fetchResult[fetchResult.count - 2] targetSize:size contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) { 201 | if (cell.tag == indexPath.row) { 202 | cell.imageViewMid.image = result; 203 | } 204 | }]; 205 | } 206 | if (fetchResult.count >= 1) { 207 | [imageManager requestImageForAsset:fetchResult[fetchResult.count - 1] targetSize:size contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) { 208 | if (cell.tag == indexPath.row) { 209 | cell.imageViewFront.image = result; 210 | } 211 | }]; 212 | } 213 | if (fetchResult.count == 0) { 214 | UIImage *placeholderImage = [UIImage placeholderImageWithSize:cell.imageViewFront.frame.size]; 215 | cell.imageViewFront.image = placeholderImage; 216 | cell.imageViewMid.image = placeholderImage; 217 | cell.imageViewBack.image = placeholderImage; 218 | } 219 | } 220 | 221 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 222 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 223 | if ([self.assetGroupsDelegate respondsToSelector:@selector(DBAssetGroupsViewController:didSelectAssetColoection:)]) { 224 | PHAssetCollection *assetCollection = self.assetCollections[indexPath.row]; 225 | [self.assetGroupsDelegate DBAssetGroupsViewController:self didSelectAssetColoection:assetCollection]; 226 | } 227 | } 228 | 229 | @end 230 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetGroupsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetItemsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetItemsViewController.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | @class DBAssetItemsViewController, PHAsset; 25 | @protocol DBAssetItemsViewControllerDelegate 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | @optional 29 | - (void)DBAssetItemsViewController:(DBAssetItemsViewController *)controller didFinishPickingAssetArray:(NSArray *)assetArray; 30 | - (BOOL)DBAssetImageViewControllerAllowsMultipleSelection:(DBAssetItemsViewController *)controller; 31 | NS_ASSUME_NONNULL_END 32 | 33 | @end 34 | 35 | @interface DBAssetItemsViewController : UICollectionViewController 36 | 37 | @property (weak, nonatomic, nullable) id assetItemsDelegate; 38 | @property (strong, nonatomic, nonnull) PHAssetCollection *assetCollection; 39 | @property (assign, nonatomic) PHAssetMediaType assetMediaType; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetItemsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetItemsViewController.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | #import "DBAssetItemsViewController.h" 24 | #import "DBThumbnailPhotoCell.h" 25 | #import "NSIndexSet+DBLibrary.h" 26 | #import "NSBundle+DBLibrary.h" 27 | 28 | static const NSInteger kNumberItemsPerRowPortrait = 4; 29 | static const NSInteger kNumberItemsPerRowLandscape = 7; 30 | static const CGFloat kDefaultItemOffset = 1.f; 31 | static NSString *const kPhotoCellIdentifier = @"DBThumbnailPhotoCellID"; 32 | 33 | @interface DBAssetItemsViewController () 34 | 35 | @property (strong, nonatomic) PHFetchResult *assetsFetchResults; 36 | @property (strong, nonatomic) PHCachingImageManager *imageManager; 37 | @property (strong, nonatomic) NSMutableArray *selectedIndexPathArray; 38 | 39 | @end 40 | 41 | @implementation DBAssetItemsViewController 42 | 43 | static NSString * const reuseIdentifier = @"Cell"; 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | 48 | self.selectedIndexPathArray = [NSMutableArray arrayWithCapacity:100]; 49 | 50 | self.navigationItem.title = self.assetCollection.localizedTitle; 51 | 52 | if ([self.assetItemsDelegate respondsToSelector:@selector(DBAssetImageViewControllerAllowsMultipleSelection:)]) { 53 | if ( [self.assetItemsDelegate DBAssetImageViewControllerAllowsMultipleSelection:self] ) { 54 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Attach", nil) style:UIBarButtonItemStyleDone target:self action:@selector(attachButtonDidSelect:)]; 55 | } 56 | } 57 | 58 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init]; 59 | flowLayout.sectionInset = UIEdgeInsetsMake(kDefaultItemOffset, kDefaultItemOffset, kDefaultItemOffset, kDefaultItemOffset); 60 | flowLayout.minimumLineSpacing = kDefaultItemOffset; 61 | flowLayout.minimumInteritemSpacing = kDefaultItemOffset; 62 | self.collectionView.collectionViewLayout = flowLayout; 63 | self.collectionView.allowsMultipleSelection = YES; 64 | 65 | self.imageManager = [[PHCachingImageManager alloc] init]; 66 | [self.imageManager stopCachingImagesForAllAssets]; 67 | 68 | [self.collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([DBThumbnailPhotoCell class]) bundle:[NSBundle dbAttachmentPickerBundle]] forCellWithReuseIdentifier:kPhotoCellIdentifier]; 69 | 70 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 71 | } 72 | 73 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 74 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 75 | [self.collectionView.collectionViewLayout invalidateLayout]; 76 | } 77 | 78 | - (void)dealloc { 79 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 80 | } 81 | 82 | #pragma mark - Actions 83 | 84 | - (void)attachButtonDidSelect:(UIBarButtonItem *)sender { 85 | if ([self.assetItemsDelegate respondsToSelector:@selector(DBAssetItemsViewController:didFinishPickingAssetArray:)]) { 86 | [self.assetItemsDelegate DBAssetItemsViewController:self didFinishPickingAssetArray:[self getSelectedAssetArray]]; 87 | } 88 | } 89 | 90 | #pragma mark Helpers 91 | 92 | - (NSArray *)getSelectedAssetArray { 93 | NSArray *selectedItems = self.selectedIndexPathArray; 94 | NSMutableArray *assetArray = [NSMutableArray arrayWithCapacity:selectedItems.count]; 95 | for (NSIndexPath *indexPath in selectedItems) { 96 | PHAsset *asset = self.assetsFetchResults[indexPath.item]; 97 | [assetArray addObject:asset]; 98 | } 99 | return [assetArray copy]; 100 | } 101 | 102 | #pragma mark - Accessors 103 | 104 | - (void)setAssetCollection:(PHAssetCollection *)assetCollection { 105 | _assetCollection = assetCollection; 106 | 107 | [self updateFetchRequest]; 108 | [self.collectionView reloadData]; 109 | } 110 | 111 | #pragma mark - Fetching Assets 112 | 113 | - (void)updateFetchRequest { 114 | if (self.assetCollection) { 115 | PHFetchOptions *allPhotosOptions = [PHFetchOptions new]; 116 | allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 117 | if (self.assetMediaType == PHAssetMediaTypeVideo || self.assetMediaType == PHAssetMediaTypeImage) { 118 | allPhotosOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", self.assetMediaType]; 119 | } 120 | 121 | self.assetsFetchResults = [PHAsset fetchAssetsInAssetCollection:self.assetCollection options:allPhotosOptions]; 122 | [self.collectionView reloadData]; 123 | } else { 124 | self.assetsFetchResults = nil; 125 | } 126 | } 127 | 128 | #pragma mark - PHPhotoLibraryChangeObserver 129 | 130 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 131 | dispatch_async(dispatch_get_main_queue(), ^{ 132 | PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults]; 133 | 134 | if (collectionChanges) { 135 | self.assetsFetchResults = [collectionChanges fetchResultAfterChanges]; 136 | 137 | if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) { 138 | [self.collectionView reloadData]; 139 | } else { 140 | [self.collectionView performBatchUpdates:^{ 141 | NSIndexSet *removedIndexes = [collectionChanges removedIndexes]; 142 | if ([removedIndexes count]) { 143 | [self.collectionView deleteItemsAtIndexPaths:[removedIndexes indexPathsFromIndexesWithSection:0]]; 144 | } 145 | 146 | NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes]; 147 | if ([insertedIndexes count]) { 148 | [self.collectionView insertItemsAtIndexPaths:[insertedIndexes indexPathsFromIndexesWithSection:0]]; 149 | } 150 | 151 | NSIndexSet *changedIndexes = [collectionChanges changedIndexes]; 152 | if ([changedIndexes count]) { 153 | [self.collectionView reloadItemsAtIndexPaths:[changedIndexes indexPathsFromIndexesWithSection:0]]; 154 | } 155 | } completion:nil]; 156 | } 157 | [self.imageManager stopCachingImagesForAllAssets]; 158 | 159 | for (NSIndexPath *indexPath in [self.collectionView indexPathsForSelectedItems]) { 160 | [self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; 161 | } 162 | [self.selectedIndexPathArray removeAllObjects]; 163 | } 164 | }); 165 | } 166 | 167 | #pragma mark - UICollectionView DataSource && Delegate 168 | 169 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 170 | return self.assetsFetchResults.count; 171 | } 172 | 173 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 174 | DBThumbnailPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kPhotoCellIdentifier forIndexPath:indexPath]; 175 | if (cell == nil) { 176 | cell = [[DBThumbnailPhotoCell alloc] init]; 177 | } 178 | [self configurePhotoCell:cell atIndexPath:indexPath]; 179 | return cell; 180 | } 181 | 182 | - (void)configurePhotoCell:(DBThumbnailPhotoCell *)cell atIndexPath:(NSIndexPath *)indexPath { 183 | PHAsset *asset = self.assetsFetchResults[indexPath.item]; 184 | 185 | cell.tintColor = self.collectionView.tintColor; 186 | cell.identifier = asset.localIdentifier; 187 | cell.needsDisplayEmptySelectedIndicator = NO; 188 | 189 | [self.imageManager cancelImageRequest:cell.phImageRequestID]; 190 | 191 | [cell.assetImageView configureWithAssetMediaType:asset.mediaType subtype:asset.mediaSubtypes]; 192 | 193 | if (asset.mediaType == PHAssetMediaTypeVideo) { 194 | NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; 195 | formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; 196 | formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; 197 | formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond; 198 | cell.durationLabel.text = [formatter stringFromTimeInterval:asset.duration]; 199 | } else { 200 | cell.durationLabel.text = nil; 201 | } 202 | 203 | CGFloat scale = [UIScreen mainScreen].scale; 204 | CGSize size = [self collectionItemCellSizeAtIndexPath:indexPath]; 205 | CGSize scaledThumbnailSize = CGSizeMake( size.width * scale, size.height * scale ); 206 | 207 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 208 | options.resizeMode = PHImageRequestOptionsResizeModeExact; 209 | options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic; 210 | 211 | cell.phImageRequestID = [self.imageManager requestImageForAsset:asset 212 | targetSize:scaledThumbnailSize 213 | contentMode:PHImageContentModeAspectFill 214 | options:options 215 | resultHandler:^(UIImage *result, NSDictionary *info) { 216 | if ([cell.identifier isEqualToString:asset.localIdentifier]) { 217 | cell.assetImageView.image = result; 218 | } 219 | }]; 220 | 221 | } 222 | 223 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 224 | [self.selectedIndexPathArray addObject:indexPath]; 225 | BOOL allowsMultipleSelection = NO; 226 | if ([self.assetItemsDelegate respondsToSelector:@selector(DBAssetImageViewControllerAllowsMultipleSelection:)]) { 227 | allowsMultipleSelection = [self.assetItemsDelegate DBAssetImageViewControllerAllowsMultipleSelection:self]; 228 | } 229 | if ( !allowsMultipleSelection ) { 230 | [self attachButtonDidSelect:nil]; 231 | } 232 | } 233 | 234 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { 235 | [self.selectedIndexPathArray removeObject:indexPath]; 236 | } 237 | 238 | #pragma mark UICollectionViewDelegateFlowLayout 239 | 240 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 241 | return [self collectionItemCellSizeAtIndexPath:indexPath]; 242 | } 243 | 244 | #pragma mark Helpers 245 | 246 | - (CGSize)collectionItemCellSizeAtIndexPath:(NSIndexPath *)indexPath { 247 | if (self.assetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumPanoramas) { 248 | PHAsset *asset = self.assetsFetchResults[indexPath.item]; 249 | const CGFloat coef = (CGFloat)asset.pixelWidth / (CGFloat)asset.pixelHeight; 250 | CGFloat itemWidth = CGRectGetWidth(self.collectionView.bounds) - kDefaultItemOffset *2; 251 | return CGSizeMake( itemWidth, itemWidth / coef ); 252 | } else { 253 | NSInteger numberOfItems = [self numberOfItemsPerRow]; 254 | CGFloat itemWidth = floorf( ( CGRectGetWidth(self.collectionView.bounds) - kDefaultItemOffset * ( numberOfItems + 1 ) ) / numberOfItems ); 255 | return CGSizeMake(itemWidth, itemWidth); 256 | } 257 | } 258 | 259 | - (NSInteger)numberOfItemsPerRow { 260 | UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 261 | const BOOL isLandscape = ( orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight); 262 | return isLandscape ? kNumberItemsPerRowLandscape : kNumberItemsPerRowPortrait; 263 | } 264 | 265 | @end 266 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetItemsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetPickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetPickerController.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | @class DBAssetPickerController, PHAsset; 25 | @protocol DBAssetPickerControllerDelegate 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | @optional 29 | - (void)DBAssetPickerController:(DBAssetPickerController *)controller didFinishPickingAssetArray:(NSArray *)assetArray; 30 | - (void)DBAssetPickerControllerDidCancel:(DBAssetPickerController *)controller; 31 | - (BOOL)DBAssetPickerControllerAllowsMultipleSelection:(DBAssetPickerController *)controller; 32 | NS_ASSUME_NONNULL_END 33 | 34 | @end 35 | 36 | @interface DBAssetPickerController : UINavigationController 37 | 38 | @property (weak, nonatomic, nullable) id assetPickerDelegate; 39 | @property (assign, nonatomic) PHAssetMediaType assetMediaType; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Source/DBAssetPickerController/DBAssetPickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAssetPickerController.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | #import "DBAssetPickerController.h" 24 | #import "DBAssetGroupsViewController.h" 25 | #import "DBAssetItemsViewController.h" 26 | #import "NSBundle+DBLibrary.h" 27 | 28 | @interface DBAssetPickerController () 29 | 30 | @end 31 | 32 | @implementation DBAssetPickerController 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | DBAssetGroupsViewController *groupController = [[DBAssetGroupsViewController alloc] initWithNibName:NSStringFromClass([DBAssetGroupsViewController class]) bundle:[NSBundle dbAttachmentPickerBundle]]; 38 | groupController.assetMediaType = self.assetMediaType; 39 | groupController.assetGroupsDelegate = self; 40 | [self setViewControllers:@[groupController]]; 41 | } 42 | 43 | #pragma mark - DBAssetGroupsViewControllerDelegate 44 | 45 | - (void)DBAssetGroupsViewController:(DBAssetGroupsViewController *)controller didSelectAssetColoection:(PHAssetCollection *)assetCollection { 46 | DBAssetItemsViewController *itemsController = [[DBAssetItemsViewController alloc] initWithNibName:NSStringFromClass([DBAssetItemsViewController class]) bundle:[NSBundle dbAttachmentPickerBundle]]; 47 | itemsController.assetMediaType = self.assetMediaType; 48 | itemsController.assetItemsDelegate = self; 49 | itemsController.assetCollection = assetCollection; 50 | [self pushViewController:itemsController animated:YES]; 51 | } 52 | 53 | - (void)DBAssetGroupsViewControllerDidCancel:(DBAssetGroupsViewController *)controller { 54 | if ([self.assetPickerDelegate respondsToSelector:@selector(DBAssetPickerControllerDidCancel:)]) { 55 | [self.assetPickerDelegate DBAssetPickerControllerDidCancel:self]; 56 | } 57 | } 58 | 59 | #pragma mark = DBAssetItemsViewControllerDelegate 60 | 61 | - (void)DBAssetItemsViewController:(nonnull DBAssetItemsViewController *)controller didFinishPickingAssetArray:(nonnull NSArray *)assetArray { 62 | if ([self.assetPickerDelegate respondsToSelector:@selector(DBAssetPickerController:didFinishPickingAssetArray:)]) { 63 | [self.assetPickerDelegate DBAssetPickerController:self didFinishPickingAssetArray:assetArray]; 64 | } 65 | } 66 | 67 | - (BOOL)DBAssetImageViewControllerAllowsMultipleSelection:(DBAssetItemsViewController *)controller { 68 | BOOL allowsMultipleSelection = NO; 69 | if ([self.assetPickerDelegate respondsToSelector:@selector(DBAssetPickerControllerAllowsMultipleSelection:)]) { 70 | allowsMultipleSelection = [self.assetPickerDelegate DBAssetPickerControllerAllowsMultipleSelection:self]; 71 | } 72 | return allowsMultipleSelection; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Source/DBAttachmentAlertController/DBAttachmentAlertController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachmentAlertController.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | typedef void (^AlertAttachAssetsHandler)(NSArray * _Nonnull assetArray); 25 | typedef void (^AlertActionHandler)(UIAlertAction * _Nonnull action); 26 | 27 | @interface DBAttachmentAlertController : UIAlertController 28 | 29 | @property (assign, nonatomic, readonly) PHAssetMediaType assetMediaType; 30 | @property (assign, nonatomic, readonly) BOOL allowsMultipleSelection; 31 | 32 | + (_Nonnull instancetype)attachmentAlertControllerWithMediaType:(PHAssetMediaType)assetMediaType 33 | allowsMultipleSelection:(BOOL)allowsMultipleSelection 34 | allowsMediaLibrary:(BOOL)allowsPhotoOrVideo 35 | allowsOtherApps:(BOOL)allowsOtherApps 36 | attachHandler:(nullable AlertAttachAssetsHandler)attachHandler 37 | allAlbumsHandler:(nullable AlertActionHandler)allAlbumsHandler 38 | takePictureHandler:(nullable AlertActionHandler)takePictureHandler 39 | otherAppsHandler:(nullable AlertActionHandler)otherAppsHandler 40 | cancelHandler:(nullable AlertActionHandler)cancelHandler; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Source/DBAttachmentAlertController/DBAttachmentAlertController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachmentAlertController.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | #import "DBAttachmentAlertController.h" 24 | #import "DBAttachmentPickerController.h" 25 | #import "DBThumbnailPhotoCell.h" 26 | #import "NSIndexSet+DBLibrary.h" 27 | 28 | static const CGFloat kDefaultThumbnailHeight = 100.f; 29 | static const CGFloat kDefaultItemOffset = 11.f; 30 | static const CGFloat kDefaultInteritemSpacing = 4.f; 31 | static NSString *const kPhotoCellIdentifier = @"DBThumbnailPhotoCellID"; 32 | 33 | @interface DBAttachmentAlertController () 34 | 35 | @property (assign, nonatomic) BOOL showCollectionView; 36 | @property (strong, nonatomic) UICollectionView *collectionView; 37 | @property (copy, nonatomic) NSString *attachActionText; 38 | 39 | @property (strong, nonatomic) PHFetchResult *assetsFetchResult; 40 | @property (strong, nonatomic) PHCachingImageManager *imageManager; 41 | 42 | @property (assign, nonatomic) PHAssetMediaType assetMediaType; 43 | @property (assign, nonatomic) BOOL needsDisplayEmptySelectedIndicator; 44 | @property (assign, nonatomic) BOOL allowsMultipleSelection; 45 | @property (strong, nonatomic) NSMutableArray *selectedIndexPathArray; 46 | 47 | @property (strong, nonatomic) AlertAttachAssetsHandler extensionAttachHandler; 48 | 49 | @end 50 | 51 | @implementation DBAttachmentAlertController 52 | 53 | #pragma mark - Class methods 54 | 55 | + (_Nonnull instancetype)attachmentAlertControllerWithMediaType:(PHAssetMediaType)assetMediaType 56 | allowsMultipleSelection:(BOOL)allowsMultipleSelection 57 | allowsMediaLibrary:(BOOL)allowsPhotoOrVideo 58 | allowsOtherApps:(BOOL)allowsOtherApps 59 | attachHandler:(nullable AlertAttachAssetsHandler)attachHandler 60 | allAlbumsHandler:(nullable AlertActionHandler)allAlbumsHandler 61 | takePictureHandler:(nullable AlertActionHandler)takePictureHandler 62 | otherAppsHandler:(nullable AlertActionHandler)otherAppsHandler 63 | cancelHandler:(nullable AlertActionHandler)cancelHandler 64 | { 65 | const BOOL showPhotoOrVideo = allowsPhotoOrVideo; 66 | 67 | DBAttachmentAlertController *controller = [DBAttachmentAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; 68 | controller.assetMediaType = assetMediaType; 69 | controller.allowsMultipleSelection = allowsMultipleSelection; 70 | controller.showCollectionView = ( showPhotoOrVideo && controller.assetsFetchResult.count ); 71 | controller.title = ( controller.showCollectionView ? @"\n\n\n\n\n" : NSLocalizedString(@"Attach files", @"Title") ); 72 | 73 | if ( showPhotoOrVideo && controller.assetsFetchResult.count ) { 74 | __weak DBAttachmentAlertController *weakController = controller; 75 | UIAlertAction *attachAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"All albums", @"Button on main menu") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 76 | if ([weakController.collectionView indexPathsForSelectedItems].count) { 77 | if (attachHandler) { 78 | attachHandler([weakController getSelectedAssetArray]); 79 | } 80 | } else if (allAlbumsHandler) { 81 | allAlbumsHandler(action); 82 | } 83 | }]; 84 | [controller addAction:attachAction]; 85 | controller.attachActionText = attachAction.title; 86 | 87 | controller.extensionAttachHandler = ^(NSArray * _Nonnull assetArray) { 88 | [weakController dismissViewControllerAnimated:YES completion: ^{ 89 | if (attachHandler) { 90 | attachHandler(assetArray); 91 | } 92 | }]; 93 | }; 94 | } 95 | 96 | if ( showPhotoOrVideo ) { 97 | NSString *buttonTitle; 98 | switch (controller.assetMediaType) { 99 | case PHAssetMediaTypeVideo: 100 | buttonTitle = NSLocalizedString(@"Take a video", @"Button on main menu"); 101 | break; 102 | case PHAssetMediaTypeImage: 103 | buttonTitle = NSLocalizedString(@"Take a picture", @"Button on main menu"); 104 | break; 105 | default: 106 | buttonTitle = NSLocalizedString(@"Take a picture or a video", @"Button on main menu"); 107 | break; 108 | } 109 | UIAlertAction *cameraAction = [UIAlertAction actionWithTitle:buttonTitle style:UIAlertActionStyleDefault handler:takePictureHandler]; 110 | [controller addAction:cameraAction]; 111 | } 112 | 113 | if (allowsOtherApps) { 114 | UIAlertAction *otherAppsAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Other apps", @"Button on main menu") style:UIAlertActionStyleDefault handler:otherAppsHandler]; 115 | [controller addAction:otherAppsAction]; 116 | } 117 | 118 | UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Common") style:UIAlertActionStyleCancel handler:cancelHandler]; 119 | [controller addAction:actionCancel]; 120 | 121 | return controller; 122 | } 123 | 124 | #pragma mark - Lifecycle 125 | 126 | - (void)viewDidLoad { 127 | [super viewDidLoad]; 128 | 129 | self.selectedIndexPathArray = [NSMutableArray arrayWithCapacity:100]; 130 | 131 | if (self.showCollectionView) { 132 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init]; 133 | [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; 134 | flowLayout.sectionInset = UIEdgeInsetsMake(.0f, kDefaultItemOffset, .0f, kDefaultItemOffset); 135 | flowLayout.minimumInteritemSpacing = kDefaultInteritemSpacing; 136 | 137 | CGRect collectionRect = CGRectMake(.0f, .0f, self.view.bounds.size.width, kDefaultThumbnailHeight + kDefaultItemOffset *2); 138 | self.collectionView = [[UICollectionView alloc] initWithFrame:collectionRect collectionViewLayout:flowLayout]; 139 | self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 140 | self.collectionView.backgroundColor = [UIColor clearColor]; 141 | self.collectionView.allowsMultipleSelection = YES; 142 | self.collectionView.delegate = self; 143 | self.collectionView.dataSource = self; 144 | self.collectionView.tintColor = [[[UIApplication sharedApplication] delegate] window].tintColor; 145 | 146 | [self.collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([DBThumbnailPhotoCell class]) bundle:[NSBundle dbAttachmentPickerBundle]] forCellWithReuseIdentifier:kPhotoCellIdentifier]; 147 | 148 | [self.view addSubview:self.collectionView]; 149 | 150 | self.imageManager = [[PHCachingImageManager alloc] init]; 151 | [self.imageManager stopCachingImagesForAllAssets]; 152 | 153 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 154 | } 155 | } 156 | 157 | - (void)viewDidAppear:(BOOL)animated { 158 | [super viewDidAppear:animated]; 159 | [self recalculateVisibleCellsSelectorOffsetWithScrollViewOffsetX:self.collectionView.contentOffset.x]; 160 | } 161 | 162 | - (void)dealloc { 163 | if (self.showCollectionView) { 164 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 165 | } 166 | } 167 | 168 | #pragma mark Helpers 169 | 170 | - (PHFetchResult *)assetsFetchResult { 171 | if (_assetsFetchResult == nil) { 172 | PHFetchOptions *allPhotosOptions = [PHFetchOptions new]; 173 | allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 174 | 175 | if (self.assetMediaType == PHAssetMediaTypeImage || self.assetMediaType == PHAssetMediaTypeVideo) { 176 | allPhotosOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", self.assetMediaType]; 177 | _assetsFetchResult = [PHAsset fetchAssetsWithMediaType:self.assetMediaType options:allPhotosOptions]; 178 | } else { 179 | _assetsFetchResult = [PHAsset fetchAssetsWithOptions:allPhotosOptions]; 180 | } 181 | } 182 | return _assetsFetchResult; 183 | } 184 | 185 | - (NSArray *)getSelectedAssetArray { 186 | NSArray *selectedItems = self.selectedIndexPathArray; 187 | NSMutableArray *assetArray = [NSMutableArray arrayWithCapacity:selectedItems.count]; 188 | for (NSIndexPath *indexPath in selectedItems) { 189 | PHAsset *asset = self.assetsFetchResult[indexPath.item]; 190 | [assetArray addObject:asset]; 191 | } 192 | return [assetArray copy]; 193 | } 194 | 195 | #pragma mark - Accessors 196 | 197 | - (void)setNeedsDisplayEmptySelectedIndicator:(BOOL)needsDisplayEmptySelectedIndicator { 198 | if (_needsDisplayEmptySelectedIndicator != needsDisplayEmptySelectedIndicator) { 199 | _needsDisplayEmptySelectedIndicator = needsDisplayEmptySelectedIndicator; 200 | 201 | for (DBThumbnailPhotoCell *cell in self.collectionView.visibleCells) { 202 | cell.needsDisplayEmptySelectedIndicator = needsDisplayEmptySelectedIndicator; 203 | } 204 | } 205 | } 206 | 207 | - (void)setAttachActionText:(NSString *)attachActionText { 208 | if (![_attachActionText isEqualToString:attachActionText]) { 209 | if (self.isViewLoaded) { 210 | UILabel *attachLabel = [self attachActionLabelForView:self.view]; 211 | attachLabel.text = [attachActionText copy]; 212 | } 213 | 214 | _attachActionText = attachActionText; 215 | } 216 | } 217 | 218 | #pragma mark Helpers 219 | 220 | - (UILabel *)attachActionLabelForView:(UIView *)baseView { 221 | UILabel *label = nil; 222 | if ([baseView isKindOfClass:[UILabel class]] && [((UILabel *)baseView).text isEqualToString:self.attachActionText]) { 223 | label = (UILabel *)baseView; 224 | } else if (baseView.subviews.count > 0) { 225 | for (UIView *subview in baseView.subviews) { 226 | label = [self attachActionLabelForView:subview]; 227 | if (label) break; 228 | } 229 | } 230 | return label; 231 | } 232 | 233 | #pragma mark - PHPhotoLibraryChangeObserver 234 | 235 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 236 | dispatch_async(dispatch_get_main_queue(), ^{ 237 | PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResult]; 238 | 239 | if (collectionChanges) { 240 | self.assetsFetchResult = [collectionChanges fetchResultAfterChanges]; 241 | 242 | if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) { 243 | [self.collectionView reloadData]; 244 | } else { 245 | [self.collectionView performBatchUpdates:^{ 246 | NSIndexSet *removedIndexes = [collectionChanges removedIndexes]; 247 | if ([removedIndexes count]) { 248 | [self.collectionView deleteItemsAtIndexPaths:[removedIndexes indexPathsFromIndexesWithSection:0]]; 249 | } 250 | 251 | NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes]; 252 | if ([insertedIndexes count]) { 253 | [self.collectionView insertItemsAtIndexPaths:[insertedIndexes indexPathsFromIndexesWithSection:0]]; 254 | } 255 | 256 | NSIndexSet *changedIndexes = [collectionChanges changedIndexes]; 257 | if ([changedIndexes count]) { 258 | [self.collectionView reloadItemsAtIndexPaths:[changedIndexes indexPathsFromIndexesWithSection:0]]; 259 | } 260 | } completion:nil]; 261 | } 262 | [self.imageManager stopCachingImagesForAllAssets]; 263 | 264 | for (NSIndexPath *indexPath in [self.collectionView indexPathsForSelectedItems]) { 265 | [self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; 266 | } 267 | [self.selectedIndexPathArray removeAllObjects]; 268 | [self updateAttachPhotoCountIfNedded]; 269 | } 270 | }); 271 | } 272 | 273 | #pragma mark - UICollectionView DataSource && Delegate 274 | 275 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 276 | return self.assetsFetchResult.count; 277 | } 278 | 279 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 280 | DBThumbnailPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kPhotoCellIdentifier forIndexPath:indexPath]; 281 | if (cell == nil) { 282 | cell = [DBThumbnailPhotoCell thumbnailImageCell]; 283 | } 284 | [self configurePhotoCell:cell atIndexPath:indexPath]; 285 | return cell; 286 | } 287 | 288 | - (void)configurePhotoCell:(DBThumbnailPhotoCell *)cell atIndexPath:(NSIndexPath *)indexPath { 289 | PHAsset *asset = self.assetsFetchResult[indexPath.item]; 290 | 291 | cell.tintColor = self.collectionView.tintColor; 292 | cell.identifier = asset.localIdentifier; 293 | cell.needsDisplayEmptySelectedIndicator = self.needsDisplayEmptySelectedIndicator; 294 | [cell.assetImageView configureWithAssetMediaType:asset.mediaType subtype:asset.mediaSubtypes]; 295 | 296 | if (asset.mediaType == PHAssetMediaTypeVideo) { 297 | NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; 298 | formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; 299 | formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; 300 | formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond; 301 | cell.durationLabel.text = [formatter stringFromTimeInterval:asset.duration]; 302 | } else { 303 | cell.durationLabel.text = nil; 304 | } 305 | 306 | CGFloat scale = [UIScreen mainScreen].scale; 307 | CGSize cellSize = [self collectionItemCellSizeAtIndexPath:indexPath]; 308 | CGSize scaledThumbnailSize = CGSizeMake( cellSize.width * scale, cellSize.height * scale ); 309 | 310 | [self.imageManager requestImageForAsset:asset 311 | targetSize:scaledThumbnailSize 312 | contentMode:PHImageContentModeAspectFill 313 | options:nil 314 | resultHandler:^(UIImage *result, NSDictionary *info) { 315 | if ([cell.identifier isEqualToString:asset.localIdentifier]) { 316 | cell.assetImageView.image = result; 317 | } 318 | }]; 319 | } 320 | 321 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 322 | [self.selectedIndexPathArray addObject:indexPath]; 323 | if (self.allowsMultipleSelection) { 324 | [self updateAttachPhotoCountIfNedded]; 325 | self.needsDisplayEmptySelectedIndicator = YES; 326 | } else { 327 | self.extensionAttachHandler([self getSelectedAssetArray]); 328 | } 329 | } 330 | 331 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { 332 | [self.selectedIndexPathArray removeObject:indexPath]; 333 | [self updateAttachPhotoCountIfNedded]; 334 | } 335 | 336 | #pragma mark Helpers 337 | 338 | - (void)updateAttachPhotoCountIfNedded { 339 | NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems]; 340 | self.attachActionText = ( selectedItems.count ? [NSString stringWithFormat:NSLocalizedString(@"Attach %zd file(s)", @"Button on main menu"), selectedItems.count] : NSLocalizedString(@"All albums", nil) ); 341 | } 342 | 343 | #pragma mark UICollectionViewDelegateFlowLayout 344 | 345 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 346 | return [self collectionItemCellSizeAtIndexPath:indexPath]; 347 | } 348 | 349 | #pragma mark - UIScrollViewDelegate 350 | 351 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 352 | [self recalculateVisibleCellsSelectorOffsetWithScrollViewOffsetX:scrollView.contentOffset.x]; 353 | } 354 | 355 | #pragma mark Helpers 356 | 357 | - (CGSize)collectionItemCellSizeAtIndexPath:(NSIndexPath *)indexPath { 358 | PHAsset *asset = self.assetsFetchResult[indexPath.item]; 359 | const CGFloat coef = (CGFloat)asset.pixelWidth / (CGFloat)asset.pixelHeight; 360 | CGFloat maxWidth = CGRectGetWidth(self.collectionView.bounds) - kDefaultInteritemSpacing *2; 361 | return CGSizeMake( MIN( kDefaultThumbnailHeight * coef, maxWidth ), kDefaultThumbnailHeight ); 362 | } 363 | 364 | - (void)recalculateVisibleCellsSelectorOffsetWithScrollViewOffsetX:(CGFloat)offsetX { 365 | for (DBThumbnailPhotoCell *cell in self.collectionView.visibleCells) { 366 | BOOL isLastItem = ( offsetX + CGRectGetWidth(self.collectionView.frame) < CGRectGetMaxX(cell.frame) ); 367 | cell.selectorOffset = ( isLastItem ? CGRectGetMaxX(cell.frame) - ( offsetX + CGRectGetWidth(self.collectionView.frame) ) : .0f); 368 | } 369 | } 370 | 371 | @end 372 | -------------------------------------------------------------------------------- /Source/DBAttachmentPickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachmentPickerController.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | #import "NSBundle+DBLibrary.h" 24 | 25 | typedef NS_OPTIONS(NSUInteger, DBAttachmentMediaType) { 26 | DBAttachmentMediaTypeImage = (1 << 0), 27 | DBAttachmentMediaTypeVideo = (1 << 1), 28 | DBAttachmentMediaTypeOther = (1 << 2), 29 | }; 30 | 31 | UIKIT_EXTERN const DBAttachmentMediaType DBAttachmentMediaTypeMaskAll; 32 | 33 | NS_ASSUME_NONNULL_BEGIN 34 | 35 | @class DBAttachment; 36 | 37 | typedef void (^FinishPickingBlock)(NSArray * attachmentArray); 38 | typedef void (^FinishImagePickingBlock)(NSArray * imageArray); 39 | typedef void (^FinishVideoPickingBlock)(NSArray* resourceArray); 40 | typedef void (^CancelBlock)(); 41 | 42 | @interface DBAttachmentPickerController : NSObject 43 | 44 | /*! 45 | @brief Used to provide opportunity to correctly calculate position popover view when app works on iPad. You can specify UIButton, UITableViewCell, etc. instance to which the user touched. 46 | @attention The parameter must contain only UIView subclass instance or nil 47 | */ 48 | @property (weak, nonatomic) UIView *senderView; 49 | 50 | /*! 51 | @brief Used to determine the types of attachments that can be picked 52 | @note You can specify one or more than one from following types:
53 | ● @a DBAttachmentMediaTypePhoto - photo from camera, camera roll or other apps;
54 | ● @a DBAttachmentMediaTypeVideo - video from camera, camera roll or other apps;
55 | ● @a DBAttachmentMediaTypeOther - any files from other apps.
56 | Also you can use @a DBAttachmentMediaTypeMaskAll to select all available types. 57 | */ 58 | @property (assign, nonatomic) DBAttachmentMediaType mediaType; // default is DBAttachmentMediaTypeMaskAll 59 | 60 | /*! 61 | @brief Used to determine the quality of the captured video from camera 62 | */ 63 | @property (assign, nonatomic) UIImagePickerControllerQualityType capturedVideoQulity; // default is UIImagePickerControllerQualityTypeMedium 64 | 65 | /*! 66 | @brief Used to add Other Apps button 67 | @attention To correctly work this option you must select iCloud Documents capability on project settings. 68 | To view detail information, see README.md. 69 | */ 70 | @property (assign, nonatomic) BOOL allowsSelectionFromOtherApps; // default is NO 71 | 72 | /*! 73 | @brief Used to allow multiple selection where it possible 74 | */ 75 | @property (assign, nonatomic) BOOL allowsMultipleSelection; // default is NO 76 | 77 | /*! 78 | @brief Creates and returns an attachment picker controller 79 | @see presentOnViewController: 80 | @param finishPickingBlock The block will be performed when user select attachment(s) 81 | @param cancelBlock The block will be performed when user select cancel button 82 | @return An instance attachment picker controller 83 | */ 84 | + (instancetype)attachmentPickerControllerFinishPickingBlock:(FinishPickingBlock)finishPickingBlock 85 | cancelBlock:(_Nullable CancelBlock)cancelBlock; 86 | 87 | /*! 88 | @brief Creates and returns an attachment picker controller with constant media type (image) 89 | @see presentOnViewController: 90 | @param finishPickingBlock The block will be performed when user select image(s) 91 | @param cancelBlock The block will be performed when user select cancel button 92 | @return An instance attachment picker controller 93 | */ 94 | + (instancetype)imagePickerControllerFinishPickingBlock:(FinishImagePickingBlock)finishPickingBlock 95 | cancelBlock:(_Nullable CancelBlock)cancelBlock; 96 | 97 | /*! 98 | @brief Creates and returns an attachment picker controller with constant media type (video) 99 | @see presentOnViewController: 100 | @param finishPickingBlock The block will be performed when user select video(s) 101 | @param cancelBlock The block will be performed when user select cancel button 102 | @return An instance attachment picker controller 103 | */ 104 | + (instancetype)videoPickerControllerFinishPickingBlock:(FinishVideoPickingBlock)finishPickingBlock 105 | cancelBlock:(_Nullable CancelBlock)cancelBlock; 106 | 107 | /*! 108 | @brief Present attachment picker controller on specify UIViewController 109 | @param initialViewController The view controller to present the attachment picker controller 110 | */ 111 | - (void)presentOnViewController:(UIViewController *)initialViewController; 112 | 113 | @end 114 | 115 | NS_ASSUME_NONNULL_END 116 | -------------------------------------------------------------------------------- /Source/DBAttachmentPickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachmentPickerController.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | #import 24 | #import "DBAttachmentPickerController.h" 25 | #import "DBAttachmentAlertController.h" 26 | #import "DBAttachment.h" 27 | #import "DBAssetPickerController.h" 28 | 29 | const DBAttachmentMediaType DBAttachmentMediaTypeMaskAll = DBAttachmentMediaTypeImage | DBAttachmentMediaTypeVideo | DBAttachmentMediaTypeOther; 30 | 31 | @interface DBAttachmentPickerController () 32 | 33 | @property (strong, nonatomic) UIViewController *initialViewController; 34 | @property (strong, nonatomic) DBAttachmentAlertController *alertController; 35 | 36 | @property (strong, nonatomic) FinishPickingBlock extendedFinishPickingBlock; 37 | @property (strong, nonatomic) CancelBlock extendedCancelBlock; 38 | 39 | @property (assign, nonatomic) BOOL ignoreChangeMediaType; 40 | 41 | @end 42 | 43 | @implementation DBAttachmentPickerController 44 | 45 | #pragma mark - Class methods 46 | 47 | + (instancetype)attachmentPickerControllerFinishPickingBlock:(FinishPickingBlock)finishPickingBlock 48 | cancelBlock:(_Nullable CancelBlock)cancelBlock 49 | { 50 | DBAttachmentPickerController *controller = [[DBAttachmentPickerController alloc] init]; 51 | controller.mediaType = DBAttachmentMediaTypeMaskAll; 52 | controller.allowsSelectionFromOtherApps = NO; 53 | controller.allowsMultipleSelection = NO; 54 | controller.capturedVideoQulity = UIImagePickerControllerQualityTypeMedium; 55 | 56 | #pragma clang diagnostic push 57 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 58 | controller.extendedFinishPickingBlock = ^void(NSArray * attachmentArray) { 59 | if (finishPickingBlock) finishPickingBlock(attachmentArray); 60 | [controller dismissAttachmentPicker]; 61 | }; 62 | controller.extendedCancelBlock = ^void() { 63 | if (cancelBlock) cancelBlock(); 64 | [controller dismissAttachmentPicker]; 65 | }; 66 | #pragma clang diagnostic pop 67 | 68 | return controller; 69 | } 70 | 71 | + (instancetype)imagePickerControllerFinishPickingBlock:(FinishImagePickingBlock)finishPickingBlock 72 | cancelBlock:(_Nullable CancelBlock)cancelBlock 73 | { 74 | void (^finishBlock)(NSArray * _Nonnull attachmentArray) = ^(NSArray * _Nonnull attachmentArray) { 75 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 76 | dispatch_group_t loadGroup = dispatch_group_create(); 77 | 78 | NSMutableDictionary *imageDict = [NSMutableDictionary dictionaryWithCapacity:attachmentArray.count]; 79 | for (DBAttachment *attachment in attachmentArray) { 80 | dispatch_group_enter(loadGroup); 81 | [attachment loadOriginalImageWithCompletion:^(UIImage * _Nullable resultImage) { 82 | if (resultImage) { 83 | [imageDict setObject:resultImage forKey:@([attachment hash])]; 84 | } 85 | dispatch_group_leave(loadGroup); 86 | }]; 87 | } 88 | 89 | dispatch_group_wait(loadGroup, DISPATCH_TIME_FOREVER); 90 | dispatch_async(dispatch_get_main_queue(), ^{ 91 | NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:attachmentArray.count]; 92 | for (DBAttachment *attachment in attachmentArray) { 93 | UIImage *image = imageDict[@([attachment hash])]; 94 | if (image) { 95 | [imageArray addObject:image]; 96 | } 97 | } 98 | if (finishPickingBlock) { 99 | finishPickingBlock([imageArray copy]); 100 | } 101 | }); 102 | }); 103 | }; 104 | 105 | DBAttachmentPickerController *controller = [self attachmentPickerControllerFinishPickingBlock:finishBlock cancelBlock:cancelBlock]; 106 | controller.mediaType = DBAttachmentMediaTypeImage; 107 | controller.ignoreChangeMediaType = YES; 108 | return controller; 109 | } 110 | 111 | + (instancetype)videoPickerControllerFinishPickingBlock:(FinishVideoPickingBlock)finishPickingBlock 112 | cancelBlock:(_Nullable CancelBlock)cancelBlock 113 | { 114 | void (^finishBlock)(NSArray * _Nonnull attachmentArray) = ^(NSArray * _Nonnull attachmentArray) { 115 | NSMutableArray *resourceArray = [NSMutableArray arrayWithCapacity:attachmentArray.count]; 116 | for (DBAttachment *attachment in attachmentArray) { 117 | id resource = [attachment originalFileResource]; 118 | if (resource) { 119 | [resourceArray addObject:resource]; 120 | } 121 | } 122 | if (finishPickingBlock) { 123 | finishPickingBlock([resourceArray copy]); 124 | } 125 | }; 126 | 127 | DBAttachmentPickerController *controller = [self attachmentPickerControllerFinishPickingBlock:finishBlock cancelBlock:cancelBlock]; 128 | controller.mediaType = DBAttachmentMediaTypeVideo; 129 | controller.ignoreChangeMediaType = YES; 130 | return controller; 131 | } 132 | 133 | #pragma mark - Lifecycle 134 | 135 | - (void)dealloc { 136 | [self.alertController dismissViewControllerAnimated:YES completion:^{ 137 | NSLog(@"Error: Responder must initialize DBAttachmentPickerController through special method attachmentPickerControllerFinishPickingBlock:cancelBlock:"); 138 | }]; 139 | } 140 | 141 | #pragma mark - Public 142 | 143 | - (void)presentOnViewController:(UIViewController *)initialViewController { 144 | self.initialViewController = initialViewController; 145 | __weak typeof(self) weakSelf = self; 146 | self.alertController = [DBAttachmentAlertController attachmentAlertControllerWithMediaType:[self assetMediaType] 147 | allowsMultipleSelection:self.allowsMultipleSelection 148 | allowsMediaLibrary:( (self.mediaType & DBAttachmentMediaTypeImage) || (self.mediaType & DBAttachmentMediaTypeVideo) ) 149 | allowsOtherApps:self.allowsSelectionFromOtherApps 150 | attachHandler:^(NSArray *assetArray) { 151 | NSArray *attachmentArray = [weakSelf attachmentArrayFromPHAssetArray:assetArray]; 152 | [weakSelf finishPickingWithAttachmentArray:attachmentArray]; 153 | } allAlbumsHandler:^(UIAlertAction *action) { 154 | [weakSelf allAlbumsDidSelect]; 155 | } takePictureHandler:^(UIAlertAction *action) { 156 | [weakSelf takePictureButtonDidSelect]; 157 | } otherAppsHandler:^(UIAlertAction *action) { 158 | [weakSelf otherAppsButtonDidSelect]; 159 | } cancelHandler:^(UIAlertAction * _Nonnull action) { 160 | [weakSelf cancelDidSelect]; 161 | }]; 162 | 163 | self.alertController.popoverPresentationController.sourceView = [self popoverPresentationView]; 164 | self.alertController.popoverPresentationController.sourceRect = [self popoverPresentationRect]; 165 | self.alertController.popoverPresentationController.permittedArrowDirections = [self popoverPresentationArrowDirection]; 166 | 167 | [self.initialViewController presentViewController:self.alertController animated:YES completion:^{ 168 | weakSelf.alertController = nil; 169 | }]; 170 | } 171 | 172 | - (void)dismissAttachmentPicker { 173 | self.extendedFinishPickingBlock = nil; 174 | self.extendedCancelBlock = nil; 175 | } 176 | 177 | #pragma mark Helpers 178 | 179 | - (PHAssetMediaType)assetMediaType { 180 | if ( (self.mediaType & DBAttachmentMediaTypeImage) && !(self.mediaType & DBAttachmentMediaTypeVideo) ) { 181 | return PHAssetMediaTypeImage; 182 | } else if ( !(self.mediaType & DBAttachmentMediaTypeImage) && (self.mediaType & DBAttachmentMediaTypeVideo) ) { 183 | return PHAssetMediaTypeVideo; 184 | } 185 | return PHAssetMediaTypeUnknown; 186 | } 187 | 188 | - (NSArray *)attachmentArrayFromPHAssetArray:(NSArray *)assetArray { 189 | NSMutableArray *attachmentArray = [NSMutableArray arrayWithCapacity:assetArray.count]; 190 | for (PHAsset *asset in assetArray) { 191 | DBAttachment *model = [DBAttachment attachmentFromPHAsset:asset]; 192 | [attachmentArray addObject:model]; 193 | } 194 | return [attachmentArray copy]; 195 | } 196 | 197 | #pragma mark - Popover presentation options 198 | 199 | - (UIView *)popoverPresentationView { 200 | return ( self.senderView ? self.senderView : self.initialViewController.view ); 201 | } 202 | 203 | - (CGRect)popoverPresentationRect { 204 | return ( self.senderView ? self.senderView.bounds : CGRectMake( CGRectGetMidX(self.initialViewController.view.bounds), CGRectGetMidY(self.initialViewController.view.bounds), .0f, .0f) ); 205 | } 206 | 207 | - (UIPopoverArrowDirection)popoverPresentationArrowDirection { 208 | return ( self.senderView ? UIPopoverArrowDirectionAny : 0 ); 209 | } 210 | 211 | #pragma mark - Accessors 212 | 213 | - (void)setMediaType:(DBAttachmentMediaType)mediaType { 214 | if (self.ignoreChangeMediaType) 215 | return; 216 | 217 | _mediaType = mediaType; 218 | } 219 | 220 | #pragma mark - Actions 221 | 222 | - (void)allAlbumsDidSelect { 223 | DBAssetPickerController *viewController =[[DBAssetPickerController alloc] init]; 224 | viewController.assetMediaType = [self assetMediaType]; 225 | viewController.assetPickerDelegate = self; 226 | [self.initialViewController presentViewController:viewController animated:YES completion:nil]; 227 | } 228 | 229 | - (void)otherAppsButtonDidSelect { 230 | NSMutableArray *documentMediaTypes = [NSMutableArray arrayWithCapacity:10]; 231 | if (self.mediaType & DBAttachmentMediaTypeImage) { 232 | [documentMediaTypes addObject:(NSString *)kUTTypeImage]; 233 | } 234 | if (self.mediaType & DBAttachmentMediaTypeVideo) { 235 | [documentMediaTypes addObject:(NSString *)kUTTypeVideo]; 236 | [documentMediaTypes addObject:(NSString *)kUTTypeMovie]; 237 | } 238 | if (self.mediaType & DBAttachmentMediaTypeOther) { 239 | [documentMediaTypes addObject:(NSString *)kUTTypeItem]; 240 | } 241 | 242 | @try { 243 | UIDocumentMenuViewController *viewController = [[UIDocumentMenuViewController alloc] initWithDocumentTypes:documentMediaTypes inMode:UIDocumentPickerModeImport]; 244 | viewController.delegate = self; 245 | viewController.modalPresentationStyle = UIModalPresentationFullScreen; 246 | 247 | viewController.popoverPresentationController.sourceView = [self popoverPresentationView]; 248 | viewController.popoverPresentationController.sourceRect = [self popoverPresentationRect]; 249 | viewController.popoverPresentationController.permittedArrowDirections = [self popoverPresentationArrowDirection]; 250 | 251 | [self.initialViewController presentViewController:viewController animated:YES completion:nil]; 252 | 253 | } @catch (NSException *exception) { 254 | [self cancelWithAlertErrorText:NSLocalizedString(@"Can't load Document Picker", @"Error text")]; 255 | } 256 | } 257 | 258 | - (void)takePictureButtonDidSelect { 259 | if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { 260 | [self cancelWithAlertErrorText:NSLocalizedString(@"Device has no camera", @"Error text")]; 261 | return; 262 | } 263 | 264 | UIImagePickerController *picker = [[UIImagePickerController alloc] init]; 265 | picker.delegate = self; 266 | picker.allowsEditing = NO; 267 | picker.sourceType = UIImagePickerControllerSourceTypeCamera; 268 | 269 | if ( (self.mediaType & DBAttachmentMediaTypeImage) && !(self.mediaType & DBAttachmentMediaTypeVideo) ) { 270 | picker.mediaTypes = @[(NSString *)kUTTypeImage]; 271 | picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto; 272 | } else if ( !(self.mediaType & DBAttachmentMediaTypeImage) && (self.mediaType & DBAttachmentMediaTypeVideo) ) { 273 | picker.mediaTypes = @[(NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo]; 274 | picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; 275 | picker.videoQuality = self.capturedVideoQulity; 276 | } else { 277 | picker.mediaTypes = @[(NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo, (NSString *)kUTTypeImage]; 278 | picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto; 279 | } 280 | 281 | [self.initialViewController presentViewController:picker animated:YES completion:nil]; 282 | } 283 | 284 | #pragma mark 285 | 286 | - (void)finishPickingWithAttachmentArray:(NSArray *)attachmentArray { 287 | self.extendedFinishPickingBlock(attachmentArray); 288 | } 289 | 290 | - (void)cancelDidSelect { 291 | self.extendedCancelBlock(); 292 | } 293 | 294 | - (void)cancelWithAlertErrorText:(NSString *)errorText { 295 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", @"Common") message:errorText preferredStyle:UIAlertControllerStyleAlert]; 296 | UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"Common") style:UIAlertActionStyleCancel handler:nil]; 297 | [alertController addAction:actionCancel]; 298 | [self.initialViewController presentViewController:alertController animated:YES completion:nil]; 299 | self.extendedCancelBlock(); 300 | } 301 | 302 | #pragma mark - UIDocumentMenuViewControllerDelegate 303 | 304 | - (void)documentMenu:(UIDocumentMenuViewController *)documentMenu didPickDocumentPicker:(UIDocumentPickerViewController *)documentPicker { 305 | documentPicker.delegate = self; 306 | [self.initialViewController presentViewController:documentPicker animated:YES completion:nil]; 307 | } 308 | 309 | - (void)documentMenuWasCancelled:(UIDocumentMenuViewController *)documentMenu { 310 | [self cancelDidSelect]; 311 | } 312 | 313 | #pragma mark - UIDocumentPickerViewControllerDelegate 314 | 315 | - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { 316 | DBAttachment *attachment = [DBAttachment attachmentFromDocumentURL:url]; 317 | [self finishPickingWithAttachmentArray:@[attachment]]; 318 | } 319 | 320 | - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { 321 | [self cancelDidSelect]; 322 | } 323 | 324 | #pragma mark - UIImagePickerControllerDelegate 325 | 326 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 327 | NSString *mediaType = info[UIImagePickerControllerMediaType]; 328 | NSArray *attachmentArray = @[]; 329 | if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { 330 | UIImage *image = info[UIImagePickerControllerOriginalImage]; 331 | DBAttachment *attachment = [DBAttachment attachmentFromCameraImage:image]; 332 | attachmentArray = @[attachment]; 333 | } else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie] || [mediaType isEqualToString:(NSString *)kUTTypeVideo]) { 334 | NSURL *documentURL = info[UIImagePickerControllerMediaURL]; 335 | if (documentURL) { 336 | DBAttachment *attachment = [DBAttachment attachmentFromDocumentURL:documentURL]; 337 | attachmentArray = @[attachment]; 338 | } 339 | } 340 | 341 | __weak typeof(self) weakSelf = self; 342 | [picker dismissViewControllerAnimated:YES completion:^{ 343 | [weakSelf finishPickingWithAttachmentArray:attachmentArray]; 344 | }]; 345 | } 346 | 347 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 348 | __weak typeof(self) weakSelf = self; 349 | [picker dismissViewControllerAnimated:YES completion:^{ 350 | [weakSelf cancelDidSelect]; 351 | }]; 352 | } 353 | 354 | #pragma mark - DBImagePickerControllerDelegate 355 | 356 | - (void)DBAssetPickerController:(nonnull DBAssetPickerController *)controller didFinishPickingAssetArray:(nonnull NSArray *)assetArray { 357 | __weak typeof(self) weakSelf = self; 358 | [controller dismissViewControllerAnimated:YES completion:^{ 359 | NSArray *attachmentArray = [weakSelf attachmentArrayFromPHAssetArray:assetArray]; 360 | [weakSelf finishPickingWithAttachmentArray:attachmentArray]; 361 | }]; 362 | } 363 | 364 | - (void)DBAssetPickerControllerDidCancel:(nonnull DBAssetPickerController *)controller { 365 | __weak typeof(self) weakSelf = self; 366 | [controller dismissViewControllerAnimated:YES completion:^{ 367 | [weakSelf cancelDidSelect]; 368 | }]; 369 | } 370 | 371 | - (BOOL)DBAssetPickerControllerAllowsMultipleSelection:(DBAssetPickerController *)controller { 372 | return self.allowsMultipleSelection; 373 | } 374 | 375 | @end 376 | -------------------------------------------------------------------------------- /Source/Localization/de.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/de.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/de.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Bringen Sie %zd Datei 17 | other 18 | Bringen Sie %zd Dateien 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/en.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/en.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/en.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Attach %zd file 17 | other 18 | Attach %zd files 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/es.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/es.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/es.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Adjuntar %zd archivo 17 | other 18 | Adjuntar %zd archivos 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/fr.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/fr.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/fr.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Joindre %zd fichier 17 | other 18 | Joindre %zd fichiers 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/ja.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/ja.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/ja.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | other 16 | %zdファイルを添付 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Source/Localization/pl-PL.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/pl-PL.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/pl-PL.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Załącz %zd plik 17 | many 18 | Załącz %zd pliki 19 | other 20 | Załącz %zd plików 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/Localization/pt-BR.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/pt-BR.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/pt-BR.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Anexar %zd arquivo 17 | other 18 | Anexar %zd arquivos 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/ru.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Albums" = "Альбомы"; 3 | 4 | /* Button on main menu */ 5 | "All albums" = "Все альбомы"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Attach" = "Прикрепить"; 9 | 10 | /* Button on main menu */ 11 | "Attach %zd file(s)" = "Прикрепить %zd файл(ов)"; 12 | 13 | /* Title */ 14 | "Attach files" = "Прикрепить файлы"; 15 | 16 | /* Error text */ 17 | "Can't load Document Picker" = "Невозможно загрузить Document Picker"; 18 | 19 | /* Common */ 20 | "Cancel" = "Отменить"; 21 | 22 | /* Error text */ 23 | "Device has no camera" = "Устройство не имеет камеры"; 24 | 25 | /* Common */ 26 | "Error" = "Ошибка"; 27 | 28 | /* Common */ 29 | "OK" = "Ок"; 30 | 31 | /* Button on main menu */ 32 | "Other apps" = "Другие приложения"; 33 | 34 | /* Button on main menu */ 35 | "Take a picture" = "Сделать фото"; 36 | 37 | /* Button on main menu */ 38 | "Take a picture or a video" = "Сделать фото или видео"; 39 | 40 | /* Button on main menu */ 41 | "Take a video" = "Сделать видео"; -------------------------------------------------------------------------------- /Source/Localization/ru.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Attach %zd file(s) 5 | 6 | NSStringLocalizedFormatKey 7 | %#@files@ 8 | files 9 | 10 | NSStringFormatSpecTypeKey 11 | NSStringPluralRuleType 12 | NSStringFormatValueTypeKey 13 | zd 14 | one 15 | Прикрепить %zd файл 16 | many 17 | Прикрепить %zd файлов 18 | other 19 | Прикрепить %zd файла 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Source/Localization/uk.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/uk.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/uk.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | one 16 | Докласти %zd файл 17 | other 18 | Докласти %zd файлів 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Localization/zh-Hans.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/zh-Hans.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/zh-Hans.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | other 16 | 附加 %zd 个文件 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Source/Localization/zh-Hant.lproj/DBAttachmentPickerController.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d0ping/DBAttachmentPickerController/355f3f35897f4dab10d656d640302b03d9174842/Source/Localization/zh-Hant.lproj/DBAttachmentPickerController.strings -------------------------------------------------------------------------------- /Source/Localization/zh-Hant.lproj/DBAttachmentPickerController.stringsdict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Attach %zd file(s) 6 | 7 | NSStringLocalizedFormatKey 8 | %#@files@ 9 | files 10 | 11 | NSStringFormatSpecTypeKey 12 | NSStringPluralRuleType 13 | NSStringFormatValueTypeKey 14 | zd 15 | other 16 | 附加 %zd 個文件 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Source/Models/DBAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachment.h 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | #import 23 | #import "DBAttachmentPickerController.h" 24 | 25 | typedef NS_ENUM(NSInteger, DBAttachmentSourceType) { 26 | DBAttachmentSourceTypeUnknown = 0, 27 | DBAttachmentSourceTypePHAsset, 28 | DBAttachmentSourceTypeImage, 29 | DBAttachmentSourceTypeDocumentURL, 30 | }; 31 | 32 | @class PHAsset; 33 | @interface DBAttachment : NSObject 34 | 35 | /*! 36 | @brief The name of the file. Can be empty. 37 | */ 38 | @property (strong, nonatomic, readonly, nullable) NSString *fileName; 39 | 40 | /*! 41 | @brief Creation date of the file. Can be nil. 42 | */ 43 | @property (strong, nonatomic, readonly, nullable) NSDate *creationDate; 44 | 45 | /*! 46 | @brief Size of the file in byte. Available only for existing files. 47 | @attention If you want get file size for PHAsset or something like that, you should calculate it after getting file data. 48 | @see fileSizeStr 49 | */ 50 | @property (assign, nonatomic, readonly) NSUInteger fileSize; 51 | 52 | /*! 53 | @brief Formatted string of file size. Can be empty. 54 | @see fileSize 55 | */ 56 | @property (readonly, nullable) NSString *fileSizeStr; 57 | 58 | /*! 59 | @brief Attachment source type 60 | */ 61 | @property (assign, nonatomic, readonly) DBAttachmentSourceType sourceType; 62 | 63 | /*! 64 | @brief Attachment media type 65 | */ 66 | @property (assign, nonatomic, readonly) DBAttachmentMediaType mediaType; 67 | 68 | NS_ASSUME_NONNULL_BEGIN 69 | 70 | /*! 71 | @brief Creates and returns an attachment on the basis of PHAsset 72 | @see attachmentFromCameraImage: 73 | @see attachmentFromDocumentURL: 74 | @param asset PHAsset object 75 | @return An instance attachment object 76 | */ 77 | + (instancetype)attachmentFromPHAsset:(PHAsset *)asset; 78 | 79 | /*! 80 | @brief Creates and returns an attachment on the basis of UIImage 81 | @see attachmentFromPHAsset: 82 | @see attachmentFromDocumentURL: 83 | @param image UIImage object 84 | @return An instance attachment object 85 | */ 86 | + (instancetype)attachmentFromCameraImage:(UIImage *)image; 87 | 88 | /*! 89 | @brief Creates and returns an attachment on the basis of NSURL 90 | @see attachmentFromPHAsset: 91 | @see attachmentFromCameraImage: 92 | @param url NSURL object 93 | @return An instance attachment object 94 | */ 95 | + (instancetype)attachmentFromDocumentURL:(NSURL *)url; 96 | 97 | 98 | /*! 99 | @brief Load thumbnail image for specified size. Resultant image size can differ from the specified size. 100 | @see loadOriginalImageWithCompletion: 101 | @param targetSize The desired image size 102 | @param completion result block 103 | */ 104 | - (void)loadThumbnailImageWithTargetSize:(CGSize)targetSize completion:(void(^)(UIImage * _Nullable resultImage))completion; 105 | 106 | /*! 107 | @brief Load original image 108 | @see loadThumbnailImageWithTargetSize:completion: 109 | @param completion result block 110 | */ 111 | - (void)loadOriginalImageWithCompletion:(void(^)(UIImage * _Nullable resultImage))completion; 112 | 113 | /*! 114 | @brief Get original resource 115 | @return Can be return PHAsset, UIImage or local file path (NSString) 116 | */ 117 | - (id)originalFileResource; 118 | 119 | NS_ASSUME_NONNULL_END 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /Source/Models/DBAttachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAttachment.m 3 | // DBAttachmentPickerController 4 | // 5 | // Created by Denis Bogatyrev on 14.03.16. 6 | // 7 | // The MIT License (MIT) 8 | // Copyright (c) 2016 Denis Bogatyrev. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | // 21 | 22 | @import Photos; 23 | @import CoreLocation; 24 | #import "DBAttachment.h" 25 | #import "UIImage+DBAssetIcons.h" 26 | 27 | @interface DBAttachment () 28 | 29 | @property (strong, nonatomic) NSString *fileName; 30 | @property (assign, nonatomic) NSUInteger fileSize; 31 | @property (strong, nonatomic) NSDate *creationDate; 32 | 33 | @property (assign, nonatomic) DBAttachmentSourceType sourceType; 34 | @property (assign, nonatomic) DBAttachmentMediaType mediaType; 35 | 36 | @property (strong, nonatomic) PHAsset *photoAsset; 37 | @property (strong, nonatomic) UIImage *image; 38 | @property (strong, nonatomic) UIImage *thumbmailImage; 39 | @property (strong, nonatomic) NSString *originalFilePath; 40 | 41 | @property (strong, nonatomic) NSString *restorationIdentifier; 42 | 43 | @end 44 | 45 | @implementation DBAttachment 46 | 47 | + (instancetype)attachmentFromPHAsset:(PHAsset *)asset { 48 | DBAttachment *model = [[[self class] alloc] init]; 49 | model.sourceType = DBAttachmentSourceTypePHAsset; 50 | model.photoAsset = asset; 51 | 52 | NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; 53 | PHAssetResource *resource = [resources firstObject]; 54 | switch (asset.mediaType) { 55 | case PHAssetMediaTypeImage: 56 | model.mediaType = DBAttachmentMediaTypeImage; 57 | break; 58 | case PHAssetMediaTypeVideo: 59 | model.mediaType = DBAttachmentMediaTypeVideo; 60 | break; 61 | default: 62 | model.mediaType = DBAttachmentMediaTypeOther; 63 | break; 64 | } 65 | model.fileName = resource.originalFilename; 66 | model.creationDate = asset.creationDate; 67 | 68 | return model; 69 | } 70 | 71 | + (instancetype)attachmentFromCameraImage:(UIImage *)image { 72 | DBAttachment *model = [[[self class] alloc] init]; 73 | model.sourceType = DBAttachmentSourceTypeImage; 74 | model.mediaType = DBAttachmentMediaTypeImage; 75 | model.image = image; 76 | 77 | NSData *imgData = UIImageJPEGRepresentation(image, 1); 78 | model.fileSize = imgData.length; 79 | model.creationDate = [NSDate date]; 80 | model.fileName = @"capturedimage.jpg"; 81 | 82 | return model; 83 | } 84 | 85 | + (instancetype)attachmentFromDocumentURL:(NSURL *)url { 86 | DBAttachment *model = [[[self class] alloc] init]; 87 | model.sourceType = DBAttachmentSourceTypeDocumentURL; 88 | 89 | NSString *fileTmpPath = [url path]; 90 | if ( [[NSFileManager defaultManager] fileExistsAtPath:fileTmpPath] ) { 91 | model.fileName = [fileTmpPath lastPathComponent]; 92 | NSString *cacheFolderPath = [[model cacheFolderPath] stringByAppendingPathComponent:model.restorationIdentifier]; 93 | NSError *error; 94 | if ( ![[NSFileManager defaultManager] fileExistsAtPath:cacheFolderPath] ) { 95 | [[NSFileManager defaultManager] createDirectoryAtPath:cacheFolderPath withIntermediateDirectories:YES attributes:nil error:&error]; 96 | } 97 | 98 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fileTmpPath error:nil]; 99 | model.creationDate = attributes[NSFileCreationDate]; 100 | model.fileSize = [attributes[NSFileSize] integerValue]; 101 | 102 | model.originalFilePath = [cacheFolderPath stringByAppendingPathComponent:model.fileName]; 103 | [[NSFileManager defaultManager] copyItemAtPath:fileTmpPath toPath:model.originalFilePath error:&error]; 104 | } 105 | 106 | NSString *fileExt = [[[url absoluteString] pathExtension] lowercaseString]; 107 | if ( [fileExt isEqualToString:@"png"] || [fileExt isEqualToString:@"jpeg"] || [fileExt isEqualToString:@"jpg"] || [fileExt isEqualToString:@"gif"] || [fileExt isEqualToString:@"tiff"]) { 108 | model.mediaType = DBAttachmentMediaTypeImage; 109 | model.thumbmailImage = [UIImage imageWithContentsOfFile:model.originalFilePath]; 110 | } else if ( [fileExt isEqualToString:@"mov"] || [fileExt isEqualToString:@"avi"]) { 111 | model.mediaType = DBAttachmentMediaTypeVideo; 112 | model.thumbmailImage = [model generateThumbnailImageFromURL:url]; 113 | } else { 114 | model.mediaType = DBAttachmentMediaTypeOther; 115 | model.thumbmailImage = [UIImage imageOfFileIconWithExtensionText:[fileExt uppercaseString]]; 116 | } 117 | return model; 118 | } 119 | 120 | #pragma mark Helpers 121 | 122 | - (NSString *)cacheFolderPath { 123 | NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 124 | return [documentsDirectory stringByAppendingPathComponent:@"DBAttachmentPickerControllerCache"]; 125 | } 126 | 127 | #pragma mark - Lifecycle 128 | 129 | - (instancetype)init { 130 | self = [super init]; 131 | if (self) { 132 | self.restorationIdentifier = [NSUUID UUID].UUIDString; 133 | } 134 | return self; 135 | } 136 | 137 | - (void)dealloc { 138 | NSString *cacheFolderPath = [[self cacheFolderPath] stringByAppendingPathComponent:self.restorationIdentifier]; 139 | if ( [[NSFileManager defaultManager] fileExistsAtPath:cacheFolderPath] ) { 140 | NSError *error; 141 | [[NSFileManager defaultManager] removeItemAtPath:cacheFolderPath error:&error]; 142 | } 143 | } 144 | 145 | #pragma mark - Accessors 146 | 147 | - (NSString *)fileSizeStr { 148 | if (self.fileSize == 0) return nil; 149 | return [NSByteCountFormatter stringFromByteCount:self.fileSize countStyle:NSByteCountFormatterCountStyleFile]; 150 | } 151 | 152 | #pragma mark - 153 | 154 | - (void)loadThumbnailImageWithTargetSize:(CGSize)targetSize completion:(void(^)(UIImage *resultImage))completion { 155 | switch (self.sourceType) { 156 | case DBAttachmentSourceTypePHAsset: 157 | if (completion) { 158 | [[PHImageManager defaultManager] requestImageForAsset:self.photoAsset 159 | targetSize:targetSize 160 | contentMode:PHImageContentModeDefault 161 | options:nil 162 | resultHandler:^(UIImage *result, NSDictionary *info) { 163 | if (![info[PHImageResultIsDegradedKey] boolValue]) { 164 | completion(result); 165 | } 166 | }]; 167 | } 168 | break; 169 | case DBAttachmentSourceTypeDocumentURL: { 170 | if (self.thumbmailImage) { 171 | completion(self.thumbmailImage); 172 | } else { 173 | [self loadOriginalImageWithCompletion:completion]; 174 | } 175 | } 176 | break; 177 | default: 178 | [self loadOriginalImageWithCompletion:completion]; 179 | break; 180 | } 181 | } 182 | 183 | - (void)loadOriginalImageWithCompletion:(void(^)(UIImage *resultImage))completion { 184 | switch (self.sourceType) { 185 | case DBAttachmentSourceTypePHAsset: 186 | if (completion) { 187 | [[PHImageManager defaultManager] requestImageForAsset:self.photoAsset 188 | targetSize:PHImageManagerMaximumSize 189 | contentMode:PHImageContentModeDefault 190 | options:nil 191 | resultHandler:^(UIImage *result, NSDictionary *info) { 192 | completion(result); 193 | }]; 194 | } 195 | break; 196 | case DBAttachmentSourceTypeImage: { 197 | if (completion) { 198 | completion(self.image); 199 | } 200 | break; 201 | } 202 | case DBAttachmentSourceTypeDocumentURL: { 203 | if (!self.image) { 204 | self.image = [UIImage imageWithContentsOfFile:self.originalFilePath]; 205 | } 206 | if (completion) { 207 | completion(self.image); 208 | } 209 | break; 210 | } 211 | default: 212 | if (completion) { 213 | completion(nil); 214 | } 215 | break; 216 | } 217 | } 218 | 219 | - (id)originalFileResource { 220 | switch (self.sourceType) { 221 | case DBAttachmentSourceTypePHAsset: 222 | return self.photoAsset; 223 | break; 224 | case DBAttachmentSourceTypeImage: 225 | return self.image; 226 | break; 227 | case DBAttachmentSourceTypeDocumentURL: 228 | return self.originalFilePath; 229 | break; 230 | 231 | default: 232 | return nil; 233 | break; 234 | } 235 | } 236 | 237 | #pragma mark Helpers 238 | 239 | - (UIImage *)generateThumbnailImageFromURL:(NSURL *)url { 240 | AVAsset *asset = [AVAsset assetWithURL:url]; 241 | AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; 242 | imageGenerator.appliesPreferredTrackTransform = YES; 243 | CMTime time = [asset duration]; 244 | time.value = 0; 245 | CGImageRef imageRef = [imageGenerator copyCGImageAtTime:time actualTime:NULL error:NULL]; 246 | UIImage *thumbnail = [UIImage imageWithCGImage:imageRef]; 247 | CGImageRelease(imageRef); 248 | return thumbnail; 249 | } 250 | 251 | @end 252 | --------------------------------------------------------------------------------