├── .gitignore ├── .travis.yml ├── BUKImagePickerController.podspec ├── BUKImagePickerController ├── Assets │ ├── .gitkeep │ ├── camera-icon.png │ ├── camera-icon@2x.png │ ├── camera-icon@3x.png │ ├── delete-button.png │ ├── delete-button@2x.png │ ├── delete-button@3x.png │ ├── device-button.png │ ├── device-button@2x.png │ ├── device-button@3x.png │ ├── en.lproj │ │ └── BUKImagePicker.strings │ ├── flash-button.png │ ├── flash-button@2x.png │ ├── flash-button@3x.png │ ├── take-picture-button.png │ ├── take-picture-button@2x.png │ ├── take-picture-button@3x.png │ └── zh-Hans.lproj │ │ └── BUKImagePicker.strings └── Classes │ ├── .gitkeep │ ├── BUKAccessDeniedPlaceholderView.h │ ├── BUKAccessDeniedPlaceholderView.m │ ├── BUKAlbumTableViewCell.h │ ├── BUKAlbumTableViewCell.m │ ├── BUKAlbumsViewController.h │ ├── BUKAlbumsViewController.m │ ├── BUKAssetCollectionViewCell.h │ ├── BUKAssetCollectionViewCell.m │ ├── BUKAssetsManager.h │ ├── BUKAssetsManager.m │ ├── BUKAssetsViewController.h │ ├── BUKAssetsViewController.m │ ├── BUKCameraCollectionViewCell.h │ ├── BUKCameraCollectionViewCell.m │ ├── BUKCameraComfirmViewController.m │ ├── BUKCameraConfirmViewController.h │ ├── BUKCameraImageCollectionViewCell.h │ ├── BUKCameraImageCollectionViewCell.m │ ├── BUKCameraViewController.h │ ├── BUKCameraViewController.m │ ├── BUKCheckmarkView.h │ ├── BUKCheckmarkView.m │ ├── BUKImagePickerController.h │ ├── BUKImagePickerController.m │ ├── BUKNoAssetsPlaceholderView.h │ ├── BUKNoAssetsPlaceholderView.m │ ├── BUKPlaceholderView.h │ ├── BUKPlaceholderView.m │ ├── BUKVideoIconView.h │ ├── BUKVideoIconView.m │ ├── BUKVideoIndicatorView.h │ ├── BUKVideoIndicatorView.m │ ├── NSBundle+BUKImagePickerController.h │ ├── NSBundle+BUKImagePickerController.m │ ├── UIColor+BUKImagePickerController.h │ ├── UIColor+BUKImagePickerController.m │ ├── UIImage+BUKImagePickerController.h │ └── UIImage+BUKImagePickerController.m ├── Example ├── BUKImagePickerController.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── BUKImagePickerController-Example.xcscheme ├── BUKImagePickerController.xcworkspace │ └── contents.xcworkspacedata ├── BUKImagePickerController │ ├── Classes │ │ ├── BUKAppDelegate.h │ │ ├── BUKAppDelegate.m │ │ ├── BUKViewController.h │ │ └── BUKViewController.m │ ├── Other Sources │ │ ├── BUKImagePickerController-Prefix.pch │ │ └── main.m │ └── Resources │ │ ├── BUKImagePickerController-Info.plist │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ │ ├── Launch Screen.storyboard │ │ ├── en.lproj │ │ └── InfoPlist.strings │ │ └── zh-Hans.lproj │ │ └── InfoPlist.strings ├── Podfile ├── Podfile.lock └── Tests │ ├── Tests-Info.plist │ ├── Tests.m │ ├── en.lproj │ └── InfoPlist.strings │ └── zh-Hans.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md ├── _Pods.xcodeproj └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Carthage 26 | Carthage 27 | 28 | # CocoaPods 29 | Pods/ 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode7.3 2 | language: objective-c 3 | cache: cocoapods 4 | podfile: Example/Podfile 5 | before_install: 6 | - gem install xcpretty cocoapods --no-rdoc --no-ri --no-document --quiet 7 | script: 8 | - set -o pipefail && xcodebuild clean test -workspace Example/BUKImagePickerController.xcworkspace -scheme BUKImagePickerController-Example -sdk iphonesimulator -destination "platform=iOS Simulator,OS=9.3,name=iPhone 6s" ONLY_ACTIVE_ARCH=NO | xcpretty -c 9 | - pod lib lint --quick 10 | -------------------------------------------------------------------------------- /BUKImagePickerController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "BUKImagePickerController" 3 | s.version = "0.1.11" 4 | s.summary = "A view controller that allows the user picking multiple assets from the photo libray or capturing images with a camera." 5 | s.homepage = "https://github.com/iException/BUKImagePickerController" 6 | s.license = 'MIT' 7 | s.author = { "Yiming Tang" => "yimingnju@gmail.com" } 8 | s.source = { :git => "https://github.com/iException/BUKImagePickerController.git", :tag => "v#{s.version.to_s}" } 9 | s.social_media_url = 'https://twitter.com/yiming_t' 10 | 11 | s.platform = :ios, '7.0' 12 | s.requires_arc = true 13 | 14 | s.source_files = 'BUKImagePickerController/Classes/**/*' 15 | s.resource_bundles = { 16 | "BUKImagePickerController" => ["BUKImagePickerController/Assets/*.{lproj,png}"] 17 | } 18 | 19 | s.frameworks = 'AssetsLibrary' 20 | s.dependency 'FastttCamera', '~> 0.3' 21 | end 22 | -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/.gitkeep -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/camera-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/camera-icon.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/camera-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/camera-icon@2x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/camera-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/camera-icon@3x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/delete-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/delete-button.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/delete-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/delete-button@2x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/delete-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/delete-button@3x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/device-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/device-button.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/device-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/device-button@2x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/device-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/device-button@3x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/en.lproj/BUKImagePicker.strings: -------------------------------------------------------------------------------- 1 | "Access Denied" = "Access Denied"; 2 | 3 | "Back" = "Back"; 4 | 5 | "Cancel" = "Cancel"; 6 | 7 | "Confirm" = "Confirm"; 8 | 9 | "Done" = "Done"; 10 | 11 | "Flash Auto" = "Flash Auto"; 12 | 13 | "Flash Off" = "Flash Off"; 14 | 15 | "Flash On" = "Flash On"; 16 | 17 | "No Photos or Videos" = "No Photos or Videos"; 18 | 19 | "OK" = "OK"; 20 | 21 | "Photos" = "Photos"; 22 | 23 | "Retake" = "Retake"; 24 | 25 | "Selected: %@/%@" = "Selected: %@/%@"; 26 | 27 | "Selected: %@" = "Selected: %@"; 28 | 29 | "Take Photos" = "Take Photos"; 30 | 31 | "This app does not have access to your photos or videos." = "This app does not have access to your photos or videos."; 32 | 33 | "You can enable access in \"Settings\" -> \"Privacy\" -> \"Camera\"." = "You can enable access in \"Settings\" -> \"Privacy\" -> \"Camera\"."; 34 | 35 | "You can enable access in \"Settings\" -> \"Privacy\" -> \"Photos\"." = "You can enable access in \"Settings\" -> \"Privacy\" -> \"Photos\"."; 36 | 37 | "You can sync photos and videos onto your %@ using iTunes." = "You can sync photos and videos onto your %@ using iTunes."; 38 | 39 | "You can take photos and videos using the camera, or sync photos and videos onto your %@\nusing iTunes." = "You can take photos and videos using the camera, or sync photos and videos onto your %@\nusing iTunes."; 40 | -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/flash-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/flash-button.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/flash-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/flash-button@2x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/flash-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/flash-button@3x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/take-picture-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/take-picture-button.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/take-picture-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/take-picture-button@2x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/take-picture-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Assets/take-picture-button@3x.png -------------------------------------------------------------------------------- /BUKImagePickerController/Assets/zh-Hans.lproj/BUKImagePicker.strings: -------------------------------------------------------------------------------- 1 | "Access Denied" = "访问被阻止"; 2 | 3 | "Back" = "返回"; 4 | 5 | "Cancel" = "取消"; 6 | 7 | "Confirm" = "使用"; 8 | 9 | "Done" = "完成"; 10 | 11 | "Flash Auto" = "自动"; 12 | 13 | "Flash Off" = "关闭"; 14 | 15 | "Flash On" = "打开"; 16 | 17 | "No Photos or Videos" = "无照片或视频"; 18 | 19 | "OK" = "确定"; 20 | 21 | "Photos" = "相片"; 22 | 23 | "Retake" = "重拍"; 24 | 25 | "Selected: %@/%@" = "已选中: %@/%@"; 26 | 27 | "Selected: %@" = "已选中: %@"; 28 | 29 | "Take Photos" = "点击拍照"; 30 | 31 | "This app does not have access to your photos or videos." = "此应用无法访问您的照片库。"; 32 | 33 | "You can enable access in \"Settings\" -> \"Privacy\" -> \"Camera\"." = "您可以在\"设置\" -> \"隐私\" -> \"照相机\"开启权限。"; 34 | 35 | "You can enable access in \"Settings\" -> \"Privacy\" -> \"Photos\"." = "您可以在\"设置\" -> \"隐私\" -> \"照片\"开启权限。"; 36 | 37 | "You can sync photos and videos onto your %@ using iTunes." = "您可以使用iTunes将照片和视频同步到%@。"; 38 | 39 | "You can take photos and videos using the camera, or sync photos and videos onto your %@\nusing iTunes." = "您可以使用相机拍摄照片和视频,或使用iTunes将照片和视频同步到%@。"; 40 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/BUKImagePickerController/Classes/.gitkeep -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAccessDeniedPlaceholderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAccessDeniedPlaceholderView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKPlaceholderView.h" 10 | 11 | @interface BUKAccessDeniedPlaceholderView : BUKPlaceholderView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAccessDeniedPlaceholderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAccessDeniedPlaceholderView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKAccessDeniedPlaceholderView.h" 10 | #import "NSBundle+BUKImagePickerController.h" 11 | 12 | @implementation BUKAccessDeniedPlaceholderView 13 | 14 | - (instancetype)initWithFrame:(CGRect)frame { 15 | if ((self = [super initWithFrame:frame])) { 16 | self.titleLabel.text = BUKImagePickerLocalizedString(@"This app does not have access to your photos or videos.", nil); 17 | self.messageLabel.text = BUKImagePickerLocalizedString(@"You can enable access in \"Settings\" -> \"Privacy\" -> \"Photos\".", nil); 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAlbumTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAlbumTableViewCell.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKAlbumTableViewCell : UITableViewCell 12 | 13 | @property (nonatomic, readonly) UIImageView *frontImageView; 14 | @property (nonatomic, readonly) UIImageView *middleImageView; 15 | @property (nonatomic, readonly) UIImageView *backImageView; 16 | @property (nonatomic, readonly) UILabel *titleLabel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAlbumTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAlbumTableViewCell.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKAlbumTableViewCell.h" 10 | 11 | @interface BUKAlbumTableViewCell () 12 | @property (nonatomic) UIView *imageContainerView; 13 | @end 14 | 15 | 16 | @implementation BUKAlbumTableViewCell 17 | 18 | #pragma mark - Accessors 19 | 20 | @synthesize frontImageView = _frontImageView; 21 | @synthesize middleImageView = _middleImageView; 22 | @synthesize backImageView = _backImageView; 23 | @synthesize titleLabel = _titleLabel; 24 | 25 | - (UIImageView *)frontImageView { 26 | if (!_frontImageView) { 27 | _frontImageView = [self borderedImageView]; 28 | } 29 | return _frontImageView; 30 | } 31 | 32 | 33 | - (UIImageView *)middleImageView { 34 | if (!_middleImageView) { 35 | _middleImageView = [self borderedImageView]; 36 | } 37 | return _middleImageView; 38 | } 39 | 40 | 41 | - (UIImageView *)backImageView { 42 | if (!_backImageView) { 43 | _backImageView = [self borderedImageView]; 44 | } 45 | return _backImageView; 46 | } 47 | 48 | 49 | - (UILabel *)titleLabel { 50 | if (!_titleLabel) { 51 | _titleLabel = [[UILabel alloc] init]; 52 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 53 | _titleLabel.font = [UIFont systemFontOfSize:14.0]; 54 | _titleLabel.textColor = [UIColor blackColor]; 55 | } 56 | return _titleLabel; 57 | } 58 | 59 | 60 | - (UIView *)imageContainerView { 61 | if (!_imageContainerView) { 62 | _imageContainerView = [[UIView alloc] init]; 63 | _imageContainerView.translatesAutoresizingMaskIntoConstraints = NO; 64 | } 65 | return _imageContainerView; 66 | } 67 | 68 | 69 | #pragma mark - UITableViewCell 70 | 71 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 72 | if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { 73 | [self.imageContainerView addSubview:self.backImageView]; 74 | [self.imageContainerView addSubview:self.middleImageView]; 75 | [self.imageContainerView addSubview:self.frontImageView]; 76 | [self.contentView addSubview:self.imageContainerView]; 77 | [self.contentView addSubview:self.titleLabel]; 78 | 79 | [self setupViewConstraints]; 80 | } 81 | return self; 82 | } 83 | 84 | 85 | #pragma mark - Private 86 | 87 | - (UIImageView *)borderedImageView { 88 | UIImageView *imageView = [[UIImageView alloc] init]; 89 | imageView.translatesAutoresizingMaskIntoConstraints = NO; 90 | imageView.contentMode = UIViewContentModeScaleAspectFit; 91 | imageView.layer.borderColor = [[UIColor whiteColor] CGColor]; 92 | imageView.layer.borderWidth = 1.0 / [[UIScreen mainScreen] scale]; 93 | return imageView; 94 | } 95 | 96 | 97 | - (void)setupViewConstraints { 98 | NSDictionary *views = @{ 99 | @"imageContainerView": self.imageContainerView, 100 | @"frontImageView": self.frontImageView, 101 | @"middleImageView": self.middleImageView, 102 | @"backImageView": self.backImageView, 103 | @"titleLabel": self.titleLabel, 104 | }; 105 | 106 | CGFloat padding = 2.0; 107 | NSDictionary *metrics = @{ 108 | @"padding": @(padding), 109 | @"horizontalMargin": @16.0, 110 | }; 111 | 112 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(horizontalMargin)-[imageContainerView(68.0)]-(horizontalMargin)-[titleLabel]|" options:NSLayoutFormatAlignAllCenterY metrics:metrics views:views]]; 113 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.imageContainerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 114 | 115 | // Image container view 116 | [self.imageContainerView addConstraint:[NSLayoutConstraint constraintWithItem:self.imageContainerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageContainerView attribute:NSLayoutAttributeWidth multiplier:1 constant:(padding * 2)]]; 117 | 118 | // Front image view 119 | [self.imageContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[frontImageView]|" options:kNilOptions metrics:nil views:views]]; 120 | [self.imageContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[frontImageView]|" options:kNilOptions metrics:nil views:views]]; 121 | [self.frontImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.frontImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.frontImageView attribute:NSLayoutAttributeHeight multiplier:1 constant:0]]; 122 | 123 | // Middle image view 124 | [self.imageContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding)-[middleImageView]-(padding)-|" options:kNilOptions metrics:metrics views:views]]; 125 | [self.imageContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(padding)-[middleImageView]" options:kNilOptions metrics:metrics views:views]]; 126 | [self.middleImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.middleImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.middleImageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 127 | 128 | // Back image view 129 | [self.imageContainerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backImageView]" options:kNilOptions metrics:nil views:views]]; 130 | [self.backImageView addConstraint:[NSLayoutConstraint constraintWithItem:self.backImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.backImageView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 131 | [self.imageContainerView addConstraint:[NSLayoutConstraint constraintWithItem:self.backImageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.imageContainerView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 132 | [self.imageContainerView addConstraint:[NSLayoutConstraint constraintWithItem:self.backImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.imageContainerView attribute:NSLayoutAttributeWidth multiplier:1 constant:(- 2 * 2 * padding)]]; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAlbumsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAlbumsViewController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @class BUKAssetsManager; 12 | @protocol BUKAlbumsViewControllerDelegate; 13 | 14 | @interface BUKAlbumsViewController : UITableViewController 15 | 16 | @property (nonatomic) BUKAssetsManager *assetsManager; 17 | @property (nonatomic, readonly) NSArray *assetsGroups; 18 | @property (nonatomic, weak) id delegate; 19 | @property (nonatomic) BOOL allowsMultipleSelection; 20 | 21 | @end 22 | 23 | 24 | @class ALAssetsGroup; 25 | 26 | @protocol BUKAlbumsViewControllerDelegate 27 | 28 | @optional 29 | - (void)albumsViewControllerDidCancel:(BUKAlbumsViewController *)viewController; 30 | - (void)albumsViewControllerDidFinishPicking:(BUKAlbumsViewController *)viewController; 31 | - (void)albumsViewController:(BUKAlbumsViewController *)viewController didSelectAssetsGroup:(ALAssetsGroup *)assetsGroup; 32 | - (BOOL)albumsViewControllerShouldEnableDoneButton:(BUKAlbumsViewController *)viewController; 33 | - (BOOL)albumsViewControllerShouldShowSelectionInfo:(BUKAlbumsViewController *)viewController; 34 | - (NSString *)albumsViewControllerSelectionInfo:(BUKAlbumsViewController *)viewController; 35 | @end 36 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAlbumsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAlbumsViewController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | 11 | #import "BUKAlbumsViewController.h" 12 | #import "BUKAssetsViewController.h" 13 | #import "BUKAlbumTableViewCell.h" 14 | #import "BUKAssetsManager.h" 15 | #import "UIImage+BUKImagePickerController.h" 16 | #import "NSBundle+BUKImagePickerController.h" 17 | 18 | static NSString *const kBUKAlbumsViewControllerCellIdentifier = @"albumCell"; 19 | 20 | @interface BUKAlbumsViewController () 21 | @property (nonatomic, readwrite) NSArray *assetsGroups; 22 | @property (nonatomic) UIBarButtonItem *doneBarButtonItem; 23 | @end 24 | 25 | 26 | @implementation BUKAlbumsViewController 27 | 28 | #pragma mark - NSObject 29 | 30 | - (instancetype)init { 31 | if ((self = [super init])) { 32 | self.title = BUKImagePickerLocalizedString(@"Photos", nil); 33 | } 34 | return self; 35 | } 36 | 37 | 38 | - (void)dealloc { 39 | [[NSNotificationCenter defaultCenter] removeObserver:self name:ALAssetsLibraryChangedNotification object:nil]; 40 | } 41 | 42 | 43 | #pragma mark - UIViewController 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | 48 | if (self.allowsMultipleSelection) { 49 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Cancel", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)]; 50 | self.doneBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Done", nil) style:UIBarButtonItemStyleDone target:self action:@selector(done:)]; 51 | self.navigationItem.rightBarButtonItem = self.doneBarButtonItem; 52 | } else { 53 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Cancel", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)]; 54 | } 55 | 56 | [self setUpToolbar]; 57 | 58 | self.clearsSelectionOnViewWillAppear = YES; 59 | self.tableView.rowHeight = 90.0; 60 | [self.tableView registerClass:[BUKAlbumTableViewCell class] forCellReuseIdentifier:kBUKAlbumsViewControllerCellIdentifier]; 61 | 62 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(assetsLibraryChanged:) name:ALAssetsLibraryChangedNotification object:nil]; 63 | 64 | // Load assets groups 65 | __weak typeof(self)weakSelf = self; 66 | [self updateAssetsGroupsWithCompletion:^{ 67 | [weakSelf.tableView reloadData]; 68 | }]; 69 | } 70 | 71 | 72 | - (void)viewWillAppear:(BOOL)animated { 73 | [super viewWillAppear:animated]; 74 | 75 | [self updateDoneButton]; 76 | [self updateSelectionInfo]; 77 | } 78 | 79 | 80 | #pragma mark - Actions 81 | 82 | - (void)cancel:(id)sender { 83 | if ([self.delegate respondsToSelector:@selector(albumsViewControllerDidCancel:)]) { 84 | [self.delegate albumsViewControllerDidCancel:self]; 85 | } else { 86 | [self dismissViewControllerAnimated:YES completion:nil]; 87 | } 88 | } 89 | 90 | 91 | - (void)done:(id)sender { 92 | if ([self.delegate respondsToSelector:@selector(albumsViewControllerDidFinishPicking:)]) { 93 | [self.delegate albumsViewControllerDidFinishPicking:self]; 94 | } else { 95 | [self dismissViewControllerAnimated:YES completion:nil]; 96 | } 97 | } 98 | 99 | 100 | #pragma mark - UITableViewDataSource 101 | 102 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 103 | return self.assetsGroups.count; 104 | } 105 | 106 | 107 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 108 | BUKAlbumTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kBUKAlbumsViewControllerCellIdentifier forIndexPath:indexPath]; 109 | [self configureCell:cell forRowAtIndexPath:indexPath]; 110 | 111 | return cell; 112 | } 113 | 114 | 115 | #pragma mark - UITableViewDelegate 116 | 117 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 118 | if ([self.delegate respondsToSelector:@selector(albumsViewController:didSelectAssetsGroup:)]) { 119 | [self.delegate albumsViewController:self didSelectAssetsGroup:[self assetsGroupAtIndexPath:indexPath]]; 120 | } 121 | } 122 | 123 | 124 | #pragma mark - Private 125 | 126 | - (void)setUpToolbar { 127 | UIBarButtonItem *leftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL]; 128 | UIBarButtonItem *rightSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL]; 129 | 130 | NSDictionary *attributes = @{ 131 | NSForegroundColorAttributeName: [UIColor blackColor], 132 | NSFontAttributeName: [UIFont systemFontOfSize:14.0], 133 | }; 134 | UIBarButtonItem *infoButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL]; 135 | infoButtonItem.enabled = NO; 136 | [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal]; 137 | [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateDisabled]; 138 | 139 | self.toolbarItems = @[leftSpace, infoButtonItem, rightSpace]; 140 | } 141 | 142 | 143 | - (void)updateSelectionInfo { 144 | BOOL shouldShow = NO; 145 | NSString *text = nil; 146 | if ([self.delegate respondsToSelector:@selector(albumsViewControllerShouldShowSelectionInfo:)]) { 147 | shouldShow = [self.delegate albumsViewControllerShouldShowSelectionInfo:self]; 148 | } 149 | 150 | if ([self.delegate respondsToSelector:@selector(albumsViewControllerSelectionInfo:)]) { 151 | text = [self.delegate albumsViewControllerSelectionInfo:self]; 152 | } 153 | 154 | shouldShow = shouldShow && text != nil; 155 | 156 | if (shouldShow) { 157 | UIBarButtonItem *barButtonItem = self.toolbarItems[1]; 158 | barButtonItem.title = text; 159 | } 160 | 161 | BOOL shoulHidden = !shouldShow; 162 | if (shoulHidden != self.navigationController.toolbarHidden) { 163 | [self.navigationController setToolbarHidden:shoulHidden animated:YES]; 164 | } 165 | } 166 | 167 | 168 | - (void)updateDoneButton { 169 | if ([self.delegate respondsToSelector:@selector(albumsViewControllerShouldEnableDoneButton:)]) { 170 | self.doneBarButtonItem.enabled = [self.delegate albumsViewControllerShouldEnableDoneButton:self]; 171 | } else { 172 | self.doneBarButtonItem.enabled = YES; 173 | } 174 | } 175 | 176 | 177 | - (BOOL)hasContent { 178 | return self.assetsGroups.count > 0; 179 | } 180 | 181 | 182 | - (ALAssetsGroup *)assetsGroupAtIndexPath:(NSIndexPath *)indexPath { 183 | return self.assetsGroups[indexPath.row]; 184 | } 185 | 186 | 187 | - (void)configureCell:(BUKAlbumTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 188 | ALAssetsGroup *assetsGroup = [self assetsGroupAtIndexPath:indexPath]; 189 | NSString *groupName = [assetsGroup valueForProperty:ALAssetsGroupPropertyName]; 190 | 191 | cell.titleLabel.text = [NSString stringWithFormat:@"%@ (%ld)", groupName, (long)assetsGroup.numberOfAssets]; 192 | cell.tag = indexPath.row; 193 | 194 | NSUInteger numberOfAssets = MIN(3, [assetsGroup numberOfAssets]); 195 | 196 | if (numberOfAssets == 0) { 197 | cell.backImageView.hidden = NO; 198 | cell.middleImageView.hidden = NO; 199 | 200 | UIImage *placeholderImage = [UIImage buk_albumPlaceholderImageWithSize:CGSizeMake(68.0, 68.0)]; 201 | cell.frontImageView.image = placeholderImage; 202 | cell.middleImageView.image = placeholderImage; 203 | cell.backImageView.image = placeholderImage; 204 | } else { 205 | NSRange range = NSMakeRange([assetsGroup numberOfAssets] - numberOfAssets, numberOfAssets); 206 | NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:range]; 207 | 208 | cell.backImageView.hidden = YES; 209 | cell.middleImageView.hidden = YES; 210 | 211 | [assetsGroup enumerateAssetsAtIndexes:indexes options:kNilOptions usingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) { 212 | if (!asset || cell.tag != indexPath.row) return; 213 | 214 | UIImage *thumbnail = [UIImage imageWithCGImage:[asset thumbnail]]; 215 | if (index == NSMaxRange(range) - 1) { 216 | cell.frontImageView.hidden = NO; 217 | cell.frontImageView.image = thumbnail; 218 | } else if (index == NSMaxRange(range) - 2) { 219 | cell.middleImageView.hidden = NO; 220 | cell.middleImageView.image = thumbnail; 221 | } else { 222 | cell.backImageView.hidden = NO; 223 | cell.backImageView.image = thumbnail; 224 | } 225 | }]; 226 | } 227 | } 228 | 229 | 230 | - (void)assetsLibraryChanged:(NSNotification *)notification { 231 | dispatch_async(dispatch_get_main_queue(), ^{ 232 | __weak typeof(self)weakSelf = self; 233 | [self updateAssetsGroupsWithCompletion:^{ 234 | [weakSelf.tableView reloadData]; 235 | }]; 236 | }); 237 | } 238 | 239 | 240 | #pragma mark - Fetching AssetsGroups 241 | 242 | - (void)updateAssetsGroupsWithCompletion:(void (^)(void))completion { 243 | [self.assetsManager fetchAssetsGroupsWithCompletion:^(NSArray *assetsGroups) { 244 | self.assetsGroups = assetsGroups; 245 | if (completion) { 246 | completion(); 247 | } 248 | } failureBlock:nil]; 249 | } 250 | 251 | @end 252 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetCollectionViewCell.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @class BUKVideoIndicatorView; 12 | 13 | @interface BUKAssetCollectionViewCell : UICollectionViewCell 14 | 15 | @property (nonatomic, readonly) UIImageView *imageView; 16 | @property (nonatomic, readonly) UIView *checkmarkView; 17 | @property (nonatomic, readonly) UIView *overlayView; 18 | @property (nonatomic, readonly) BUKVideoIndicatorView *videoIndicatorView; 19 | @property (nonatomic) BOOL showsOverlayViewWhenSelected; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetCollectionViewCell.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKAssetCollectionViewCell.h" 10 | #import "BUKCheckmarkView.h" 11 | #import "BUKVideoIndicatorView.h" 12 | 13 | @implementation BUKAssetCollectionViewCell 14 | 15 | #pragma mark - Accessors 16 | 17 | @synthesize imageView = _imageView; 18 | @synthesize checkmarkView = _checkmarkView; 19 | @synthesize videoIndicatorView = _videoIndicatorView; 20 | @synthesize overlayView = _overlayView; 21 | 22 | - (UIImageView *)imageView { 23 | if (!_imageView) { 24 | _imageView = [[UIImageView alloc] init]; 25 | _imageView.translatesAutoresizingMaskIntoConstraints = NO; 26 | } 27 | return _imageView; 28 | } 29 | 30 | 31 | - (UIView *)checkmarkView { 32 | if (!_checkmarkView) { 33 | _checkmarkView = [[BUKCheckmarkView alloc] init]; 34 | _checkmarkView.translatesAutoresizingMaskIntoConstraints = NO; 35 | _checkmarkView.hidden = YES; 36 | } 37 | return _checkmarkView; 38 | } 39 | 40 | 41 | - (BUKVideoIndicatorView *)videoIndicatorView { 42 | if (!_videoIndicatorView) { 43 | _videoIndicatorView = [[BUKVideoIndicatorView alloc] init]; 44 | _videoIndicatorView.translatesAutoresizingMaskIntoConstraints = NO; 45 | _videoIndicatorView.hidden = YES; 46 | } 47 | return _videoIndicatorView; 48 | } 49 | 50 | 51 | - (UIView *)overlayView { 52 | if (!_overlayView) { 53 | _overlayView = [[UIView alloc] init]; 54 | _overlayView.translatesAutoresizingMaskIntoConstraints = NO; 55 | _overlayView.backgroundColor = [UIColor whiteColor]; 56 | _overlayView.alpha = 0.4f; 57 | _overlayView.hidden = YES; 58 | } 59 | return _overlayView; 60 | } 61 | 62 | 63 | #pragma mark - UICollectionViewCell 64 | 65 | - (instancetype)initWithFrame:(CGRect)frame { 66 | if ((self = [super initWithFrame:frame])) { 67 | [self.contentView addSubview:self.imageView]; 68 | [self.contentView addSubview:self.videoIndicatorView]; 69 | [self.contentView addSubview:self.overlayView]; 70 | [self.contentView addSubview:self.checkmarkView]; 71 | [self setupViewConstraints]; 72 | 73 | self.showsOverlayViewWhenSelected = YES; 74 | } 75 | return self; 76 | } 77 | 78 | 79 | - (void)setSelected:(BOOL)selected { 80 | [super setSelected:selected]; 81 | 82 | self.checkmarkView.hidden = !selected; 83 | self.overlayView.hidden = !(self.showsOverlayViewWhenSelected && selected); 84 | } 85 | 86 | 87 | #pragma mark - Private 88 | 89 | - (void)setupViewConstraints { 90 | NSDictionary *views = @{ 91 | @"imageView": self.imageView, 92 | @"videoIndicatorView": self.videoIndicatorView, 93 | @"overlayView": self.overlayView, 94 | @"checkmarkView": self.checkmarkView, 95 | }; 96 | 97 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|" options:kNilOptions metrics:nil views:views]]; 98 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|" options:kNilOptions metrics:nil views:views]]; 99 | 100 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[overlayView]|" options:kNilOptions metrics:nil views:views]]; 101 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[overlayView]|" options:kNilOptions metrics:nil views:views]]; 102 | 103 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[videoIndicatorView]|" options:kNilOptions metrics:nil views:views]]; 104 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[videoIndicatorView(20.0)]|" options:kNilOptions metrics:nil views:views]]; 105 | 106 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[checkmarkView(24.0)]-4.0-|" options:kNilOptions metrics:nil views:views]]; 107 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-4.0-[checkmarkView(24.0)]" options:kNilOptions metrics:nil views:views]]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetsManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetsManager.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/15/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | #import "BUKImagePickerController.h" 11 | 12 | extern NSString *const kBUKImagePickerAccessDeniedNotificationName; 13 | 14 | typedef void (^BUKAssetsManagerFetchAssetsGroupsCompletionBlock)(NSArray *groups); 15 | typedef void (^BUKAssetsManagerFetchAssetsGroupsFailureBlock)(NSError *error); 16 | typedef void (^BUKAssetsManagerWriteImagesProgressBlock)(NSURL *assetURL, NSUInteger currentCount, NSUInteger totalCount); 17 | typedef void (^BUKAssetsManagerFetchAssetsProgressBlock)(ALAsset *asset, NSUInteger currentCout, NSUInteger totalCount); 18 | typedef void (^BUKAssetsManagerFetchAssetsCompletionBlock)(NSArray *assets, NSError *error); 19 | 20 | 21 | @interface BUKAssetsManager : NSObject 22 | 23 | @property (nonatomic) ALAssetsLibrary *assetsLibrary; 24 | @property (nonatomic) BUKImagePickerControllerMediaType mediaType; 25 | @property (nonatomic) ALAssetsGroupType groupTypes; 26 | @property (nonatomic) BOOL excludesEmptyGroups; 27 | 28 | + (BOOL)isAccessDenied; 29 | + (instancetype)managerWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary; 30 | + (NSArray *)assetsInAssetsGroup:(ALAssetsGroup *)assetsGroup reverse:(BOOL)reverse; 31 | + (void)fetchAssetsGroupsFromAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 32 | withGroupTypes:(ALAssetsGroupType)groupTypes 33 | mediaType:(BUKImagePickerControllerMediaType)mediaType 34 | excludesEmptyGroups:(BOOL)excludesEmptyGroups 35 | completion:(BUKAssetsManagerFetchAssetsGroupsCompletionBlock)completion 36 | failureBlock:(BUKAssetsManagerFetchAssetsGroupsFailureBlock)failureBlock; 37 | + (void)fetchAssetsFromAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 38 | withAssetURLs:(NSArray *)assetURLs 39 | progress:(BUKAssetsManagerFetchAssetsProgressBlock)progressBlock 40 | completion:(BUKAssetsManagerFetchAssetsCompletionBlock)completionBlock; 41 | 42 | - (instancetype)initWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary; 43 | - (instancetype)initWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 44 | mediaTyle:(BUKImagePickerControllerMediaType)mediaType 45 | groupTypes:(ALAssetsGroupType)groupTypes; 46 | 47 | // Fetch assets groups 48 | - (void)fetchAssetsGroupsWithCompletion:(BUKAssetsManagerFetchAssetsGroupsCompletionBlock)completion 49 | failureBlock:(BUKAssetsManagerFetchAssetsGroupsFailureBlock)failureBlock; 50 | 51 | // Write images 52 | - (void)writeImagesToSavedPhotosAlbum:(NSArray *)images 53 | progress:(BUKAssetsManagerWriteImagesProgressBlock)progressBlock 54 | completion:(void (^)(NSArray *assetURLs, NSError *error))completionBlock; 55 | - (void)writeImageToSavedPhotosAlbum:(UIImage *)image completion:(ALAssetsLibraryWriteImageCompletionBlock)completion; 56 | 57 | // Fetch assets 58 | - (void)fetchAssetsWithAssetURLs:(NSArray *)assetURLs 59 | progress:(BUKAssetsManagerFetchAssetsProgressBlock)progressBlock 60 | completion:(BUKAssetsManagerFetchAssetsCompletionBlock)completionBlock; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetsManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetsManager.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/15/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKAssetsManager.h" 10 | 11 | NSString *const kBUKImagePickerAccessDeniedNotificationName = @"BUKImagePickerAccessDenied"; 12 | 13 | @interface BUKAssetsManager () 14 | @property (nonatomic) dispatch_queue_t saveImagesQueue; 15 | @end 16 | 17 | @implementation BUKAssetsManager 18 | 19 | #pragma mark - Class Methods 20 | 21 | + (instancetype)managerWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary { 22 | return [[self alloc] initWithAssetsLibrary:assetsLibrary]; 23 | } 24 | 25 | 26 | + (NSArray *)assetsInAssetsGroup:(ALAssetsGroup *)assetsGroup reverse:(BOOL)reverse { 27 | NSMutableArray *mutableAssets = [NSMutableArray array]; 28 | NSEnumerationOptions options = reverse ? NSEnumerationReverse : kNilOptions; 29 | [assetsGroup enumerateAssetsWithOptions:options usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { 30 | if (result) { 31 | [mutableAssets addObject:result]; 32 | } 33 | }]; 34 | return mutableAssets; 35 | } 36 | 37 | 38 | + (BOOL)isAccessDenied { 39 | ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus]; 40 | BOOL result; 41 | 42 | switch (authorizationStatus) { 43 | case ALAuthorizationStatusAuthorized: 44 | case ALAuthorizationStatusNotDetermined: 45 | result = NO; 46 | break; 47 | case ALAuthorizationStatusRestricted: 48 | case ALAuthorizationStatusDenied: 49 | result = YES; 50 | break; 51 | } 52 | 53 | return result; 54 | } 55 | 56 | 57 | + (ALAssetsFilter *)assetsFilterForMediaType:(BUKImagePickerControllerMediaType)mediaType { 58 | switch (mediaType) { 59 | case BUKImagePickerControllerMediaTypeAny: { 60 | return [ALAssetsFilter allAssets]; 61 | } 62 | case BUKImagePickerControllerMediaTypeImage: { 63 | return [ALAssetsFilter allPhotos]; 64 | } 65 | case BUKImagePickerControllerMediaTypeVideo: { 66 | return [ALAssetsFilter allVideos]; 67 | } 68 | } 69 | } 70 | 71 | 72 | + (void)fetchAssetsGroupsFromAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 73 | withGroupTypes:(ALAssetsGroupType)groupTypes 74 | mediaType:(BUKImagePickerControllerMediaType)mediaType 75 | excludesEmptyGroups:(BOOL)excludesEmptyGroups 76 | completion:(BUKAssetsManagerFetchAssetsGroupsCompletionBlock)completion 77 | failureBlock:(BUKAssetsManagerFetchAssetsGroupsFailureBlock)failureBlock 78 | { 79 | NSMutableArray *assetsGroups = [NSMutableArray array]; 80 | ALAssetsFilter *assetsFilter = [self assetsFilterForMediaType:mediaType]; 81 | 82 | [assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *assetsGroup, BOOL *stop) { 83 | if (assetsGroup) { 84 | [assetsGroup setAssetsFilter:assetsFilter]; 85 | if (!excludesEmptyGroups || [assetsGroup numberOfAssets] > 0) { 86 | [assetsGroups addObject:assetsGroup]; 87 | } 88 | } 89 | // When the enumeration is done, enumerationBlock is invoked with group set to nil. 90 | else { 91 | if (completion) { 92 | completion([self sortAssetsGroups:assetsGroups]); 93 | } 94 | } 95 | } failureBlock:^(NSError *error) { 96 | NSLog(@"[BUKImagePickerController] An error occurs while fetching assets gourps: %@", [error localizedDescription]); 97 | [[NSNotificationCenter defaultCenter] postNotificationName:kBUKImagePickerAccessDeniedNotificationName object:nil]; 98 | 99 | if (failureBlock) { 100 | failureBlock(error); 101 | } 102 | }]; 103 | } 104 | 105 | 106 | + (void)fetchAssetsFromAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 107 | withAssetURLs:(NSArray *)assetURLs 108 | progress:(BUKAssetsManagerFetchAssetsProgressBlock)progressBlock 109 | completion:(BUKAssetsManagerFetchAssetsCompletionBlock)completionBlock 110 | { 111 | NSUInteger totalCount = assetURLs.count; 112 | NSMutableArray *mutableAssets = [NSMutableArray arrayWithCapacity:totalCount]; 113 | __block NSUInteger count = 0; 114 | __block NSError *lastError = nil; 115 | 116 | void (^checkNumberOfAssets)(void) = ^{ 117 | if (count == totalCount) { 118 | if (completionBlock) { 119 | completionBlock([mutableAssets copy], lastError); 120 | } 121 | } 122 | }; 123 | 124 | if (totalCount == 0) { 125 | checkNumberOfAssets(); 126 | return; 127 | } 128 | 129 | // NOTE: This is a quick and dirty solution 130 | for (NSURL *assetURL in assetURLs) { 131 | [assetsLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { 132 | count ++; 133 | if (progressBlock) { 134 | progressBlock(asset, count, totalCount); 135 | } 136 | 137 | if (asset) { 138 | [mutableAssets addObject:asset]; 139 | } 140 | 141 | checkNumberOfAssets(); 142 | } failureBlock:^(NSError *error) { 143 | NSLog(@"[BUKImagePickerController] An error occurs while fetching asset: %@", [error localizedDescription]); 144 | [[NSNotificationCenter defaultCenter] postNotificationName:kBUKImagePickerAccessDeniedNotificationName object:nil]; 145 | 146 | count ++; 147 | if (progressBlock) { 148 | progressBlock(nil, count, totalCount); 149 | } 150 | 151 | lastError = error; 152 | checkNumberOfAssets(); 153 | }]; 154 | } 155 | } 156 | 157 | 158 | + (NSArray *)sortAssetsGroups:(NSArray *)assetsGroups { 159 | NSMutableDictionary *mappedAssetsGroups = [NSMutableDictionary dictionary]; 160 | for (ALAssetsGroup *assetsGroup in assetsGroups) { 161 | NSNumber *groupType = [assetsGroup valueForProperty:ALAssetsGroupPropertyType]; 162 | NSMutableArray *array = mappedAssetsGroups[groupType]; 163 | if (!array) { 164 | array = [NSMutableArray array]; 165 | mappedAssetsGroups[groupType] = array; 166 | } 167 | [array addObject:assetsGroup]; 168 | } 169 | 170 | // Sort groups 171 | NSArray *groupTypesOrder = @[ 172 | @(ALAssetsGroupSavedPhotos), 173 | @(ALAssetsGroupPhotoStream), 174 | @(ALAssetsGroupAlbum) 175 | ]; 176 | 177 | NSMutableArray *sortedAssetsGroups = [NSMutableArray arrayWithCapacity:assetsGroups.count]; 178 | for (NSNumber *groupType in groupTypesOrder) { 179 | NSArray *array = [mappedAssetsGroups objectForKey:groupType]; 180 | if (array) { 181 | [sortedAssetsGroups addObjectsFromArray:array]; 182 | } 183 | } 184 | 185 | // Add other groups 186 | [mappedAssetsGroups enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id array, BOOL *stop) { 187 | if (!array) { 188 | return; 189 | } 190 | if (![groupTypesOrder containsObject:key]) { 191 | [sortedAssetsGroups addObjectsFromArray:array]; 192 | } 193 | }]; 194 | 195 | return sortedAssetsGroups; 196 | } 197 | 198 | 199 | #pragma mark - NSObject 200 | 201 | - (instancetype)init { 202 | return [self initWithAssetsLibrary:[[ALAssetsLibrary alloc] init]]; 203 | } 204 | 205 | 206 | - (instancetype)initWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary { 207 | if ((self = [super init])) { 208 | _assetsLibrary = assetsLibrary; 209 | _groupTypes = ALAssetsGroupSavedPhotos | ALAssetsGroupPhotoStream | ALAssetsGroupAlbum; 210 | _mediaType = BUKImagePickerControllerMediaTypeAny; 211 | _excludesEmptyGroups = NO; 212 | _saveImagesQueue = dispatch_queue_create("com.buk-image-picker-controller.save-images-queue", DISPATCH_QUEUE_SERIAL); 213 | } 214 | return self; 215 | } 216 | 217 | 218 | - (instancetype)initWithAssetsLibrary:(ALAssetsLibrary *)assetsLibrary 219 | mediaTyle:(BUKImagePickerControllerMediaType)mediaType 220 | groupTypes:(ALAssetsGroupType)groupTypes 221 | { 222 | if ((self = [self initWithAssetsLibrary:assetsLibrary])) { 223 | _mediaType = mediaType; 224 | _groupTypes = groupTypes; 225 | } 226 | return self; 227 | } 228 | 229 | 230 | #pragma mark - Public 231 | 232 | - (void)fetchAssetsGroupsWithCompletion:(BUKAssetsManagerFetchAssetsGroupsCompletionBlock)completion failureBlock:(BUKAssetsManagerFetchAssetsGroupsFailureBlock)failureBlock 233 | { 234 | return [[self class] fetchAssetsGroupsFromAssetsLibrary:self.assetsLibrary 235 | withGroupTypes:self.groupTypes 236 | mediaType:self.mediaType 237 | excludesEmptyGroups:self.excludesEmptyGroups 238 | completion:completion 239 | failureBlock:failureBlock]; 240 | } 241 | 242 | 243 | - (void)writeImagesToSavedPhotosAlbum:(NSArray *)images 244 | progress:(BUKAssetsManagerWriteImagesProgressBlock)progressBlock 245 | completion:(void (^)(NSArray *, NSError *))completionBlock 246 | { 247 | NSUInteger totalCount = images.count; 248 | NSMutableArray *mutableAssetURLs = [NSMutableArray arrayWithCapacity:totalCount]; 249 | 250 | if (totalCount == 0) { 251 | if (completionBlock) { 252 | completionBlock(mutableAssetURLs, nil); 253 | } 254 | return; 255 | } 256 | 257 | __block NSUInteger count = 0; 258 | __weak typeof(self)weakSelf = self; 259 | 260 | for (UIImage *image in images) { 261 | dispatch_async(self.saveImagesQueue, ^(void) { 262 | __strong __typeof(weakSelf)strongSelf = weakSelf; 263 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 264 | [strongSelf writeImageToSavedPhotosAlbum:image completion:^(NSURL *assetURL, NSError *error) { 265 | count ++; 266 | if (assetURL) { 267 | NSLog(@"[BUKImagePicker] Saved image to photos album."); 268 | 269 | [mutableAssetURLs addObject:assetURL]; 270 | dispatch_async(dispatch_get_main_queue(), ^(void) { 271 | if (progressBlock) { 272 | progressBlock(assetURL, count, totalCount); 273 | } 274 | }); 275 | } else { 276 | NSLog(@"[BUKImagePicker] Saving images failed: %@", error); 277 | } 278 | 279 | if (count == totalCount) { 280 | dispatch_async(dispatch_get_main_queue(), ^(void) { 281 | if (completionBlock) { 282 | completionBlock(mutableAssetURLs, nil); 283 | } 284 | }); 285 | } 286 | dispatch_semaphore_signal(semaphore); 287 | }]; 288 | 289 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 290 | }); 291 | } 292 | } 293 | 294 | 295 | - (void)writeImageToSavedPhotosAlbum:(UIImage *)image 296 | completion:(ALAssetsLibraryWriteImageCompletionBlock)completion 297 | { 298 | [self.assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)image.imageOrientation completionBlock:completion]; 299 | } 300 | 301 | 302 | - (void)fetchAssetsWithAssetURLs:(NSArray *)assetURLs 303 | progress:(BUKAssetsManagerFetchAssetsProgressBlock)progressBlock 304 | completion:(BUKAssetsManagerFetchAssetsCompletionBlock)completionBlock 305 | { 306 | [[self class] fetchAssetsFromAssetsLibrary:self.assetsLibrary withAssetURLs:assetURLs progress:progressBlock completion:completionBlock]; 307 | } 308 | 309 | @end 310 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetsViewController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @class ALAssetsGroup; 12 | @protocol BUKAssetsViewControllerDelegate; 13 | 14 | @interface BUKAssetsViewController : UICollectionViewController 15 | 16 | @property (nonatomic) ALAssetsGroup *assetsGroup; 17 | @property (nonatomic, readonly) NSArray *assets; 18 | @property (nonatomic, weak) id delegate; 19 | @property (nonatomic) BOOL allowsMultipleSelection; 20 | @property (nonatomic) BOOL reversesAssets; 21 | @property (nonatomic) BOOL showsCameraCell; 22 | @property (nonatomic) CGFloat minimumInteritemSpacing; 23 | @property (nonatomic) CGFloat minimumLineSpacing; 24 | @property (nonatomic) NSUInteger numberOfColumnsInPortrait; 25 | @property (nonatomic) NSUInteger numberOfColumnsInLandscape; 26 | @property (nonatomic) BOOL ignoreChange; 27 | 28 | - (void)updateAssets; 29 | 30 | @end 31 | 32 | 33 | @class ALAsset; 34 | 35 | @protocol BUKAssetsViewControllerDelegate 36 | 37 | @optional 38 | - (BOOL)assetsViewController:(BUKAssetsViewController *)assetsViewController shouldSelectAsset:(ALAsset *)asset; 39 | - (void)assetsViewController:(BUKAssetsViewController *)assetsViewController didSelectAsset:(ALAsset *)asset; 40 | - (void)assetsViewController:(BUKAssetsViewController *)assetsViewController didDeselectAsset:(ALAsset *)asset; 41 | 42 | - (void)assetsViewControllerDidFinishPicking:(BUKAssetsViewController *)assetsViewController; 43 | - (void)assetsViewControllerDidCancel:(BUKAssetsViewController *)assetsViewController; 44 | 45 | - (void)assetsViewControllerDidSelectCamera:(BUKAssetsViewController *)assetsViewController; 46 | 47 | - (BOOL)assetsViewControllerShouldEnableDoneButton:(BUKAssetsViewController *)assetsViewController; 48 | - (BOOL)assetsViewController:(BUKAssetsViewController *)assetsViewController isAssetSelected:(ALAsset *)asset; 49 | - (BOOL)assetsViewControllerShouldShowSelectionInfo:(BUKAssetsViewController *)assetsViewController; 50 | - (NSString *)assetsViewControllerSelectionInfo:(BUKAssetsViewController *)assetsViewController; 51 | 52 | @end 53 | 54 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKAssetsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAssetsViewController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | 11 | #import "BUKAssetsViewController.h" 12 | #import "BUKImagePickerController.h" 13 | #import "BUKAssetCollectionViewCell.h" 14 | #import "BUKCameraCollectionViewCell.h" 15 | #import "BUKVideoIndicatorView.h" 16 | #import "BUKNoAssetsPlaceholderView.h" 17 | #import "BUKAssetsManager.h" 18 | #import "UIImage+BUKImagePickerController.h" 19 | #import "NSBundle+BUKImagePickerController.h" 20 | 21 | static NSString *const kBUKAssetsViewControllerAssetCellIdentifier = @"AssetCell"; 22 | static NSString *const kBUKAssetsViewControllerCameraCellIdentifier = @"CameraCell"; 23 | 24 | 25 | @interface BUKAssetsViewController () 26 | @property (nonatomic, readwrite) NSArray *assets; 27 | @property (nonatomic) UIBarButtonItem *doneBarButtonItem; 28 | @property (nonatomic) UIView *placeholderView; 29 | @end 30 | 31 | 32 | @implementation BUKAssetsViewController 33 | 34 | #pragma mark - Accessors 35 | 36 | - (void)setAssetsGroup:(ALAssetsGroup *)assetsGroup { 37 | if (_assetsGroup == assetsGroup) { 38 | return; 39 | } 40 | 41 | _assetsGroup = assetsGroup; 42 | 43 | self.title = [assetsGroup valueForProperty:ALAssetsGroupPropertyName]; 44 | 45 | [self updateAssets]; 46 | } 47 | 48 | 49 | #pragma mark - NSObject 50 | 51 | - (void)dealloc { 52 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 53 | } 54 | 55 | 56 | - (instancetype)init { 57 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 58 | 59 | if ((self = [super initWithCollectionViewLayout:layout])) { 60 | _minimumInteritemSpacing = 2.0; 61 | _minimumLineSpacing = 2.0; 62 | _ignoreChange = NO; 63 | 64 | layout.minimumInteritemSpacing = _minimumInteritemSpacing; 65 | layout.minimumLineSpacing = _minimumLineSpacing; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | 72 | #pragma mark - UIViewController 73 | 74 | - (void)viewDidLoad { 75 | [super viewDidLoad]; 76 | 77 | if (self.allowsMultipleSelection) { 78 | self.doneBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Done", nil) style:UIBarButtonItemStyleDone target:self action:@selector(finishPicking:)]; 79 | self.navigationItem.rightBarButtonItem = self.doneBarButtonItem; 80 | } else { 81 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Cancel", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)]; 82 | } 83 | 84 | [self setUpToolbar]; 85 | 86 | // Configure collection view 87 | self.collectionView.backgroundColor = [UIColor whiteColor]; 88 | self.collectionView.alwaysBounceVertical = YES; 89 | self.collectionView.allowsMultipleSelection = self.allowsMultipleSelection; 90 | [self.collectionView registerClass:[BUKAssetCollectionViewCell class] forCellWithReuseIdentifier:kBUKAssetsViewControllerAssetCellIdentifier]; 91 | [self.collectionView registerClass:[BUKCameraCollectionViewCell class] forCellWithReuseIdentifier:kBUKAssetsViewControllerCameraCellIdentifier]; 92 | 93 | self.placeholderView = [[BUKNoAssetsPlaceholderView alloc] init]; 94 | 95 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(assetsLibraryChanged:) name:ALAssetsLibraryChangedNotification object:nil]; 96 | 97 | [self updateAssets]; 98 | } 99 | 100 | 101 | - (void)viewWillAppear:(BOOL)animated { 102 | [super viewWillAppear:animated]; 103 | 104 | [self updateDoneButton]; 105 | [self scrollToLatestPhotos]; 106 | [self updateSelectionInfo]; 107 | } 108 | 109 | 110 | #pragma mark - Actions 111 | 112 | - (void)cancel:(id)sender { 113 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerDidCancel:)]) { 114 | [self.delegate assetsViewControllerDidCancel:self]; 115 | } else { 116 | [self dismissViewControllerAnimated:YES completion:nil]; 117 | } 118 | } 119 | 120 | 121 | - (void)finishPicking:(id)sender { 122 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerDidFinishPicking:)]) { 123 | [self.delegate assetsViewControllerDidFinishPicking:self]; 124 | } else { 125 | [self dismissViewControllerAnimated:YES completion:nil]; 126 | } 127 | } 128 | 129 | 130 | #pragma mark - UICollectionViewDataSource 131 | 132 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 133 | return 1; 134 | } 135 | 136 | 137 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 138 | return self.showsCameraCell ? self.assets.count + 1 : self.assets.count; 139 | } 140 | 141 | 142 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 143 | UICollectionViewCell *cell = nil; 144 | if (indexPath.item == 0 && self.showsCameraCell) { 145 | BUKCameraCollectionViewCell *cameraCell = [collectionView dequeueReusableCellWithReuseIdentifier:kBUKAssetsViewControllerCameraCellIdentifier forIndexPath:indexPath]; 146 | cell = cameraCell; 147 | } else { 148 | BUKAssetCollectionViewCell *assetCell = [collectionView dequeueReusableCellWithReuseIdentifier:kBUKAssetsViewControllerAssetCellIdentifier forIndexPath:indexPath]; 149 | [self configureCell:assetCell forItemAtIndexPath:indexPath]; 150 | cell = assetCell; 151 | } 152 | 153 | return cell; 154 | } 155 | 156 | 157 | #pragma mark - UICollectionViewDelegate 158 | 159 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath { 160 | if (self.showsCameraCell && indexPath.item == 0) { 161 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerDidSelectCamera:)]) { 162 | [self.delegate assetsViewControllerDidSelectCamera:self]; 163 | } 164 | return NO; 165 | } 166 | 167 | if ([self.delegate respondsToSelector:@selector(assetsViewController:shouldSelectAsset:)]) { 168 | ALAsset *asset = [self assetItemAtIndexPath:indexPath]; 169 | return [self.delegate assetsViewController:self shouldSelectAsset:asset]; 170 | } 171 | 172 | return YES; 173 | } 174 | 175 | 176 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 177 | if ([self.delegate respondsToSelector:@selector(assetsViewController:didSelectAsset:)]) { 178 | ALAsset *asset = [self assetItemAtIndexPath:indexPath]; 179 | [self.delegate assetsViewController:self didSelectAsset:asset]; 180 | } 181 | 182 | [self updateDoneButton]; 183 | [self updateSelectionInfo]; 184 | } 185 | 186 | 187 | - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { 188 | if ([self.delegate respondsToSelector:@selector(assetsViewController:didDeselectAsset:)]) { 189 | ALAsset *asset = [self assetItemAtIndexPath:indexPath]; 190 | [self.delegate assetsViewController:self didDeselectAsset:asset]; 191 | } 192 | 193 | [self updateDoneButton]; 194 | [self updateSelectionInfo]; 195 | } 196 | 197 | 198 | #pragma mark - UICollectionViewDelegateFlowLayout 199 | 200 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 201 | NSUInteger numberOfColumns; 202 | if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { 203 | numberOfColumns = self.numberOfColumnsInPortrait; 204 | } else { 205 | numberOfColumns = self.numberOfColumnsInLandscape; 206 | } 207 | 208 | CGFloat width = (self.view.bounds.size.width - 2.0 * (numberOfColumns + 1)) / numberOfColumns; 209 | 210 | return CGSizeMake(width, width); 211 | } 212 | 213 | 214 | #pragma mark - Private 215 | 216 | - (BOOL)hasContent { 217 | return self.assets.count > 0 || self.showsCameraCell; 218 | } 219 | 220 | 221 | - (void)updatePlaceholderView:(BOOL)animated { 222 | if (![self hasContent]) { 223 | [self showPlacehoderView:animated]; 224 | } else { 225 | [self hidePlaceholderView:animated]; 226 | } 227 | } 228 | 229 | 230 | - (void)showPlacehoderView:(BOOL)animated { 231 | if (!self.placeholderView || self.placeholderView.superview) { 232 | return; 233 | } 234 | 235 | self.placeholderView.alpha = 0; 236 | self.placeholderView.translatesAutoresizingMaskIntoConstraints = NO; 237 | [self.view addSubview:self.placeholderView]; 238 | 239 | // Add constraints 240 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0]]; 241 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; 242 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:0]]; 243 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]]; 244 | 245 | void (^change)(void) = ^{ 246 | self.placeholderView.alpha = 1.0f; 247 | }; 248 | 249 | if (animated) { 250 | [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:change completion:nil]; 251 | } else { 252 | change(); 253 | } 254 | } 255 | 256 | 257 | - (void)hidePlaceholderView:(BOOL)animated { 258 | if (!self.placeholderView || !self.placeholderView.superview) { 259 | return; 260 | } 261 | 262 | void (^change)(void) = ^{ 263 | self.placeholderView.alpha = 0.0f; 264 | }; 265 | 266 | void (^completion)(BOOL finished) = ^(BOOL finished) { 267 | [self.placeholderView removeFromSuperview]; 268 | }; 269 | 270 | if (animated) { 271 | [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:change completion:completion]; 272 | } else { 273 | change(); 274 | completion(YES); 275 | } 276 | } 277 | 278 | 279 | - (void)setUpToolbar { 280 | UIBarButtonItem *leftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL]; 281 | UIBarButtonItem *rightSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL]; 282 | 283 | NSDictionary *attributes = @{ 284 | NSForegroundColorAttributeName: [UIColor blackColor], 285 | NSFontAttributeName: [UIFont systemFontOfSize:14.0], 286 | }; 287 | UIBarButtonItem *infoButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL]; 288 | infoButtonItem.enabled = NO; 289 | [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal]; 290 | [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateDisabled]; 291 | 292 | self.toolbarItems = @[leftSpace, infoButtonItem, rightSpace]; 293 | } 294 | 295 | 296 | - (void)updateSelectionInfo { 297 | BOOL shouldShow = NO; 298 | NSString *text = nil; 299 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerShouldShowSelectionInfo:)]) { 300 | shouldShow = [self.delegate assetsViewControllerShouldShowSelectionInfo:self]; 301 | } 302 | 303 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerSelectionInfo:)]) { 304 | text = [self.delegate assetsViewControllerSelectionInfo:self]; 305 | } 306 | 307 | shouldShow = shouldShow && text != nil; 308 | 309 | if (shouldShow) { 310 | UIBarButtonItem *barButtonItem = self.toolbarItems[1]; 311 | barButtonItem.title = text; 312 | } 313 | 314 | BOOL shoulHidden = !shouldShow; 315 | if (shoulHidden != self.navigationController.toolbarHidden) { 316 | [self.navigationController setToolbarHidden:shoulHidden animated:YES]; 317 | } 318 | } 319 | 320 | 321 | - (void)scrollToLatestPhotos { 322 | if (self.reversesAssets) { 323 | NSInteger section = 0; 324 | NSInteger count = [self collectionView:self.collectionView numberOfItemsInSection:section] ; 325 | if (count > 0) { 326 | // Make sure the collection view scrolls to the bottom, it's a little tricky though. 327 | dispatch_async(dispatch_get_main_queue(), ^(void) { 328 | [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO]; 329 | }); 330 | } 331 | } else { 332 | NSInteger section = [self numberOfSectionsInCollectionView:self.collectionView] - 1; 333 | NSInteger item = [self collectionView:self.collectionView numberOfItemsInSection:section] - 1; 334 | if (item >= 0) { 335 | dispatch_async(dispatch_get_main_queue(), ^(void) { 336 | [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:section] atScrollPosition:UICollectionViewScrollPositionTop animated:NO]; 337 | }); 338 | } 339 | } 340 | } 341 | 342 | 343 | - (void)updateDoneButton { 344 | if ([self.delegate respondsToSelector:@selector(assetsViewControllerShouldEnableDoneButton:)]) { 345 | self.doneBarButtonItem.enabled = [self.delegate assetsViewControllerShouldEnableDoneButton:self]; 346 | } else { 347 | self.doneBarButtonItem.enabled = YES; 348 | } 349 | } 350 | 351 | 352 | - (NSUInteger)assetIndexForViewIndexPath:(NSIndexPath *)indexPath { 353 | return self.showsCameraCell ? indexPath.item - 1 : indexPath.item; 354 | } 355 | 356 | 357 | - (NSIndexPath *)viewIndexPathForAssetsIndex:(NSUInteger)index { 358 | return self.showsCameraCell ? [NSIndexPath indexPathForItem:(index + 1) inSection:0] : [NSIndexPath indexPathForItem:index inSection:0]; 359 | } 360 | 361 | 362 | - (ALAsset *)assetItemAtIndexPath:(NSIndexPath *)indexPath { 363 | return self.assets[[self assetIndexForViewIndexPath:indexPath]]; 364 | } 365 | 366 | 367 | - (void)configureCell:(BUKAssetCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 368 | ALAsset *asset = [self assetItemAtIndexPath:indexPath]; 369 | 370 | cell.tag = indexPath.item; 371 | cell.showsOverlayViewWhenSelected = self.allowsMultipleSelection; 372 | 373 | // Load thumbnail asynchronously 374 | __weak BUKAssetCollectionViewCell *weakCell = cell; 375 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { 376 | UIImage *thumbnailImage = [UIImage imageWithCGImage:[asset thumbnail]]; 377 | 378 | dispatch_async(dispatch_get_main_queue(), ^(void) { 379 | if (weakCell.tag == indexPath.item) { 380 | cell.imageView.image = thumbnailImage; 381 | } 382 | }); 383 | }); 384 | 385 | // Video indicator 386 | NSString *assetType = [asset valueForProperty:ALAssetPropertyType]; 387 | 388 | if ([assetType isEqualToString:ALAssetTypeVideo]) { 389 | cell.videoIndicatorView.hidden = NO; 390 | 391 | NSTimeInterval duration = [[asset valueForProperty:ALAssetPropertyDuration] doubleValue]; 392 | NSInteger minutes = (NSInteger)(duration / 60.0); 393 | NSInteger seconds = (NSInteger)ceil(duration - 60.0 * (double)minutes); 394 | cell.videoIndicatorView.timeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds]; 395 | } else { 396 | cell.videoIndicatorView.hidden = YES; 397 | } 398 | 399 | // Select 400 | if ([self.delegate respondsToSelector:@selector(assetsViewController:isAssetSelected:)]) { 401 | BOOL selected = [self.delegate assetsViewController:self isAssetSelected:asset]; 402 | cell.selected = selected; 403 | if (selected) { 404 | [self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; 405 | } 406 | } 407 | } 408 | 409 | 410 | - (void)updateAssets { 411 | self.assets = [BUKAssetsManager assetsInAssetsGroup:self.assetsGroup reverse:self.reversesAssets]; 412 | 413 | if (self.isViewLoaded) { 414 | [self updatePlaceholderView:NO]; 415 | [self.collectionView reloadData]; 416 | [self updateSelectionInfo]; 417 | } 418 | } 419 | 420 | 421 | #pragma mark - Handle Assets Library Changes 422 | 423 | - (void)assetsLibraryChanged:(NSNotification *)notification { 424 | dispatch_async(dispatch_get_main_queue(), ^{ 425 | if (self.ignoreChange) { 426 | return; 427 | } 428 | 429 | NSURL *assetsGroupURL = [self.assetsGroup valueForProperty:ALAssetsGroupPropertyURL]; 430 | NSSet *updatedAssetsGroupsURLs = notification.userInfo[ALAssetLibraryUpdatedAssetGroupsKey]; 431 | for (NSURL *groupURL in updatedAssetsGroupsURLs) { 432 | if ([groupURL isEqual:assetsGroupURL]) { 433 | [self updateAssets]; 434 | } 435 | } 436 | }); 437 | } 438 | 439 | @end 440 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraCollectionViewCell.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 10/22/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKCameraCollectionViewCell : UICollectionViewCell 12 | 13 | @property (nonatomic, readonly) UIImageView *cameraIconImageView; 14 | @property (nonatomic, readonly) UILabel *titleLabel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraCollectionViewCell.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 10/22/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKCameraCollectionViewCell.h" 10 | #import "UIImage+BUKImagePickerController.h" 11 | #import "NSBundle+BUKImagePickerController.h" 12 | 13 | @implementation BUKCameraCollectionViewCell 14 | 15 | #pragma mark - Accessors 16 | 17 | @synthesize cameraIconImageView = _cameraIconImageView; 18 | @synthesize titleLabel = _titleLabel; 19 | 20 | - (UIImageView *)cameraIconImageView { 21 | if (!_cameraIconImageView) { 22 | _cameraIconImageView = [[UIImageView alloc] initWithImage:[UIImage buk_bundleImageNamed:@"camera-icon"]]; 23 | _cameraIconImageView.translatesAutoresizingMaskIntoConstraints = NO; 24 | _cameraIconImageView.contentMode = UIViewContentModeScaleAspectFit; 25 | } 26 | return _cameraIconImageView; 27 | } 28 | 29 | 30 | - (UILabel *)titleLabel { 31 | if (!_titleLabel) { 32 | _titleLabel = [[UILabel alloc] init]; 33 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 34 | _titleLabel.font = [UIFont systemFontOfSize:10.0]; 35 | _titleLabel.textColor = [UIColor whiteColor]; 36 | _titleLabel.textAlignment = NSTextAlignmentCenter; 37 | _titleLabel.text = BUKImagePickerLocalizedString(@"Take Photos", nil); 38 | } 39 | return _titleLabel; 40 | } 41 | 42 | 43 | #pragma mark - UIView 44 | 45 | - (instancetype)initWithFrame:(CGRect)frame { 46 | if ((self = [super initWithFrame:frame])) { 47 | self.contentView.backgroundColor = [UIColor colorWithWhite:0.42f alpha:1.0]; 48 | [self.contentView addSubview:self.cameraIconImageView]; 49 | [self.contentView addSubview:self.titleLabel]; 50 | [self setupViewConstraints]; 51 | } 52 | return self; 53 | } 54 | 55 | 56 | #pragma mark - Private 57 | 58 | - (void)setupViewConstraints { 59 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraIconImageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 60 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.cameraIconImageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:-6.0]]; 61 | 62 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 63 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.cameraIconImageView attribute:NSLayoutAttributeBottom multiplier:1 constant:2.0]]; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraComfirmViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraComfirmViewController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/17/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKCameraConfirmViewController.h" 10 | #import "FastttCapturedImage.h" 11 | #import "NSBundle+BUKImagePickerController.h" 12 | 13 | @interface BUKCameraConfirmViewController () 14 | 15 | @property (nonatomic) UIButton *cancelButton; 16 | @property (nonatomic) UIButton *confirmButton; 17 | @property (nonatomic) UIView *bottomToolbarView; 18 | @property (nonatomic) UIImageView *previewImageView; 19 | 20 | @end 21 | 22 | 23 | @implementation BUKCameraConfirmViewController 24 | 25 | #pragma mark - Accessors 26 | 27 | - (UIImageView *)previewImageView { 28 | if (!_previewImageView) { 29 | _previewImageView = [[UIImageView alloc] init]; 30 | _previewImageView.translatesAutoresizingMaskIntoConstraints = NO; 31 | } 32 | return _previewImageView; 33 | } 34 | 35 | 36 | - (UIButton *)confirmButton { 37 | if (!_confirmButton) { 38 | _confirmButton = [[UIButton alloc] init]; 39 | _confirmButton.translatesAutoresizingMaskIntoConstraints = NO; 40 | [_confirmButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 41 | [_confirmButton setTitle:BUKImagePickerLocalizedString(@"Confirm", nil) forState:UIControlStateNormal]; 42 | [_confirmButton addTarget:self action:@selector(confirmImage:) forControlEvents:UIControlEventTouchUpInside]; 43 | } 44 | return _confirmButton; 45 | } 46 | 47 | 48 | - (UIButton *)cancelButton { 49 | if (!_cancelButton) { 50 | _cancelButton = [[UIButton alloc] init]; 51 | _cancelButton.translatesAutoresizingMaskIntoConstraints = NO; 52 | [_cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 53 | [_cancelButton setTitle:BUKImagePickerLocalizedString(@"Retake", nil) forState:UIControlStateNormal]; 54 | [_cancelButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside]; 55 | } 56 | return _cancelButton; 57 | } 58 | 59 | 60 | - (UIView *)bottomToolbarView { 61 | if (!_bottomToolbarView) { 62 | _bottomToolbarView = [[UIView alloc] init]; 63 | _bottomToolbarView.translatesAutoresizingMaskIntoConstraints = NO; 64 | _bottomToolbarView.backgroundColor = [UIColor blackColor]; 65 | } 66 | return _bottomToolbarView; 67 | } 68 | 69 | 70 | #pragma mark - UIViewController 71 | 72 | - (void)viewDidLoad { 73 | [super viewDidLoad]; 74 | 75 | self.view.backgroundColor = [UIColor blackColor]; 76 | 77 | [self.view addSubview:self.previewImageView]; 78 | [self.view addSubview:self.bottomToolbarView]; 79 | [self.bottomToolbarView addSubview:self.cancelButton]; 80 | [self.bottomToolbarView addSubview:self.confirmButton]; 81 | 82 | [self setupViewConstraints]; 83 | } 84 | 85 | 86 | - (void)viewWillAppear:(BOOL)animated { 87 | [super viewWillAppear:animated]; 88 | 89 | if (self.capturedImage.rotatedPreviewImage) { 90 | self.previewImageView.image = self.capturedImage.rotatedPreviewImage; 91 | } else { 92 | self.previewImageView.image = self.capturedImage.fullImage; 93 | } 94 | } 95 | 96 | 97 | #pragma mark - Actions 98 | 99 | - (void)confirmImage:(id)sender { 100 | [self.delegate cameraConfirmViewControllerDidConfirm:self capturedImage:self.capturedImage]; 101 | } 102 | 103 | 104 | - (void)cancel:(id)sender { 105 | [self.delegate cameraConfirmViewControllerDidCancel:self]; 106 | } 107 | 108 | 109 | #pragma mark - Private 110 | 111 | - (void)setupViewConstraints { 112 | NSDictionary *views = @{ 113 | @"previewImageView": self.previewImageView, 114 | @"cancelButton": self.cancelButton, 115 | @"confirmButton": self.confirmButton, 116 | @"bottomToolbarView": self.bottomToolbarView, 117 | }; 118 | 119 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(40.0)-[previewImageView][bottomToolbarView(100.0)]|" options:kNilOptions metrics:nil views:views]]; 120 | 121 | // Image preview 122 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[previewImageView]|" options:kNilOptions metrics:nil views:views]]; 123 | 124 | // Bottom toolbar view 125 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[bottomToolbarView]|" options:kNilOptions metrics:nil views:views]]; 126 | [self.bottomToolbarView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10.0)-[cancelButton]-(>=100)-[confirmButton]-(10.0)-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; 127 | [self.bottomToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.cancelButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.bottomToolbarView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraConfirmViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraComfirmViewController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/17/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | 10 | @import UIKit; 11 | 12 | @class FastttCapturedImage; 13 | @protocol BUKCameraConfirmViewControllerDelegate; 14 | 15 | @interface BUKCameraConfirmViewController : UIViewController 16 | 17 | @property (nonatomic, weak) id delegate; 18 | @property (nonatomic) FastttCapturedImage *capturedImage; 19 | 20 | @end 21 | 22 | 23 | @protocol BUKCameraConfirmViewControllerDelegate 24 | @required 25 | - (void)cameraConfirmViewControllerDidCancel:(BUKCameraConfirmViewController *)viewController; 26 | - (void)cameraConfirmViewControllerDidConfirm:(BUKCameraConfirmViewController *)viewController capturedImage:(FastttCapturedImage *)capturedImage; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraImageCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraImageCollectionViewCell.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/13/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @protocol BUKCameraImageCollectionViewCellDelegate; 12 | 13 | @interface BUKCameraImageCollectionViewCell : UICollectionViewCell 14 | 15 | @property (nonatomic, readonly) UIImageView *imageView; 16 | @property (nonatomic, readonly) UIButton *deleteButton; 17 | @property (nonatomic, weak) id delegate; 18 | 19 | @end 20 | 21 | 22 | @protocol BUKCameraImageCollectionViewCellDelegate 23 | @optional 24 | - (void)imageCollectionViewCell:(BUKCameraImageCollectionViewCell *)cell didClickDeleteButton:(UIButton *)button; 25 | @end 26 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraImageCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraImageCollectionViewCell.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/13/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKCameraImageCollectionViewCell.h" 10 | #import "UIImage+BUKImagePickerController.h" 11 | 12 | @implementation BUKCameraImageCollectionViewCell 13 | 14 | #pragma mark - Accessors 15 | 16 | @synthesize imageView = _imageView; 17 | @synthesize deleteButton = _deleteButton; 18 | 19 | - (UIImageView *)imageView { 20 | if (!_imageView) { 21 | _imageView = [[UIImageView alloc] init]; 22 | _imageView.translatesAutoresizingMaskIntoConstraints = NO; 23 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 24 | _imageView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5f]; 25 | } 26 | return _imageView; 27 | } 28 | 29 | 30 | - (UIButton *)deleteButton { 31 | if (!_deleteButton) { 32 | _deleteButton = [[UIButton alloc] init]; 33 | _deleteButton.translatesAutoresizingMaskIntoConstraints = NO; 34 | [_deleteButton setImage:[UIImage buk_bundleImageNamed:@"delete-button"] forState:UIControlStateNormal]; 35 | [_deleteButton addTarget:self action:@selector(deleteButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; 36 | } 37 | return _deleteButton; 38 | } 39 | 40 | 41 | #pragma mark - UICollectionViewCell 42 | 43 | - (instancetype)initWithFrame:(CGRect)frame { 44 | if ((self = [super initWithFrame:frame])) { 45 | [self.contentView addSubview:self.imageView]; 46 | [self.contentView addSubview:self.deleteButton]; 47 | [self setupViewConstraints]; 48 | } 49 | return self; 50 | } 51 | 52 | 53 | #pragma mark - Actions 54 | 55 | - (void)deleteButtonClicked:(id)sender { 56 | if ([self.delegate respondsToSelector:@selector(imageCollectionViewCell:didClickDeleteButton:)]) { 57 | [self.delegate imageCollectionViewCell:self didClickDeleteButton:sender]; 58 | } 59 | } 60 | 61 | 62 | #pragma mark - Private 63 | 64 | - (void)setupViewConstraints { 65 | NSDictionary *views = @{ 66 | @"imageView": self.imageView, 67 | @"deleteButton": self.deleteButton, 68 | }; 69 | 70 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]-12.0-|" options:kNilOptions metrics:nil views:views]]; 71 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-12.0-[imageView]|" options:kNilOptions metrics:nil views:views]]; 72 | 73 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[deleteButton]-(5.0)-|" options:kNilOptions metrics:nil views:views]]; 74 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(5.0)-[deleteButton]" options:kNilOptions metrics:nil views:views]]; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraViewController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @class FastttCamera; 12 | @protocol BUKCameraViewControllerDelegate; 13 | 14 | @interface BUKCameraViewController : UIViewController 15 | 16 | @property (nonatomic, weak) id delegate; 17 | @property (nonatomic, readonly) FastttCamera *fastCamera; 18 | @property (nonatomic, readonly) NSArray *capturedImages; 19 | @property (nonatomic) CGSize thumbnailSize; 20 | @property (nonatomic) BOOL allowsMultipleSelection; 21 | @property (nonatomic) BOOL needsConfirmation; 22 | @property (nonatomic) BOOL usesScaledImage; 23 | @property (nonatomic) CGFloat maxScaledDimension; 24 | 25 | @end 26 | 27 | @protocol BUKCameraViewControllerDelegate 28 | @optional 29 | - (void)cameraViewControllerDidCancel:(BUKCameraViewController *)cameraViewController; 30 | - (void)cameraViewController:(BUKCameraViewController *)cameraViewController didFinishCapturingImages:(NSArray *)images; 31 | - (BOOL)cameraViewControllerShouldTakePicture:(BUKCameraViewController *)cameraViewController; 32 | 33 | - (BOOL)cameraViewControllerShouldEnableDoneButton:(BUKCameraViewController *)cameraViewController; 34 | - (void)userDeniedCameraPermissionsForCameraViewController:(BUKCameraViewController *)cameraViewController; 35 | 36 | - (BOOL)cameraViewControllerShouldShowSelectionInfo:(BUKCameraViewController *)cameraViewController; 37 | - (NSString *)cameraViewControllerSelectionInfo:(BUKCameraViewController *)cameraViewController; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCameraViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCameraViewController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BUKCameraViewController.h" 11 | #import "BUKCameraConfirmViewController.h" 12 | #import "BUKCameraImageCollectionViewCell.h" 13 | #import "UIImage+BUKImagePickerController.h" 14 | #import "NSBundle+BUKImagePickerController.h" 15 | 16 | static NSString *const kBUKCameraViewControllerCellIdentifier = @"cell"; 17 | 18 | @interface BUKCameraViewController () 19 | 20 | @property (nonatomic) UIButton *takePictureButton; 21 | @property (nonatomic) UIButton *flashModeButton; 22 | @property (nonatomic) UIButton *cameraDeviceButton; 23 | @property (nonatomic) UIButton *doneButton; 24 | @property (nonatomic) UIButton *cancelButton; 25 | @property (nonatomic) UILabel *selectionInfoLabel; 26 | @property (nonatomic) UIView *topToolbarView; 27 | @property (nonatomic) UIView *bottomToolbarView; 28 | @property (nonatomic) UIView *flashView; 29 | @property (nonatomic) UICollectionView *collectionView; 30 | @property (nonatomic) NSMutableArray *mutableCapturedImages; 31 | @property (nonatomic) FastttCameraFlashMode flashMode; 32 | @property (nonatomic) FastttCameraDevice cameraDevice; 33 | 34 | @end 35 | 36 | 37 | @implementation BUKCameraViewController 38 | 39 | #pragma mark - Accessors 40 | 41 | @synthesize fastCamera = _fastCamera; 42 | @dynamic flashMode; 43 | @dynamic cameraDevice; 44 | 45 | - (FastttCamera *)fastCamera { 46 | if (!_fastCamera) { 47 | _fastCamera = [[FastttCamera alloc] init]; 48 | _fastCamera.delegate = self; 49 | _fastCamera.normalizesImageOrientations = NO; 50 | _fastCamera.returnsRotatedPreview = self.needsConfirmation; 51 | if (self.usesScaledImage) { 52 | if (self.maxScaledDimension > 0) { 53 | _fastCamera.maxScaledDimension = self.maxScaledDimension; 54 | } 55 | } else { 56 | _fastCamera.maxScaledDimension = MAX(self.thumbnailSize.width, self.thumbnailSize.height); 57 | } 58 | } 59 | return _fastCamera; 60 | } 61 | 62 | 63 | - (UIButton *)takePictureButton { 64 | if (!_takePictureButton) { 65 | _takePictureButton = [[UIButton alloc] init]; 66 | _takePictureButton.translatesAutoresizingMaskIntoConstraints = NO; 67 | [_takePictureButton setImage:[UIImage buk_bundleImageNamed:@"take-picture-button"] forState:UIControlStateNormal]; 68 | [_takePictureButton addTarget:self action:@selector(takePicture:) forControlEvents:UIControlEventTouchUpInside]; 69 | } 70 | return _takePictureButton; 71 | } 72 | 73 | 74 | - (UIButton *)flashModeButton { 75 | if (!_flashModeButton) { 76 | _flashModeButton = [[UIButton alloc] init]; 77 | _flashModeButton.translatesAutoresizingMaskIntoConstraints = NO; 78 | _flashModeButton.titleLabel.font = [UIFont systemFontOfSize:12.0]; 79 | [_flashModeButton setImage:[UIImage buk_bundleImageNamed:@"flash-button"] forState:UIControlStateNormal]; 80 | [_flashModeButton addTarget:self action:@selector(toggleFlashMode:) forControlEvents:UIControlEventTouchUpInside]; 81 | } 82 | return _flashModeButton; 83 | } 84 | 85 | 86 | - (UIButton *)cameraDeviceButton { 87 | if (!_cameraDeviceButton) { 88 | _cameraDeviceButton = [[UIButton alloc] init]; 89 | _cameraDeviceButton.translatesAutoresizingMaskIntoConstraints = NO; 90 | [_cameraDeviceButton setImage:[UIImage buk_bundleImageNamed:@"device-button"] forState:UIControlStateNormal]; 91 | [_cameraDeviceButton addTarget:self action:@selector(toggleCameraDevice:) forControlEvents:UIControlEventTouchUpInside]; 92 | } 93 | return _cameraDeviceButton; 94 | } 95 | 96 | 97 | - (UIButton *)cancelButton { 98 | if (!_cancelButton) { 99 | _cancelButton = [[UIButton alloc] init]; 100 | _cancelButton.translatesAutoresizingMaskIntoConstraints = NO; 101 | [_cancelButton setTitle:BUKImagePickerLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; 102 | [_cancelButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside]; 103 | } 104 | return _cancelButton; 105 | } 106 | 107 | 108 | - (UIButton *)doneButton { 109 | if (!_doneButton) { 110 | _doneButton = [[UIButton alloc] init]; 111 | _doneButton.translatesAutoresizingMaskIntoConstraints = NO; 112 | [_doneButton setTitle:BUKImagePickerLocalizedString(@"Done", nil) forState:UIControlStateNormal]; 113 | [_doneButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 114 | [_doneButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled]; 115 | [_doneButton addTarget:self action:@selector(done:) forControlEvents:UIControlEventTouchUpInside]; 116 | } 117 | return _doneButton; 118 | } 119 | 120 | 121 | - (UILabel *)selectionInfoLabel { 122 | if (!_selectionInfoLabel) { 123 | _selectionInfoLabel = [[UILabel alloc] init]; 124 | _selectionInfoLabel.translatesAutoresizingMaskIntoConstraints = NO; 125 | _selectionInfoLabel.font = [UIFont systemFontOfSize:14.0]; 126 | _selectionInfoLabel.textColor = [UIColor orangeColor]; 127 | _selectionInfoLabel.textAlignment = NSTextAlignmentCenter; 128 | } 129 | return _selectionInfoLabel; 130 | } 131 | 132 | 133 | - (UIView *)topToolbarView { 134 | if (!_topToolbarView) { 135 | _topToolbarView = [[UIView alloc] init]; 136 | _topToolbarView.translatesAutoresizingMaskIntoConstraints = NO; 137 | _topToolbarView.backgroundColor = [UIColor blackColor]; 138 | } 139 | return _topToolbarView; 140 | } 141 | 142 | 143 | - (UIView *)bottomToolbarView { 144 | if (!_bottomToolbarView) { 145 | _bottomToolbarView = [[UIView alloc] init]; 146 | _bottomToolbarView.translatesAutoresizingMaskIntoConstraints = NO; 147 | _bottomToolbarView.backgroundColor = [UIColor blackColor]; 148 | } 149 | return _bottomToolbarView; 150 | } 151 | 152 | 153 | - (UIView *)flashView { 154 | if (!_flashView) { 155 | _flashView = [[UIView alloc] init]; 156 | _flashView.translatesAutoresizingMaskIntoConstraints = NO; 157 | _flashView.backgroundColor = [UIColor colorWithWhite:0.9f alpha:1.0]; 158 | } 159 | return _flashView; 160 | } 161 | 162 | 163 | - (UICollectionView *)collectionView { 164 | if (!_collectionView) { 165 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 166 | flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 167 | flowLayout.itemSize = self.thumbnailSize; 168 | flowLayout.sectionInset = UIEdgeInsetsMake(0, 5.0, 0, 5.0); 169 | flowLayout.minimumInteritemSpacing = 4.0; 170 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout]; 171 | _collectionView.translatesAutoresizingMaskIntoConstraints = NO; 172 | _collectionView.backgroundColor = [UIColor clearColor]; 173 | _collectionView.showsHorizontalScrollIndicator = NO; 174 | _collectionView.showsVerticalScrollIndicator = NO; 175 | _collectionView.alwaysBounceHorizontal = YES; 176 | _collectionView.delegate = self; 177 | _collectionView.dataSource = self; 178 | [_collectionView registerClass:[BUKCameraImageCollectionViewCell class] forCellWithReuseIdentifier:kBUKCameraViewControllerCellIdentifier]; 179 | } 180 | return _collectionView; 181 | } 182 | 183 | 184 | - (FastttCameraFlashMode)flashMode { 185 | return self.fastCamera.cameraFlashMode; 186 | } 187 | 188 | 189 | - (void)setFlashMode:(FastttCameraFlashMode)flashMode { 190 | NSString *title; 191 | switch (flashMode) { 192 | case FastttCameraFlashModeAuto: 193 | title = BUKImagePickerLocalizedString(@"Flash Auto", nil); 194 | break; 195 | case FastttCameraFlashModeOn: 196 | title = BUKImagePickerLocalizedString(@"Flash On", nil); 197 | break; 198 | case FastttCameraFlashModeOff: 199 | title = BUKImagePickerLocalizedString(@"Flash Off", nil); 200 | break; 201 | } 202 | 203 | [self.flashModeButton setTitle:title forState:UIControlStateNormal]; 204 | self.fastCamera.cameraFlashMode = flashMode; 205 | } 206 | 207 | 208 | - (FastttCameraDevice)cameraDevice { 209 | return self.fastCamera.cameraDevice; 210 | } 211 | 212 | 213 | - (void)setCameraDevice:(FastttCameraDevice)cameraDevice { 214 | if ([FastttCamera isCameraDeviceAvailable:cameraDevice]) { 215 | [self.fastCamera setCameraDevice:cameraDevice]; 216 | self.flashModeButton.hidden = ![FastttCamera isFlashAvailableForCameraDevice:cameraDevice]; 217 | } 218 | } 219 | 220 | 221 | - (NSArray *)capturedImages { 222 | NSMutableArray *mutableFullImages = [NSMutableArray arrayWithCapacity:self.mutableCapturedImages.count]; 223 | if (self.usesScaledImage) { 224 | for (FastttCapturedImage *capturedImage in self.mutableCapturedImages) { 225 | [mutableFullImages addObject:capturedImage.scaledImage]; 226 | } 227 | } else { 228 | for (FastttCapturedImage *capturedImage in self.mutableCapturedImages) { 229 | [mutableFullImages addObject:capturedImage.fullImage]; 230 | } 231 | } 232 | return mutableFullImages; 233 | } 234 | 235 | 236 | #pragma mark - NSObject 237 | 238 | - (instancetype)init { 239 | if ((self = [super init])) { 240 | _thumbnailSize = CGSizeMake(72.0, 72.0); 241 | _mutableCapturedImages = [NSMutableArray array]; 242 | _needsConfirmation = NO; 243 | _allowsMultipleSelection = NO; 244 | } 245 | return self; 246 | } 247 | 248 | 249 | #pragma mark - UIViewController 250 | 251 | - (void)viewDidLoad { 252 | [super viewDidLoad]; 253 | 254 | self.view.backgroundColor = [UIColor blackColor]; 255 | 256 | // Camera view 257 | [self fastttAddChildViewController:self.fastCamera]; 258 | self.fastCamera.view.translatesAutoresizingMaskIntoConstraints = NO; 259 | 260 | // Top toolbar 261 | [self.topToolbarView addSubview:self.flashModeButton]; 262 | [self.topToolbarView addSubview:self.cameraDeviceButton]; 263 | [self.topToolbarView addSubview:self.selectionInfoLabel]; 264 | [self.view addSubview:self.topToolbarView]; 265 | 266 | // Bottom toolbar 267 | [self.bottomToolbarView addSubview:self.takePictureButton]; 268 | [self.bottomToolbarView addSubview:self.cancelButton]; 269 | [self.bottomToolbarView addSubview:self.doneButton]; 270 | [self.view addSubview:self.bottomToolbarView]; 271 | 272 | // Collection view 273 | [self.view addSubview:self.collectionView]; 274 | 275 | [self setupViewConstraints]; 276 | 277 | self.flashMode = FastttCameraFlashModeOff; 278 | self.cameraDevice = FastttCameraDeviceRear; 279 | 280 | self.doneButton.hidden = !self.allowsMultipleSelection; 281 | } 282 | 283 | 284 | - (BOOL)prefersStatusBarHidden { 285 | return YES; 286 | } 287 | 288 | 289 | - (void)viewWillAppear:(BOOL)animated { 290 | [super viewWillAppear:animated]; 291 | [self.navigationController setNavigationBarHidden:YES animated:animated]; 292 | [self.navigationController setToolbarHidden:YES animated:animated]; 293 | [self updateDoneButton]; 294 | [self updateSelectionInfo]; 295 | 296 | // Ugly hack 297 | // If pushed, set cancel button title "Back"; 298 | if (self.navigationController.viewControllers.count > 1) { 299 | [self.cancelButton setTitle:BUKImagePickerLocalizedString(@"Back", nil) forState:UIControlStateNormal]; 300 | } 301 | } 302 | 303 | 304 | - (void)viewWillDisappear:(BOOL)animated { 305 | [super viewWillDisappear:animated]; 306 | [self.navigationController setNavigationBarHidden:NO animated:animated]; 307 | } 308 | 309 | 310 | #pragma mark - Actions 311 | 312 | - (void)cancel:(id)sender { 313 | if ([self.delegate respondsToSelector:@selector(cameraViewControllerDidCancel:)]) { 314 | [self.delegate cameraViewControllerDidCancel:self]; 315 | } else { 316 | [self dismissViewControllerAnimated:YES completion:nil]; 317 | } 318 | } 319 | 320 | 321 | - (void)done:(id)sender { 322 | if ([self.delegate respondsToSelector:@selector(cameraViewController:didFinishCapturingImages:)]) { 323 | [self.delegate cameraViewController:self didFinishCapturingImages:self.capturedImages]; 324 | } else { 325 | [self dismissViewControllerAnimated:YES completion:nil]; 326 | } 327 | } 328 | 329 | 330 | - (void)takePicture:(id)sender { 331 | BOOL shouldTakePicture = YES; 332 | if ([self.delegate respondsToSelector:@selector(cameraViewControllerShouldTakePicture:)]) { 333 | shouldTakePicture = [self.delegate cameraViewControllerShouldTakePicture:self]; 334 | } 335 | 336 | if (shouldTakePicture) { 337 | [self.fastCamera takePicture]; 338 | } 339 | } 340 | 341 | 342 | - (void)toggleFlashMode:(id)sender { 343 | FastttCameraFlashMode flashMode; 344 | switch (self.flashMode) { 345 | case FastttCameraFlashModeAuto: 346 | flashMode = FastttCameraFlashModeOn; 347 | break; 348 | case FastttCameraFlashModeOn: 349 | flashMode = FastttCameraFlashModeOff; 350 | break; 351 | case FastttCameraFlashModeOff: 352 | flashMode = FastttCameraFlashModeAuto; 353 | break; 354 | } 355 | 356 | self.flashMode = flashMode; 357 | } 358 | 359 | 360 | - (void)toggleCameraDevice:(id)sender { 361 | FastttCameraDevice cameraDevice; 362 | switch (self.cameraDevice) { 363 | case FastttCameraDeviceFront: 364 | cameraDevice = FastttCameraDeviceRear; 365 | break; 366 | case FastttCameraDeviceRear: 367 | cameraDevice = FastttCameraDeviceFront; 368 | break; 369 | } 370 | 371 | self.cameraDevice = cameraDevice; 372 | } 373 | 374 | 375 | #pragma mark - UICollectionViewDataSource 376 | 377 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 378 | return 1; 379 | } 380 | 381 | 382 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 383 | return self.mutableCapturedImages.count; 384 | } 385 | 386 | 387 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 388 | BUKCameraImageCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kBUKCameraViewControllerCellIdentifier forIndexPath:indexPath]; 389 | 390 | [self configureCell:cell forItemAtIndexPath:indexPath]; 391 | 392 | return cell; 393 | } 394 | 395 | 396 | #pragma mark - UICollectionViewDelegate 397 | 398 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 399 | NSLog(@"[BUKImagePickerController] didSelectItemAtIndexPath"); 400 | } 401 | 402 | 403 | #pragma mark - FastttCameraDelegate 404 | 405 | - (void)cameraController:(id)cameraController didFinishCapturingImage:(FastttCapturedImage *)capturedImage { 406 | if (!self.needsConfirmation) { 407 | [self flashAnimated:YES completion:nil]; 408 | return; 409 | } 410 | 411 | // Confirm 412 | BUKCameraConfirmViewController *confirmViewController = [[BUKCameraConfirmViewController alloc] init]; 413 | confirmViewController.delegate = self; 414 | confirmViewController.capturedImage = capturedImage; 415 | 416 | __weak typeof(self)weakSelf = self; 417 | [self flashAnimated:YES completion:^{ 418 | NSDictionary *views = @{ 419 | @"confirmView": confirmViewController.view, 420 | }; 421 | [weakSelf fastttAddChildViewController:confirmViewController belowSubview:weakSelf.flashView]; 422 | [weakSelf.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[confirmView]|" options:kNilOptions metrics:nil views:views]]; 423 | [weakSelf.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[confirmView]|" options:kNilOptions metrics:nil views:views]]; 424 | }]; 425 | } 426 | 427 | 428 | - (void)cameraController:(id)cameraController didFinishScalingCapturedImage:(FastttCapturedImage *)capturedImage { 429 | if (self.needsConfirmation) { 430 | return; 431 | } 432 | 433 | [self addCapturedImage:capturedImage]; 434 | } 435 | 436 | 437 | - (void)userDeniedCameraPermissionsForCameraController:(id)cameraController { 438 | if ([self.delegate respondsToSelector:@selector(userDeniedCameraPermissionsForCameraViewController:)]) { 439 | [self.delegate userDeniedCameraPermissionsForCameraViewController:self]; 440 | } 441 | 442 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:BUKImagePickerLocalizedString(@"Access Denied", nil) 443 | message:BUKImagePickerLocalizedString(@"You can enable access in \"Settings\" -> \"Privacy\" -> \"Camera\".", nil) 444 | delegate:nil 445 | cancelButtonTitle:BUKImagePickerLocalizedString(@"OK", nil) 446 | otherButtonTitles:nil]; 447 | [alertView show]; 448 | } 449 | 450 | 451 | #pragma mark - BUKImageCollectionViewCellDelegate 452 | 453 | - (void)imageCollectionViewCell:(BUKCameraImageCollectionViewCell *)cell didClickDeleteButton:(UIButton *)button { 454 | NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; 455 | 456 | [self removeImageAtIndex:indexPath.item]; 457 | [self.collectionView deleteItemsAtIndexPaths:@[indexPath]]; 458 | 459 | [self updateDoneButton]; 460 | [self updateSelectionInfo]; 461 | } 462 | 463 | 464 | #pragma mark - BUKCameraConfirmViewControllerDelegate 465 | 466 | - (void)cameraConfirmViewControllerDidCancel:(BUKCameraConfirmViewController *)viewController { 467 | [self fastttRemoveChildViewController:viewController]; 468 | } 469 | 470 | 471 | - (void)cameraConfirmViewControllerDidConfirm:(BUKCameraConfirmViewController *)viewController capturedImage:(FastttCapturedImage *)capturedImage { 472 | [self addCapturedImage:capturedImage]; 473 | [self fastttRemoveChildViewController:viewController]; 474 | } 475 | 476 | 477 | #pragma mark - Private 478 | 479 | - (void)addCapturedImage:(FastttCapturedImage *)capturedImage { 480 | if (!capturedImage || !capturedImage.fullImage || !capturedImage.scaledImage) { 481 | return; 482 | } 483 | 484 | // Since we don't need preview image, release it to save memory. 485 | capturedImage.rotatedPreviewImage = nil; 486 | if (self.usesScaledImage) { 487 | // If the client want to use scaled image, release the full image data. 488 | capturedImage.fullImage = nil; 489 | } 490 | 491 | [self.mutableCapturedImages addObject:capturedImage]; 492 | 493 | if (!self.allowsMultipleSelection) { 494 | [self done:nil]; 495 | } 496 | 497 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(self.mutableCapturedImages.count - 1) inSection:0]; 498 | [self.collectionView insertItemsAtIndexPaths:@[indexPath]]; 499 | [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionRight animated:YES]; 500 | 501 | [self updateDoneButton]; 502 | [self updateSelectionInfo]; 503 | } 504 | 505 | 506 | - (void)updateDoneButton { 507 | if ([self.delegate respondsToSelector:@selector(cameraViewControllerShouldEnableDoneButton:)]) { 508 | self.doneButton.enabled = [self.delegate cameraViewControllerShouldEnableDoneButton:self]; 509 | } else { 510 | self.doneButton.enabled = YES; 511 | } 512 | } 513 | 514 | 515 | - (void)updateSelectionInfo { 516 | BOOL shouldShow = NO; 517 | NSString *text = nil; 518 | if ([self.delegate respondsToSelector:@selector(cameraViewControllerShouldShowSelectionInfo:)]) { 519 | shouldShow = [self.delegate cameraViewControllerShouldShowSelectionInfo:self]; 520 | } 521 | 522 | if ([self.delegate respondsToSelector:@selector(cameraViewControllerSelectionInfo:)]) { 523 | text = [self.delegate cameraViewControllerSelectionInfo:self]; 524 | } 525 | 526 | shouldShow = shouldShow && text != nil; 527 | 528 | if (shouldShow) { 529 | self.selectionInfoLabel.text = text; 530 | } else { 531 | self.selectionInfoLabel.text = nil; 532 | } 533 | } 534 | 535 | 536 | - (void)flashAnimated:(BOOL)animated completion:(void (^)(void))completion { 537 | if (self.flashView.superview) { 538 | return; 539 | } 540 | 541 | self.flashView.alpha = 0; 542 | [self.view addSubview:self.flashView]; 543 | 544 | UIView *cameraView = self.fastCamera.view; 545 | [self.view addConstraints:@[ 546 | [NSLayoutConstraint constraintWithItem:self.flashView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:cameraView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0], 547 | [NSLayoutConstraint constraintWithItem:self.flashView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:cameraView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0], 548 | [NSLayoutConstraint constraintWithItem:self.flashView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cameraView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0], 549 | [NSLayoutConstraint constraintWithItem:self.flashView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:cameraView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0] 550 | ]]; 551 | 552 | __weak typeof(self)weakSelf = self; 553 | void (^change)(void) = ^{ 554 | weakSelf.flashView.alpha = 1.0; 555 | }; 556 | 557 | void (^animationCompletionBlock)(BOOL finished) = ^(BOOL finished) { 558 | if (completion) { 559 | completion(); 560 | } 561 | [weakSelf hideFlashView:animated]; 562 | }; 563 | 564 | if (animated) { 565 | [UIView animateWithDuration:0.15f delay:0 options:UIViewAnimationOptionCurveEaseIn animations:change completion:animationCompletionBlock]; 566 | } else { 567 | change(); 568 | animationCompletionBlock(YES); 569 | } 570 | } 571 | 572 | 573 | - (void)hideFlashView:(BOOL)animated { 574 | if (!self.flashView.superview) { 575 | return; 576 | } 577 | 578 | void (^change)(void) = ^{ 579 | self.flashView.alpha = 0.0f; 580 | }; 581 | 582 | void (^completion)(BOOL finished) = ^(BOOL finished) { 583 | [self.flashView removeFromSuperview]; 584 | }; 585 | 586 | if (animated) { 587 | [UIView animateWithDuration:0.15f delay:0.05f options:UIViewAnimationOptionCurveEaseOut animations:change completion:completion]; 588 | } else { 589 | change(); 590 | completion(YES); 591 | } 592 | } 593 | 594 | 595 | - (void)removeImageAtIndex:(NSUInteger)index { 596 | NSUInteger count = self.mutableCapturedImages.count; 597 | if (index >= count) { 598 | return; 599 | } 600 | 601 | [self.mutableCapturedImages removeObjectAtIndex:index]; 602 | } 603 | 604 | 605 | - (id)objectAtIndexPath:(NSIndexPath *)indexPath { 606 | return [self.mutableCapturedImages objectAtIndex:indexPath.item]; 607 | } 608 | 609 | 610 | - (void)configureCell:(BUKCameraImageCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 611 | FastttCapturedImage *capturedImage = [self objectAtIndexPath:indexPath]; 612 | cell.imageView.image = capturedImage.scaledImage; 613 | cell.delegate = self; 614 | } 615 | 616 | 617 | - (void)setupViewConstraints { 618 | NSDictionary *views = @{ 619 | @"topToolbarView": self.topToolbarView, 620 | @"bottomToolbarView": self.bottomToolbarView, 621 | @"cameraView": self.fastCamera.view, 622 | @"flashModeButton": self.flashModeButton, 623 | @"selectionInfoLabel": self.selectionInfoLabel, 624 | @"cameraDeviceButton": self.cameraDeviceButton, 625 | @"takePictureButton": self.takePictureButton, 626 | @"doneButton": self.doneButton, 627 | @"cancelButton": self.cancelButton, 628 | @"collectionView": self.collectionView, 629 | }; 630 | 631 | NSDictionary *metrics = @{ 632 | @"thumbnailHeight": @(self.thumbnailSize.height + 4.0), 633 | @"margin": @15.0, 634 | }; 635 | 636 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topToolbarView(40.0)][cameraView][bottomToolbarView(100.0)]|" options:kNilOptions metrics:nil views:views]]; 637 | 638 | // Camera view 639 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cameraView]|" options:kNilOptions metrics:nil views:views]]; 640 | 641 | // Top toolbar 642 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[topToolbarView]|" options:kNilOptions metrics:nil views:views]]; 643 | 644 | // Flash mode button and camera device button 645 | [self.topToolbarView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[flashModeButton]-(>=0)-[selectionInfoLabel]-(>=0)-[cameraDeviceButton]-(margin)-|" options:NSLayoutFormatAlignAllCenterY metrics:metrics views:views]]; 646 | [self.topToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.flashModeButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.topToolbarView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 647 | [self.topToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.selectionInfoLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.topToolbarView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 648 | 649 | // Bottom toolbar 650 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[bottomToolbarView]|" options:kNilOptions metrics:nil views:views]]; 651 | 652 | // Take picture button 653 | [self.bottomToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.takePictureButton attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.bottomToolbarView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 654 | [self.bottomToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.takePictureButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.bottomToolbarView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 655 | [self.takePictureButton addConstraint:[NSLayoutConstraint constraintWithItem:self.takePictureButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:66.0]]; 656 | [self.takePictureButton addConstraint:[NSLayoutConstraint constraintWithItem:self.takePictureButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.takePictureButton attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 657 | 658 | // Cancel button 659 | [self.bottomToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.cancelButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.bottomToolbarView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 660 | [self.bottomToolbarView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[cancelButton]" options:kNilOptions metrics:metrics views:views]]; 661 | 662 | // Done button 663 | [self.bottomToolbarView addConstraint:[NSLayoutConstraint constraintWithItem:self.doneButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.bottomToolbarView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 664 | [self.bottomToolbarView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[doneButton]-(margin)-|" options:kNilOptions metrics:metrics views:views]]; 665 | 666 | // Collection view 667 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView]|" options:kNilOptions metrics:nil views:views]]; 668 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[collectionView(thumbnailHeight)]-10.0-[bottomToolbarView]" options:kNilOptions metrics:metrics views:views]]; 669 | } 670 | 671 | @end 672 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCheckmarkView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCheckmarkView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKCheckmarkView : UIView 12 | 13 | @property (nonatomic) UIColor *borderColor UI_APPEARANCE_SELECTOR; 14 | @property (nonatomic) CGFloat borderWidth UI_APPEARANCE_SELECTOR; 15 | @property (nonatomic) UIColor *checkmarkColor UI_APPEARANCE_SELECTOR; 16 | @property (nonatomic) CGFloat checkmarkLineWidth UI_APPEARANCE_SELECTOR; 17 | @property (nonatomic) UIColor *fillColor UI_APPEARANCE_SELECTOR; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKCheckmarkView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKCheckmarkView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKCheckmarkView.h" 10 | 11 | @implementation BUKCheckmarkView 12 | 13 | #pragma mark - Accessors 14 | 15 | - (void)setBorderWidth:(CGFloat)borderWidth { 16 | _borderWidth = borderWidth; 17 | [self setNeedsDisplay]; 18 | } 19 | 20 | 21 | - (void)setBorderColor:(UIColor *)borderColor { 22 | _borderColor = borderColor; 23 | [self setNeedsDisplay]; 24 | } 25 | 26 | 27 | - (void)setFillColor:(UIColor *)fillColor { 28 | _fillColor = fillColor; 29 | [self setNeedsDisplay]; 30 | } 31 | 32 | 33 | - (void)setCheckmarkLineWidth:(CGFloat)checkmarkLineWidth { 34 | _checkmarkLineWidth = checkmarkLineWidth; 35 | [self setNeedsDisplay]; 36 | } 37 | 38 | 39 | - (void)setCheckmarkColor:(UIColor *)checkmarkColor { 40 | _checkmarkColor = checkmarkColor; 41 | [self setNeedsDisplay]; 42 | } 43 | 44 | 45 | #pragma mark - Class Methods 46 | 47 | + (void)initialize { 48 | if (self == [BUKCheckmarkView class]) { 49 | BUKCheckmarkView *appearance = [BUKCheckmarkView appearance]; 50 | [appearance setBorderColor:[UIColor whiteColor]]; 51 | [appearance setBorderWidth:1.0]; 52 | [appearance setCheckmarkColor:[UIColor whiteColor]]; 53 | [appearance setCheckmarkLineWidth:2.0]; 54 | [appearance setFillColor:[UIColor colorWithRed:0.078f green:0.435f blue:0.875f alpha:1.0]]; 55 | } 56 | } 57 | 58 | 59 | #pragma mark - UIView 60 | 61 | - (instancetype)initWithFrame:(CGRect)frame { 62 | if ((self = [super initWithFrame:frame])) { 63 | [self initialize]; 64 | } 65 | return self; 66 | } 67 | 68 | 69 | - (id)initWithCoder:(NSCoder *)aDecoder { 70 | if ((self = [super initWithCoder:aDecoder])) { 71 | [self initialize]; 72 | } 73 | return self; 74 | } 75 | 76 | 77 | - (void)drawRect:(CGRect)rect { 78 | if (self.borderColor && self.borderWidth > 0) { 79 | [self.borderColor setFill]; 80 | [[UIBezierPath bezierPathWithOvalInRect:self.bounds] fill]; 81 | } 82 | 83 | if (self.fillColor) { 84 | [self.fillColor setFill]; 85 | [[UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, self.borderWidth, self.borderWidth)] fill]; 86 | } 87 | 88 | if (self.checkmarkColor && self.checkmarkLineWidth > 0) { 89 | UIBezierPath *checkmarkPath = [UIBezierPath bezierPath]; 90 | checkmarkPath.lineWidth = self.checkmarkLineWidth; 91 | 92 | CGFloat width = CGRectGetWidth(self.bounds); 93 | CGFloat height = CGRectGetHeight(self.bounds); 94 | 95 | [checkmarkPath moveToPoint:CGPointMake(width * (6.0 / 24.0), height * (12.0 / 24.0))]; 96 | [checkmarkPath addLineToPoint:CGPointMake(width * (10.0 / 24.0), height * (16.0 / 24.0))]; 97 | [checkmarkPath addLineToPoint:CGPointMake(width * (18.0 / 24.0), height * (8.0 / 24.0))]; 98 | 99 | [self.checkmarkColor setStroke]; 100 | [checkmarkPath stroke]; 101 | } 102 | } 103 | 104 | 105 | #pragma mark - Private 106 | 107 | - (void)initialize { 108 | self.backgroundColor = [UIColor clearColor]; 109 | self.contentMode = UIViewContentModeRedraw; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKImagePickerController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | /** 12 | The media type of assets. 13 | */ 14 | typedef NS_ENUM(NSUInteger, BUKImagePickerControllerMediaType) { 15 | /** 16 | Both images and videos. 17 | */ 18 | BUKImagePickerControllerMediaTypeAny, 19 | /** 20 | Only images. 21 | */ 22 | BUKImagePickerControllerMediaTypeImage, 23 | /** 24 | Only videos. 25 | */ 26 | BUKImagePickerControllerMediaTypeVideo, 27 | }; 28 | 29 | /** 30 | The source type of image picker. 31 | */ 32 | typedef NS_ENUM(NSUInteger, BUKImagePickerControllerSourceType) { 33 | /** 34 | Selects assets from the photo library. 35 | */ 36 | BUKImagePickerControllerSourceTypeLibrary, 37 | /** 38 | Captures images with a camera. 39 | */ 40 | BUKImagePickerControllerSourceTypeCamera, 41 | /** 42 | Selects images in saved photos album. 43 | */ 44 | BUKImagePickerControllerSourceTypeSavedPhotosAlbum, 45 | }; 46 | 47 | @protocol BUKImagePickerControllerDelegate; 48 | 49 | /** 50 | A controller that allows picking multiple assets from user's photo libray or capturing images with a camera. 51 | */ 52 | @interface BUKImagePickerController : UIViewController 53 | 54 | /// -------------------- 55 | /// @name Picking Assets 56 | /// -------------------- 57 | 58 | /** 59 | The media type of the controller. Default value is `BUKImagePickerControllerMediaTypeImage`. 60 | 61 | @see BUKImagePickerControllerMediaType 62 | */ 63 | @property (nonatomic) BUKImagePickerControllerMediaType mediaType; 64 | 65 | /** 66 | The source type of the controller. Default value is `BUKImagePickerControllerSourceTypeLibrary`. 67 | 68 | @see BUKImagePickerControllerSourceType 69 | */ 70 | @property (nonatomic) BUKImagePickerControllerSourceType sourceType; 71 | 72 | /** 73 | An array of selected assets' URLs. The value is always `nil` if the `sourceType` of the receiver is `BUKImagePickerControllerSourceTypeCamera`. 74 | */ 75 | @property (nonatomic, readonly) NSArray *selectedAssetURLs; 76 | 77 | /** 78 | The receiver's delegate. 79 | 80 | @discussion A `BUKImagePickerControllerDelegate` responds to messages sent by selecting assets or capturing images with the picker. You can use the delegate to handle these events. 81 | */ 82 | @property (nonatomic, weak) id delegate; 83 | 84 | /** 85 | Indicates whether empty albums will be ignored. Default value is `NO`. 86 | 87 | @warning It has no effects when `sourceType` is `BUKImagePickerControllerSourceTypeCamera`. 88 | */ 89 | @property (nonatomic) BOOL excludesEmptyAlbums; 90 | 91 | /** 92 | Indicates whether multiple selection is enabled. Default value is `YES`. 93 | */ 94 | @property (nonatomic) BOOL allowsMultipleSelection; 95 | 96 | /** 97 | Indicates whether a camera cell will be shown on assets collection view. When the cell is tapped, a camera view will be presented, so the user can capture additional images besides original assets. Default value is `NO`. 98 | 99 | @warning It has no effects when `sourceType` is `BUKImagePickerControllerSourceTypeCamera`. 100 | */ 101 | @property (nonatomic) BOOL showsCameraCell; 102 | 103 | /** 104 | Controls the order of assets. Default value is `NO` which means do not reverse assets order. 105 | 106 | @warning It has no effects when `sourceType` is `BUKImagePickerControllerSourceTypeCamera`. 107 | */ 108 | @property (nonatomic) BOOL reversesAssets; 109 | 110 | /** 111 | Indicates whether a text label will be shown when the number of selected assets is changed. Default value is `YES`. 112 | */ 113 | @property (nonatomic) BOOL showsNumberOfSelectedAssets; 114 | 115 | /** 116 | The minimum number of selected assets required. Default value is `1`. 117 | */ 118 | @property (nonatomic) NSUInteger minimumNumberOfSelection; 119 | 120 | /** 121 | The maximum number of selected assets allowed. If the value is `0`, selection is unlimited. Default value is `0`. 122 | 123 | @warning If the value is less than `minimumNumberOfSelection`, it will be marked as invalid and maximum selection is unlimited. 124 | */ 125 | @property (nonatomic) NSUInteger maximumNumberOfSelection; 126 | 127 | /** 128 | The number of columns of the receiver in portrait mode. Default value is `4`. Only works for `BUKImagePickerControllerSourceTypeLibrary` and `BUKImagePickerControllerSourceTypeSavedPhotosAlbum`. 129 | */ 130 | @property (nonatomic) NSUInteger numberOfColumnsInPortrait; 131 | 132 | /** 133 | The number of columns of the receiver in landscape mode. Default value is `7`. Only works for `BUKImagePickerControllerSourceTypeLibrary` and `BUKImagePickerControllerSourceTypeSavedPhotosAlbum`. 134 | */ 135 | @property (nonatomic) NSUInteger numberOfColumnsInLandscape; 136 | 137 | /// ---------------------- 138 | /// @name Capturing Images 139 | /// ---------------------- 140 | 141 | /** 142 | Indicates whether the receiver should save images to user's photo libray after capturing images. Default value is `NO`. 143 | */ 144 | @property (nonatomic) BOOL savesToPhotoLibrary; 145 | 146 | /** 147 | Indicates whether the receiver should waiting for user's confirmation after capturing images. Only works for `BUKImagePickerControllerSourceTypeCamera`. Default value is `NO`. 148 | */ 149 | @property (nonatomic) BOOL needsConfirmation; 150 | 151 | /** 152 | The receiver will scale the captured images to fit within the size of the camera preview if this property is set `YES`. 153 | */ 154 | @property (nonatomic) BOOL usesScaledImage; 155 | 156 | /** 157 | The maximum dimension the receiver uses to scale images. 158 | 159 | @discussion If you'd like to set an explicit max dimension for scaling the image, set it here. This can be useful if you have specific 160 | requirements for uploading the image. 161 | */ 162 | @property (nonatomic) BOOL maxScaledDimension; 163 | 164 | @end 165 | 166 | 167 | @class ALAsset; 168 | 169 | @protocol BUKImagePickerControllerDelegate 170 | 171 | @optional 172 | 173 | /** 174 | Asks the delegate whether the asset should be marked as selected. 175 | 176 | @param imagePickerController The image picker controller. 177 | @param asset The asset that was tapped. 178 | 179 | @return A boolean value indicating whether the asset should be selected. 180 | */ 181 | - (BOOL)buk_imagePickerController:(BUKImagePickerController *)imagePickerController shouldSelectAsset:(ALAsset *)asset; 182 | 183 | /** 184 | Tells the delegate that the user selected an asset. 185 | 186 | @param imagePickerController The image picker controller. 187 | @param asset An asset that was selected. 188 | */ 189 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didSelectAsset:(ALAsset *)asset; 190 | 191 | /** 192 | Tells the delegate that the user deselected an asset. 193 | 194 | @param imagePickerController The image picker controller. 195 | @param asset An asset that was deselected. 196 | */ 197 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didDeselectAsset:(ALAsset *)asset; 198 | 199 | /** 200 | Asks the delegate whether the controller should enable done button. 201 | 202 | @param imagePickerController The image picker controller. 203 | 204 | @return A boolean value indicating whether the image picker should enable done button. 205 | */ 206 | - (BOOL)buk_imagePickerControllerShouldEnableDoneButton:(BUKImagePickerController *)imagePickerController; 207 | 208 | /** 209 | Tells the delegate the user cancelled picking. 210 | 211 | @param imagePickerController The image picker that was cancelled. 212 | */ 213 | - (void)buk_imagePickerControllerDidCancel:(BUKImagePickerController *)imagePickerController; 214 | 215 | /** 216 | Tells the delegate that the user finished picking assets. The message is sent if the source type of the picker is `BUKImagePickerControllerSourceTypeLibrary` and `BUKImagePickerControllerSourceTypeSavedPhotosAlbum`. If you're going to capturing images, use `-buk_imagePickerController:didFinishPickingImages:` instead. 217 | 218 | @param imagePickerController The image picker controller. 219 | @param assets An array of assets which were selected by the user. 220 | 221 | @see -buk_imagePickerController:didFinishPickingImages: 222 | */ 223 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishPickingAssets:(NSArray *)assets; 224 | 225 | /** 226 | Tells the delegate that the user finished capturing images. The message is sent if and only if the source type of the picker is `BUKImagePickerControllerSourceTypeCamera`. 227 | 228 | @param imagePickerController The image picker controller. 229 | @param images An array of `UIImage` objects which were captured by the user. 230 | 231 | @see -buk_imagePickerController:didFinishPickingAssets: 232 | */ 233 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishPickingImages:(NSArray *)images; 234 | 235 | /** 236 | Asks the delegate whether the controller should take picture. 237 | 238 | @param imagePickerController The image picker controller. 239 | @param capturedImages An array of `UIImage` objects which has already been captured. 240 | 241 | @return A boolean value indicating whether the image picker should take picture. 242 | */ 243 | - (BOOL)buk_imagePickerController:(BUKImagePickerController *)imagePickerController shouldTakePictureWithCapturedImages:(NSArray *)capturedImages; 244 | 245 | /** 246 | Tells the delegate that the access is denied. 247 | 248 | @param imagePickerController The image picker controller who attemped to open user's photo libray or use the camera. 249 | */ 250 | - (void)buk_imagePickerControllerAccessDenied:(BUKImagePickerController *)imagePickerController; 251 | 252 | /** 253 | Tells the delegate that the image picker controller will save images to the user's photo library. 254 | 255 | @param imagePickerController The image picker who's going to save images. 256 | @param images An array of `UIImage` objects which will be saved. 257 | */ 258 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController willSaveImages:(NSArray *)images; 259 | 260 | /** 261 | Tells the delegate that the image picker controller is saving images to the user's photo library. 262 | 263 | @param imagePickerController The image picker who is saving images. 264 | @param images An array of `UIImage` objects which will be saved. 265 | @param currentCount The current count of images that were saved. 266 | @param totalCount The total number of images that will be saved. 267 | */ 268 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController saveImages:(NSArray *)images withProgress:(NSUInteger)currentCount totalCount:(NSUInteger)totalCount; 269 | 270 | /** 271 | Tells the delegate that the image picker finished saving images. 272 | 273 | @param imagePickerController The image picker who finished saving images. 274 | @param images An array of `UIImage` objects saved. 275 | @param urls An array of `NSURL` objects of `ALAsset`s corresponding to images. 276 | */ 277 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishSavingImages:(NSArray *)images resultAssetURLs:(NSArray *)urls; 278 | 279 | @end 280 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKImagePickerController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/8/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | #import 11 | #import "BUKImagePickerController.h" 12 | #import "BUKAssetsViewController.h" 13 | #import "BUKAlbumsViewController.h" 14 | #import "BUKCameraViewController.h" 15 | #import "BUKAccessDeniedPlaceholderView.h" 16 | #import "BUKAssetsManager.h" 17 | #import "NSBundle+BUKImagePickerController.h" 18 | 19 | @interface BUKImagePickerController () 20 | 21 | @property (nonatomic) NSMutableOrderedSet *mutableSelectedAssetURLs; 22 | @property (nonatomic) BUKAssetsManager *assetsManager; 23 | @property (nonatomic) UINavigationController *childNavigationController; 24 | @property (nonatomic) UIView *accessDeniedPlaceholderView; 25 | @property (nonatomic, weak) BUKAssetsViewController *assetsViewController; 26 | @property (nonatomic, weak) BUKAlbumsViewController *albumsViewController; 27 | 28 | @end 29 | 30 | @implementation BUKImagePickerController 31 | 32 | #pragma mark - Accessors 33 | 34 | - (void)setMinimumNumberOfSelection:(NSUInteger)minimumNumberOfSelection { 35 | _minimumNumberOfSelection = MAX(minimumNumberOfSelection, 1); 36 | } 37 | 38 | 39 | - (NSArray *)selectedAssetURLs { 40 | return [self.mutableSelectedAssetURLs array]; 41 | } 42 | 43 | 44 | - (BUKAssetsManager *)assetsManager { 45 | if (!_assetsManager) { 46 | _assetsManager = [[BUKAssetsManager alloc] initWithAssetsLibrary:[[ALAssetsLibrary alloc] init] 47 | mediaTyle:self.mediaType 48 | groupTypes:(ALAssetsGroupSavedPhotos | ALAssetsGroupPhotoStream | ALAssetsGroupAlbum)]; 49 | _assetsManager.excludesEmptyGroups = self.excludesEmptyAlbums; 50 | } 51 | return _assetsManager; 52 | } 53 | 54 | 55 | - (UIView *)accessDeniedPlaceholderView { 56 | if (!_accessDeniedPlaceholderView) { 57 | _accessDeniedPlaceholderView = [[BUKAccessDeniedPlaceholderView alloc] init]; 58 | _accessDeniedPlaceholderView.translatesAutoresizingMaskIntoConstraints = NO; 59 | } 60 | return _accessDeniedPlaceholderView; 61 | } 62 | 63 | 64 | #pragma mark - NSObject 65 | 66 | - (void)dealloc { 67 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 68 | } 69 | 70 | 71 | - (instancetype)init { 72 | if ((self = [super init])) { 73 | _mutableSelectedAssetURLs = [NSMutableOrderedSet orderedSet]; 74 | _mediaType = BUKImagePickerControllerMediaTypeImage; 75 | _sourceType = BUKImagePickerControllerSourceTypeLibrary; 76 | _allowsMultipleSelection = YES; 77 | _excludesEmptyAlbums = NO; 78 | _showsCameraCell = NO; 79 | _savesToPhotoLibrary = NO; 80 | _needsConfirmation = NO; 81 | _reversesAssets = NO; 82 | _maxScaledDimension = 0; 83 | _usesScaledImage = NO; 84 | _showsNumberOfSelectedAssets = YES; 85 | _numberOfColumnsInPortrait = 4; 86 | _numberOfColumnsInLandscape = 7; 87 | _minimumNumberOfSelection = 1; 88 | _maximumNumberOfSelection = 0; 89 | } 90 | return self; 91 | } 92 | 93 | 94 | #pragma mark - UIViewController 95 | 96 | - (void)viewDidLoad { 97 | [super viewDidLoad]; 98 | 99 | // Check authorization status 100 | if ((self.sourceType == BUKImagePickerControllerSourceTypeSavedPhotosAlbum || self.sourceType == BUKImagePickerControllerSourceTypeLibrary) && [BUKAssetsManager isAccessDenied]) { 101 | [self showAccessDeniedView]; 102 | return; 103 | } 104 | 105 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(assetsAccessDenied:) name:kBUKImagePickerAccessDeniedNotificationName object:nil]; 106 | 107 | // Setup view controllers 108 | UIViewController *viewController; 109 | switch (self.sourceType) { 110 | case BUKImagePickerControllerSourceTypeSavedPhotosAlbum: { 111 | self.childNavigationController = [[UINavigationController alloc] init]; 112 | BUKAlbumsViewController *albumsViewController = [self createAlbumsViewController]; 113 | BUKAssetsViewController *assetsViewController = [self createAssetsViewController]; 114 | self.albumsViewController = albumsViewController; 115 | self.assetsViewController = assetsViewController; 116 | [self.childNavigationController setViewControllers:@[albumsViewController, assetsViewController]]; 117 | viewController = self.childNavigationController; 118 | 119 | [self.assetsManager fetchAssetsGroupsWithCompletion:^(NSArray *assetsGroups) { 120 | if (assetsGroups.count > 0) { 121 | assetsViewController.assetsGroup = [assetsGroups firstObject]; 122 | } 123 | } failureBlock:nil]; 124 | 125 | break; 126 | } 127 | case BUKImagePickerControllerSourceTypeLibrary: { 128 | BUKAlbumsViewController *albumsViewController = [self createAlbumsViewController]; 129 | self.albumsViewController = albumsViewController; 130 | self.childNavigationController = [[UINavigationController alloc] initWithRootViewController:albumsViewController]; 131 | viewController = self.childNavigationController; 132 | break; 133 | } 134 | case BUKImagePickerControllerSourceTypeCamera: { 135 | viewController = [self createCameraViewController]; 136 | break; 137 | } 138 | } 139 | 140 | if (viewController) { 141 | [self fastttAddChildViewController:viewController]; 142 | } 143 | } 144 | 145 | 146 | - (BOOL)prefersStatusBarHidden { 147 | if (self.sourceType == BUKImagePickerControllerSourceTypeCamera) { 148 | return YES; 149 | } 150 | 151 | return [self.childNavigationController.topViewController prefersStatusBarHidden]; 152 | } 153 | 154 | 155 | #pragma mark - BUKAlbumsViewControllerDelegate 156 | 157 | - (void)albumsViewController:(BUKAlbumsViewController *)viewController didSelectAssetsGroup:(ALAssetsGroup *)assetsGroup { 158 | BUKAssetsViewController *assetsViewController = [self createAssetsViewController]; 159 | assetsViewController.assetsGroup = assetsGroup; 160 | self.assetsViewController = assetsViewController; 161 | [self.childNavigationController pushViewController:assetsViewController animated:YES]; 162 | } 163 | 164 | 165 | - (void)albumsViewControllerDidCancel:(BUKAlbumsViewController *)viewController { 166 | [self cancelPicking]; 167 | } 168 | 169 | 170 | - (void)albumsViewControllerDidFinishPicking:(BUKAlbumsViewController *)viewController { 171 | [self finishPickingAssets]; 172 | } 173 | 174 | 175 | - (BOOL)albumsViewControllerShouldEnableDoneButton:(BUKAlbumsViewController *)viewController { 176 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerShouldEnableDoneButton:)]) { 177 | return [self.delegate buk_imagePickerControllerShouldEnableDoneButton:self]; 178 | } 179 | 180 | return [self isTotalNumberOfSelectedAssetsValid]; 181 | } 182 | 183 | 184 | - (BOOL)albumsViewControllerShouldShowSelectionInfo:(BUKAlbumsViewController *)viewController { 185 | if (!self.showsNumberOfSelectedAssets) { 186 | return NO; 187 | } 188 | 189 | return self.mutableSelectedAssetURLs.count > 0; 190 | } 191 | 192 | 193 | - (NSString *)albumsViewControllerSelectionInfo:(BUKAlbumsViewController *)viewController { 194 | return [self selectionInfoWithNumberOfSelection:self.mutableSelectedAssetURLs.count]; 195 | } 196 | 197 | 198 | #pragma mark - BUKAssetsViewControllerDelegate 199 | 200 | - (BOOL)assetsViewController:(BUKAssetsViewController *)assetsViewController shouldSelectAsset:(ALAsset *)asset { 201 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:shouldSelectAsset:)]) { 202 | return [self.delegate buk_imagePickerController:self shouldSelectAsset:asset]; 203 | } 204 | 205 | return !(self.minimumNumberOfSelection <= self.maximumNumberOfSelection && self.selectedAssetURLs.count >= self.maximumNumberOfSelection); 206 | } 207 | 208 | 209 | - (void)assetsViewController:(BUKAssetsViewController *)assetsViewController didSelectAsset:(ALAsset *)asset { 210 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 211 | [self.mutableSelectedAssetURLs addObject:assetURL]; 212 | 213 | if (!self.allowsMultipleSelection) { 214 | [self finishPickingAssets]; 215 | } 216 | } 217 | 218 | 219 | - (void)assetsViewController:(BUKAssetsViewController *)assetsViewController didDeselectAsset:(ALAsset *)asset { 220 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 221 | [self.mutableSelectedAssetURLs removeObject:assetURL]; 222 | } 223 | 224 | 225 | - (BOOL)assetsViewController:(BUKAssetsViewController *)assetsViewController isAssetSelected:(ALAsset *)asset { 226 | NSURL *assetURL = [asset valueForProperty:ALAssetPropertyAssetURL]; 227 | return [self.selectedAssetURLs containsObject:assetURL]; 228 | } 229 | 230 | 231 | - (void)assetsViewControllerDidFinishPicking:(BUKAssetsViewController *)assetsViewController { 232 | [self finishPickingAssets]; 233 | } 234 | 235 | 236 | - (void)assetsViewControllerDidSelectCamera:(BUKAssetsViewController *)assetsViewController { 237 | self.savesToPhotoLibrary = YES; 238 | BUKCameraViewController *cameraViewController = [self createCameraViewController]; 239 | [self.childNavigationController pushViewController:cameraViewController animated:YES]; 240 | } 241 | 242 | 243 | - (BOOL)assetsViewControllerShouldEnableDoneButton:(BUKAssetsViewController *)assetsViewController { 244 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerShouldEnableDoneButton:)]) { 245 | return [self.delegate buk_imagePickerControllerShouldEnableDoneButton:self]; 246 | } 247 | 248 | return [self isTotalNumberOfSelectedAssetsValid]; 249 | } 250 | 251 | 252 | - (BOOL)assetsViewControllerShouldShowSelectionInfo:(BUKAssetsViewController *)assetsViewController { 253 | if (!self.showsNumberOfSelectedAssets) { 254 | return NO; 255 | } 256 | 257 | return self.mutableSelectedAssetURLs.count > 0; 258 | } 259 | 260 | 261 | - (NSString *)assetsViewControllerSelectionInfo:(BUKAssetsViewController *)assetsViewController { 262 | return [self selectionInfoWithNumberOfSelection:self.mutableSelectedAssetURLs.count]; 263 | } 264 | 265 | 266 | #pragma mark - BUKCameraViewControllerDelegate 267 | 268 | - (void)userDeniedCameraPermissionsForCameraViewController:(BUKCameraViewController *)cameraViewController { 269 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerAccessDenied:)]) { 270 | [self.delegate buk_imagePickerControllerAccessDenied:self]; 271 | } 272 | } 273 | 274 | 275 | - (void)cameraViewControllerDidCancel:(BUKCameraViewController *)cameraViewController { 276 | if (self.sourceType == BUKImagePickerControllerSourceTypeCamera) { 277 | [self cancelPicking]; 278 | } else { 279 | [self.childNavigationController popViewControllerAnimated:YES]; 280 | 281 | // Save photos to albums if needed 282 | NSArray *images = cameraViewController.capturedImages; 283 | if (images.count == 0) { 284 | return; 285 | } 286 | 287 | if (self.savesToPhotoLibrary) { 288 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:willSaveImages:)]) { 289 | [self.delegate buk_imagePickerController:self willSaveImages:images]; 290 | } 291 | 292 | self.assetsViewController.ignoreChange = YES; 293 | 294 | [self.assetsManager writeImagesToSavedPhotosAlbum:images progress:^(NSURL *assetURL, NSUInteger currentCount, NSUInteger totalCount) { 295 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:saveImages:withProgress:totalCount:)]) { 296 | [self.delegate buk_imagePickerController:self saveImages:images withProgress:currentCount totalCount:totalCount]; 297 | } 298 | } completion:^(NSArray *assetURLs, NSError *error) { 299 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:didFinishSavingImages:resultAssetURLs:)]) { 300 | [self.delegate buk_imagePickerController:self didFinishSavingImages:images resultAssetURLs:assetURLs]; 301 | } 302 | 303 | [self.assetsViewController updateAssets]; 304 | self.assetsViewController.ignoreChange = NO; 305 | 306 | [self.mutableSelectedAssetURLs addObjectsFromArray:assetURLs]; 307 | }]; 308 | } 309 | } 310 | } 311 | 312 | 313 | - (void)cameraViewController:(BUKCameraViewController *)cameraViewController didFinishCapturingImages:(NSArray *)images { 314 | if (self.savesToPhotoLibrary) { 315 | if (images.count == 0) { 316 | [self finishPickingAssets]; 317 | return; 318 | } 319 | 320 | // Start saving 321 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:willSaveImages:)]) { 322 | [self.delegate buk_imagePickerController:self willSaveImages:images]; 323 | } 324 | self.assetsViewController.ignoreChange = YES; 325 | 326 | [self.assetsManager writeImagesToSavedPhotosAlbum:images progress:^(NSURL *assetURL, NSUInteger currentCount, NSUInteger totalCount) { 327 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:saveImages:withProgress:totalCount:)]) { 328 | [self.delegate buk_imagePickerController:self saveImages:images withProgress:currentCount totalCount:totalCount]; 329 | } 330 | } completion:^(NSArray *assetURLs, NSError *error) { 331 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:didFinishSavingImages:resultAssetURLs:)]) { 332 | [self.delegate buk_imagePickerController:self didFinishSavingImages:images resultAssetURLs:assetURLs]; 333 | } 334 | [self.mutableSelectedAssetURLs addObjectsFromArray:assetURLs]; 335 | [self finishPickingAssets]; 336 | }]; 337 | } else { 338 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:didFinishPickingImages:)]) { 339 | [self.delegate buk_imagePickerController:self didFinishPickingImages:images]; 340 | } else { 341 | [self dismissViewControllerAnimated:YES completion:nil]; 342 | } 343 | } 344 | } 345 | 346 | 347 | - (BOOL)cameraViewControllerShouldTakePicture:(BUKCameraViewController *)cameraViewController { 348 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:shouldTakePictureWithCapturedImages:)]) { 349 | return [self.delegate buk_imagePickerController:self shouldTakePictureWithCapturedImages:cameraViewController.capturedImages]; 350 | } 351 | 352 | NSUInteger numberOfCapturedImages = cameraViewController.capturedImages.count; 353 | BOOL result = self.minimumNumberOfSelection > self.maximumNumberOfSelection; 354 | 355 | return result || (self.selectedAssetURLs.count + numberOfCapturedImages) < self.maximumNumberOfSelection; 356 | } 357 | 358 | 359 | - (BOOL)cameraViewControllerShouldEnableDoneButton:(BUKCameraViewController *)cameraViewController { 360 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerShouldEnableDoneButton:)]) { 361 | return [self.delegate buk_imagePickerControllerShouldEnableDoneButton:self]; 362 | } 363 | 364 | NSUInteger numberOfCapturedImages = cameraViewController.capturedImages.count; 365 | NSUInteger numberOfSelection = self.selectedAssetURLs.count; 366 | 367 | return [self isNumberOfSelectionValid:(numberOfSelection + numberOfCapturedImages)]; 368 | } 369 | 370 | 371 | - (BOOL)cameraViewControllerShouldShowSelectionInfo:(BUKCameraViewController *)cameraViewController { 372 | if (!self.showsNumberOfSelectedAssets) { 373 | return NO; 374 | } 375 | 376 | NSUInteger numberOfCapturedImages = cameraViewController.capturedImages.count; 377 | NSUInteger numberOfSelection = self.selectedAssetURLs.count; 378 | return (numberOfCapturedImages + numberOfSelection) > 0; 379 | } 380 | 381 | 382 | - (NSString *)cameraViewControllerSelectionInfo:(BUKCameraViewController *)cameraViewController { 383 | return [self selectionInfoWithNumberOfSelection:(cameraViewController.capturedImages.count + self.mutableSelectedAssetURLs.count)]; 384 | } 385 | 386 | 387 | #pragma mark - Private 388 | 389 | - (void)assetsAccessDenied:(NSNotification *)notification { 390 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerAccessDenied:)]) { 391 | [self.delegate buk_imagePickerControllerAccessDenied:self]; 392 | } 393 | 394 | [self showAccessDeniedView]; 395 | } 396 | 397 | 398 | - (void)showAccessDeniedView { 399 | if (self.accessDeniedPlaceholderView.superview) { 400 | return; 401 | } 402 | 403 | UIViewController *viewController = [[UIViewController alloc] init]; 404 | viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BUKImagePickerLocalizedString(@"Cancel", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancelPicking)]; 405 | 406 | UIView *view = viewController.view; 407 | [view addSubview:self.accessDeniedPlaceholderView]; 408 | 409 | // Add constraints 410 | [view addConstraint:[NSLayoutConstraint constraintWithItem:self.accessDeniedPlaceholderView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeLeft multiplier:1 constant:0]]; 411 | [view addConstraint:[NSLayoutConstraint constraintWithItem:self.accessDeniedPlaceholderView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; 412 | [view addConstraint:[NSLayoutConstraint constraintWithItem:self.accessDeniedPlaceholderView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeRight multiplier:1 constant:0]]; 413 | [view addConstraint:[NSLayoutConstraint constraintWithItem:self.accessDeniedPlaceholderView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]]; 414 | 415 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 416 | [self fastttAddChildViewController:navigationController]; 417 | } 418 | 419 | 420 | - (BUKAlbumsViewController *)createAlbumsViewController { 421 | BUKAlbumsViewController *albumsViewController = [[BUKAlbumsViewController alloc] init]; 422 | albumsViewController.delegate = self; 423 | albumsViewController.assetsManager = self.assetsManager; 424 | albumsViewController.allowsMultipleSelection = self.allowsMultipleSelection; 425 | return albumsViewController; 426 | } 427 | 428 | 429 | - (BUKAssetsViewController *)createAssetsViewController { 430 | BUKAssetsViewController *assetsViewController = [[BUKAssetsViewController alloc] init]; 431 | assetsViewController.delegate = self; 432 | assetsViewController.allowsMultipleSelection = self.allowsMultipleSelection; 433 | assetsViewController.reversesAssets = self.reversesAssets; 434 | assetsViewController.showsCameraCell = self.showsCameraCell; 435 | assetsViewController.minimumInteritemSpacing = 2.0; 436 | assetsViewController.minimumLineSpacing = 4.0; 437 | assetsViewController.numberOfColumnsInPortrait = self.numberOfColumnsInPortrait; 438 | assetsViewController.numberOfColumnsInLandscape = self.numberOfColumnsInLandscape; 439 | return assetsViewController; 440 | } 441 | 442 | 443 | - (BUKCameraViewController *)createCameraViewController { 444 | BUKCameraViewController *cameraViewController = [[BUKCameraViewController alloc] init]; 445 | cameraViewController.delegate = self; 446 | cameraViewController.allowsMultipleSelection = self.allowsMultipleSelection; 447 | cameraViewController.needsConfirmation = self.needsConfirmation; 448 | cameraViewController.usesScaledImage = self.usesScaledImage; 449 | cameraViewController.maxScaledDimension = self.maxScaledDimension; 450 | return cameraViewController; 451 | } 452 | 453 | 454 | - (NSString *)selectionInfoWithNumberOfSelection:(NSInteger)count { 455 | NSString *text = nil; 456 | if (self.maximumNumberOfSelection > 0) { 457 | text = [NSString stringWithFormat:BUKImagePickerLocalizedString(@"Selected: %@/%@", nil), @(count), @(self.maximumNumberOfSelection)]; 458 | } else { 459 | text = [NSString stringWithFormat:BUKImagePickerLocalizedString(@"Selected: %@", nil), @(count)]; 460 | } 461 | return text; 462 | } 463 | 464 | 465 | - (BOOL)isNumberOfSelectionValid:(NSUInteger)numberOfSelection { 466 | BOOL result = (numberOfSelection >= self.minimumNumberOfSelection); 467 | 468 | if (self.minimumNumberOfSelection <= self.maximumNumberOfSelection) { 469 | result = result && numberOfSelection <= self.maximumNumberOfSelection; 470 | } 471 | 472 | return result; 473 | } 474 | 475 | 476 | - (BOOL)isTotalNumberOfSelectedAssetsValid { 477 | NSUInteger numberOfSelection = self.selectedAssetURLs.count; 478 | return [self isNumberOfSelectionValid:numberOfSelection]; 479 | } 480 | 481 | 482 | - (void)cancelPicking { 483 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerControllerDidCancel:)]) { 484 | [self.delegate buk_imagePickerControllerDidCancel:self]; 485 | } else { 486 | [self dismissViewControllerAnimated:YES completion:nil]; 487 | } 488 | } 489 | 490 | 491 | - (void)finishPickingAssets { 492 | [self.assetsManager fetchAssetsWithAssetURLs:self.selectedAssetURLs progress:nil completion:^(NSArray *assets, NSError *error) { 493 | if ([self.delegate respondsToSelector:@selector(buk_imagePickerController:didFinishPickingAssets:)]) { 494 | [self.delegate buk_imagePickerController:self didFinishPickingAssets:assets]; 495 | } else { 496 | [self dismissViewControllerAnimated:YES completion:nil]; 497 | } 498 | }]; 499 | } 500 | 501 | @end 502 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKNoAssetsPlaceholderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKNoAssetsPlaceholderView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKPlaceholderView.h" 10 | 11 | @interface BUKNoAssetsPlaceholderView : BUKPlaceholderView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKNoAssetsPlaceholderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKNoAssetsPlaceholderView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKNoAssetsPlaceholderView.h" 10 | #import "NSBundle+BUKImagePickerController.h" 11 | 12 | @implementation BUKNoAssetsPlaceholderView 13 | 14 | #pragma mark - UIView 15 | 16 | - (instancetype)initWithFrame:(CGRect)frame { 17 | if ((self = [super initWithFrame:frame])) { 18 | self.titleLabel.text = BUKImagePickerLocalizedString(@"No Photos or Videos", nil); 19 | 20 | NSString *messageFormat = nil; 21 | if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { 22 | messageFormat = BUKImagePickerLocalizedString(@"You can take photos and videos using the camera, or sync photos and videos onto your %@\nusing iTunes.", nil); 23 | } else { 24 | messageFormat = BUKImagePickerLocalizedString(@"You can sync photos and videos onto your %@ using iTunes.", nil); 25 | } 26 | self.messageLabel.text = [NSString stringWithFormat:messageFormat, [[UIDevice currentDevice] model]]; 27 | } 28 | return self; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKPlaceholderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKPlaceholderView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKPlaceholderView : UIView 12 | 13 | @property (nonatomic, readonly) UILabel *titleLabel; 14 | @property (nonatomic, readonly) UILabel *messageLabel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKPlaceholderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKPlaceholderView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKPlaceholderView.h" 10 | #import "UIColor+BUKImagePickerController.h" 11 | 12 | @implementation BUKPlaceholderView 13 | 14 | #pragma mark - Accessors 15 | 16 | @synthesize titleLabel = _titleLabel; 17 | @synthesize messageLabel = _messageLabel; 18 | 19 | - (UILabel *)titleLabel { 20 | if (!_titleLabel) { 21 | _titleLabel = [[UILabel alloc] init]; 22 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 23 | _titleLabel.numberOfLines = 5; 24 | _titleLabel.textColor = [UIColor buk_lightTextColor]; 25 | _titleLabel.textAlignment = NSTextAlignmentCenter; 26 | UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; 27 | _titleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:(fontDescriptor.pointSize * 1.6f)]; 28 | } 29 | return _titleLabel; 30 | } 31 | 32 | 33 | - (UILabel *)messageLabel { 34 | if (!_messageLabel) { 35 | _messageLabel = [[UILabel alloc] init]; 36 | _messageLabel.translatesAutoresizingMaskIntoConstraints = NO; 37 | _messageLabel.numberOfLines = 5; 38 | _messageLabel.textColor = [UIColor buk_lightTextColor]; 39 | _messageLabel.textAlignment = NSTextAlignmentCenter; 40 | _messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; 41 | } 42 | return _messageLabel; 43 | } 44 | 45 | 46 | #pragma mark - UIView 47 | 48 | - (instancetype)initWithFrame:(CGRect)frame { 49 | if ((self = [super initWithFrame:frame])) { 50 | [self initialize]; 51 | } 52 | return self; 53 | } 54 | 55 | 56 | - (id)initWithCoder:(NSCoder *)aDecoder { 57 | if ((self = [super initWithCoder:aDecoder])) { 58 | [self initialize]; 59 | } 60 | return self; 61 | } 62 | 63 | 64 | #pragma mark - Private 65 | 66 | - (void)initialize { 67 | self.backgroundColor = [UIColor whiteColor]; 68 | [self addSubview:self.titleLabel]; 69 | [self addSubview:self.messageLabel]; 70 | [self setupViewConstraints]; 71 | } 72 | 73 | 74 | - (void)setupViewConstraints { 75 | NSDictionary *views = @{ 76 | @"titleLabel": self.titleLabel, 77 | @"messageLabel": self.messageLabel 78 | }; 79 | 80 | NSDictionary *metrics = @{ 81 | @"margin": @(15.0) 82 | }; 83 | 84 | // Title label 85 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:(-5.0)]]; 86 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[titleLabel]-(margin)-|" options:kNilOptions metrics:metrics views:views]]; 87 | 88 | // Message label 89 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:5.0]]; 90 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[messageLabel]-(margin)-|" options:kNilOptions metrics:metrics views:views]]; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKVideoIconView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKVideoIconView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKVideoIconView : UIView 12 | 13 | @property (nonatomic) UIColor *iconColor UI_APPEARANCE_SELECTOR; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKVideoIconView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKVideoIconView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKVideoIconView.h" 10 | 11 | @implementation BUKVideoIconView 12 | 13 | + (void)initialize { 14 | if (self == [BUKVideoIconView class]) { 15 | BUKVideoIconView *appearance = [BUKVideoIconView appearance]; 16 | appearance.iconColor = [UIColor whiteColor]; 17 | } 18 | } 19 | 20 | 21 | #pragma mark - UIView 22 | 23 | - (id)initWithCoder:(NSCoder *)aDecoder { 24 | if ((self = [super initWithCoder:aDecoder])) { 25 | [self setup]; 26 | } 27 | return self; 28 | } 29 | 30 | 31 | - (instancetype)initWithFrame:(CGRect)frame { 32 | if ((self = [super initWithFrame:frame])) { 33 | [self setup]; 34 | } 35 | return self; 36 | } 37 | 38 | 39 | - (void)drawRect:(CGRect)rect { 40 | if (!self.iconColor) { 41 | return; 42 | } 43 | 44 | [self.iconColor setFill]; 45 | 46 | // Draw triangle 47 | UIBezierPath *trianglePath = [UIBezierPath bezierPath]; 48 | [trianglePath moveToPoint:CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMinY(self.bounds))]; 49 | [trianglePath addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds))]; 50 | [trianglePath addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds) - CGRectGetMidY(self.bounds), CGRectGetMidY(self.bounds))]; 51 | [trianglePath closePath]; 52 | [trianglePath fill]; 53 | 54 | // Draw rounded square 55 | UIBezierPath *squarePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(CGRectGetMinX(self.bounds), CGRectGetMinY(self.bounds), CGRectGetWidth(self.bounds) - CGRectGetMidY(self.bounds) - 1.0, CGRectGetHeight(self.bounds)) cornerRadius:2.0]; 56 | [squarePath fill]; 57 | } 58 | 59 | 60 | #pragma mark - Private 61 | 62 | - (void)setup { 63 | self.contentMode = UIViewContentModeRedraw; 64 | self.backgroundColor = [UIColor clearColor]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKVideoIndicatorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKVideoIndicatorView.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @class BUKVideoIconView; 12 | 13 | @interface BUKVideoIndicatorView : UIView 14 | 15 | @property (nonatomic, readonly) BUKVideoIconView *videoIconView; 16 | @property (nonatomic, readonly) UILabel *timeLabel; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/BUKVideoIndicatorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKVideoIndicatorView.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKVideoIndicatorView.h" 10 | #import "BUKVideoIconView.h" 11 | 12 | @implementation BUKVideoIndicatorView 13 | 14 | #pragma mark - Accessors 15 | 16 | @synthesize videoIconView = _videoIconView; 17 | @synthesize timeLabel = _timeLabel; 18 | 19 | - (BUKVideoIconView *)videoIconView { 20 | if (!_videoIconView) { 21 | _videoIconView = [[BUKVideoIconView alloc] init]; 22 | _videoIconView.translatesAutoresizingMaskIntoConstraints = NO; 23 | } 24 | return _videoIconView; 25 | } 26 | 27 | 28 | - (UILabel *)timeLabel { 29 | if (!_timeLabel) { 30 | _timeLabel = [[UILabel alloc] init]; 31 | _timeLabel.translatesAutoresizingMaskIntoConstraints = NO; 32 | _timeLabel.font = [UIFont systemFontOfSize:12.0]; 33 | _timeLabel.textColor = [UIColor whiteColor]; 34 | _timeLabel.textAlignment = NSTextAlignmentRight; 35 | _timeLabel.lineBreakMode = NSLineBreakByTruncatingTail; 36 | } 37 | return _timeLabel; 38 | } 39 | 40 | 41 | #pragma mark - UIView 42 | 43 | - (id)initWithCoder:(NSCoder *)aDecoder { 44 | if ((self = [super initWithCoder:aDecoder])) { 45 | [self initialize]; 46 | } 47 | return self; 48 | } 49 | 50 | 51 | - (instancetype)initWithFrame:(CGRect)frame { 52 | if ((self = [super initWithFrame:frame])) { 53 | [self initialize]; 54 | } 55 | return self; 56 | } 57 | 58 | 59 | #pragma mark - Private 60 | 61 | - (void)initialize { 62 | self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4f]; 63 | [self addSubview:self.videoIconView]; 64 | [self addSubview:self.timeLabel]; 65 | 66 | [self setupViewConstraints]; 67 | } 68 | 69 | 70 | - (void)setupViewConstraints { 71 | NSDictionary *views = @{ 72 | @"videoIconView": self.videoIconView, 73 | @"timeLabel": self.timeLabel, 74 | }; 75 | 76 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5.0-[videoIconView(14.0)]-4.0-[timeLabel]-5.0-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; 77 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.videoIconView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 78 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.videoIconView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.videoIconView attribute:NSLayoutAttributeWidth multiplier:(8.0 / 14.0) constant:0]]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/NSBundle+BUKImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+BUKImagePickerController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/4/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | #define BUKImagePickerLocalizedString(key, comment) NSLocalizedStringFromTableInBundle((key), @"BUKImagePicker", [NSBundle buk_imagePickerBundle], (comment)) 12 | 13 | @interface NSBundle (BUKImagePickerController) 14 | 15 | + (NSBundle *)buk_imagePickerBundle; 16 | + (NSURL *)buk_imagePickerBundleURL; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/NSBundle+BUKImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+BUKImagePickerController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/4/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+BUKImagePickerController.h" 10 | #import "BUKImagePickerController.h" 11 | 12 | @implementation NSBundle (BUKImagePickerController) 13 | 14 | + (NSBundle *)buk_imagePickerBundle { 15 | return [self bundleWithURL:[self buk_imagePickerBundleURL]]; 16 | } 17 | 18 | 19 | + (NSURL *)buk_imagePickerBundleURL { 20 | NSBundle *bundle = [NSBundle bundleForClass:[BUKImagePickerController class]]; 21 | return [bundle URLForResource:@"BUKImagePickerController" withExtension:@"bundle"]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/UIColor+BUKImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+BUKImagePickerController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface UIColor (BUKImagePickerController) 12 | 13 | + (UIColor *)buk_textColor; 14 | + (UIColor *)buk_lightTextColor; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/UIColor+BUKImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+BUKImagePickerController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 8/12/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "UIColor+BUKImagePickerController.h" 10 | 11 | @implementation UIColor (BUKImagePickerController) 12 | 13 | + (UIColor *)buk_textColor { 14 | return [UIColor colorWithRed:129.0f/255.0f green:136.0f/255.0f blue:148.0f/255.0f alpha:1]; 15 | } 16 | 17 | 18 | + (UIColor *)buk_lightTextColor { 19 | return [UIColor colorWithRed:153.0f/255.0f green:153.0f/255.0f blue:153.0f/255.0f alpha:1]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/UIImage+BUKImagePickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+BUKImagePickerController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface UIImage (BUKImagePickerController) 12 | 13 | + (UIImage *)buk_bundleImageNamed:(NSString *)name; 14 | + (UIImage *)buk_imageNamed:(NSString *)name inBundle:(NSBundle *)bundle; 15 | + (UIImage *)buk_albumPlaceholderImageWithSize:(CGSize)size; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BUKImagePickerController/Classes/UIImage+BUKImagePickerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+BUKImagePickerController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 7/9/15. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "UIImage+BUKImagePickerController.h" 10 | #import "NSBundle+BUKImagePickerController.h" 11 | 12 | @implementation UIImage (BUKImagePickerController) 13 | 14 | + (UIImage *)buk_albumPlaceholderImageWithSize:(CGSize)size { 15 | UIGraphicsBeginImageContext(size); 16 | CGContextRef context = UIGraphicsGetCurrentContext(); 17 | 18 | UIColor *backgroundColor = [UIColor colorWithRed:0.937f green:0.937f blue:0.957f alpha:1.0]; 19 | UIColor *iconColor = [UIColor colorWithRed:0.702f green:0.702f blue:0.714f alpha:1.0]; 20 | 21 | // Background 22 | CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); 23 | CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height)); 24 | 25 | // Icon (back) 26 | CGRect backIconRect = CGRectMake(size.width * (16.0 / 68.0), 27 | size.height * (20.0 / 68.0), 28 | size.width * (32.0 / 68.0), 29 | size.height * (24.0 / 68.0)); 30 | 31 | CGContextSetFillColorWithColor(context, [iconColor CGColor]); 32 | CGContextFillRect(context, backIconRect); 33 | 34 | CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); 35 | CGContextFillRect(context, CGRectInset(backIconRect, 1.0, 1.0)); 36 | 37 | // Icon (front) 38 | CGRect frontIconRect = CGRectMake(size.width * (20.0 / 68.0), 39 | size.height * (24.0 / 68.0), 40 | size.width * (32.0 / 68.0), 41 | size.height * (24.0 / 68.0)); 42 | 43 | CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); 44 | CGContextFillRect(context, CGRectInset(frontIconRect, -1.0, -1.0)); 45 | 46 | CGContextSetFillColorWithColor(context, [iconColor CGColor]); 47 | CGContextFillRect(context, frontIconRect); 48 | 49 | CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); 50 | CGContextFillRect(context, CGRectInset(frontIconRect, 1.0, 1.0)); 51 | 52 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 53 | UIGraphicsEndImageContext(); 54 | 55 | return image; 56 | } 57 | 58 | 59 | + (UIImage *)buk_bundleImageNamed:(NSString *)name { 60 | return [self buk_imageNamed:name inBundle:[NSBundle buk_imagePickerBundle]]; 61 | } 62 | 63 | 64 | + (UIImage *)buk_imageNamed:(NSString *)name inBundle:(NSBundle *)bundle { 65 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 66 | return [self imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil]; 67 | #elif __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_8_0 68 | return [self imageWithContentsOfFile:[bundle pathForResource:name ofType:@"png"]]; 69 | #else 70 | if ([self respondsToSelector:@selector(imageNamed:inBundle:compatibleWithTraitCollection:)]) { 71 | return [self imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil]; 72 | } else { 73 | return [self imageWithContentsOfFile:[bundle pathForResource:name ofType:@"png"]]; 74 | } 75 | #endif 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController.xcodeproj/xcshareddata/xcschemes/BUKImagePickerController-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Classes/BUKAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAppDelegate.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKAppDelegate : UIResponder 12 | 13 | @property (nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Classes/BUKAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKAppDelegate.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKAppDelegate.h" 10 | #import "BUKViewController.h" 11 | 12 | @implementation BUKAppDelegate 13 | 14 | #pragma mark - Accessors 15 | 16 | @synthesize window = _window; 17 | 18 | - (UIWindow *)window { 19 | if (!_window) { 20 | _window = [[UIWindow alloc] init]; 21 | _window.frame = [[UIScreen mainScreen] bounds]; 22 | _window.backgroundColor = [UIColor whiteColor]; 23 | } 24 | return _window; 25 | } 26 | 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 29 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:[[BUKViewController alloc] init]]; 30 | self.window.rootViewController = navigationController; 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Classes/BUKViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BUKViewController.h 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface BUKViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Classes/BUKViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKViewController.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | #import "BUKViewController.h" 10 | 11 | @import BUKImagePickerController; 12 | 13 | static NSString *const kBUKViewControllerCellIdentifier = @"cell"; 14 | 15 | @interface BUKViewController () 16 | 17 | @property (nonatomic) NSArray *optionInfos; 18 | 19 | @end 20 | 21 | 22 | @implementation BUKViewController 23 | 24 | #pragma mark - Accessors 25 | 26 | @synthesize optionInfos = _optionInfos; 27 | 28 | - (NSArray *)optionInfos { 29 | if (!_optionInfos) { 30 | _optionInfos = @[ 31 | @{@"text": @"Camera - single selection", 32 | @"selectorName": NSStringFromSelector(@selector(showSingleSelectionCamera:)), 33 | }, 34 | @{@"text": @"Camera - multiple selection", 35 | @"selectorName": NSStringFromSelector(@selector(showMultipleSelectionCamera:)), 36 | }, 37 | @{@"text": @"Library - single selection", 38 | @"selectorName": NSStringFromSelector(@selector(showSingleSelectionLibrary:)), 39 | }, 40 | @{@"text": @"Library - multiple selection", 41 | @"selectorName": NSStringFromSelector(@selector(showMultipleSelectionLibrary:)), 42 | }, 43 | @{@"text": @"Library - 6 photos at most ", 44 | @"selectorName": NSStringFromSelector(@selector(showMaximumSelectionLibrary:)), 45 | }, 46 | @{@"text": @"Saved Photos Album - single selection", 47 | @"selectorName": NSStringFromSelector(@selector(showSingleSelectionAlbum:)), 48 | }, 49 | @{@"text": @"Saved Photos Album - multiple Selection", 50 | @"selectorName": NSStringFromSelector(@selector(showMultipleSelectionAlbum:)), 51 | }, 52 | @{@"text": @"Saved Photos Album - 3 photos at least", 53 | @"selectorName": NSStringFromSelector(@selector(showMinimumSelectionAlbum:)), 54 | }, 55 | @{@"text": @"Saved Photos Album - with camera cell", 56 | @"selectorName": NSStringFromSelector(@selector(showAlbumWithCameraCell:)), 57 | }, 58 | ]; 59 | } 60 | return _optionInfos; 61 | } 62 | 63 | 64 | #pragma mark - UIViewController 65 | 66 | - (void)viewDidLoad { 67 | [super viewDidLoad]; 68 | 69 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kBUKViewControllerCellIdentifier]; 70 | self.tableView.rowHeight = 40.0; 71 | 72 | self.title = @"BUKImagePickerController"; 73 | } 74 | 75 | 76 | #pragma mark - Actions 77 | 78 | - (void)showSingleSelectionCamera:(id)sender { 79 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 80 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeCamera; 81 | imagePickerController.allowsMultipleSelection = NO; 82 | imagePickerController.needsConfirmation = YES; 83 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 84 | } 85 | 86 | 87 | - (void)showMultipleSelectionCamera:(id)sender { 88 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 89 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeCamera; 90 | imagePickerController.allowsMultipleSelection = YES; 91 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 92 | } 93 | 94 | 95 | - (void)showSingleSelectionLibrary:(id)sender { 96 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 97 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeLibrary; 98 | imagePickerController.allowsMultipleSelection = NO; 99 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 100 | } 101 | 102 | 103 | - (void)showMultipleSelectionLibrary:(id)sender { 104 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 105 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeLibrary; 106 | imagePickerController.mediaType = BUKImagePickerControllerMediaTypeAny; 107 | imagePickerController.excludesEmptyAlbums = YES; 108 | imagePickerController.allowsMultipleSelection = YES; 109 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 110 | } 111 | 112 | 113 | - (void)showMaximumSelectionLibrary:(id)sender { 114 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 115 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeLibrary; 116 | imagePickerController.allowsMultipleSelection = YES; 117 | imagePickerController.maximumNumberOfSelection = 6; 118 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 119 | } 120 | 121 | 122 | - (void)showSingleSelectionAlbum:(id)sender { 123 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 124 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeSavedPhotosAlbum; 125 | imagePickerController.allowsMultipleSelection = NO; 126 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 127 | } 128 | 129 | 130 | - (void)showMultipleSelectionAlbum:(id)sender { 131 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 132 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeSavedPhotosAlbum; 133 | imagePickerController.allowsMultipleSelection = YES; 134 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 135 | } 136 | 137 | 138 | - (void)showMinimumSelectionAlbum:(id)sender { 139 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 140 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeSavedPhotosAlbum; 141 | imagePickerController.allowsMultipleSelection = YES; 142 | imagePickerController.minimumNumberOfSelection = 3; 143 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 144 | } 145 | 146 | 147 | - (void)showAlbumWithCameraCell:(id)sender { 148 | BUKImagePickerController *imagePickerController = [self imagePickerController]; 149 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeSavedPhotosAlbum; 150 | imagePickerController.allowsMultipleSelection = YES; 151 | imagePickerController.showsCameraCell = YES; 152 | imagePickerController.reversesAssets = YES; 153 | imagePickerController.usesScaledImage = YES; 154 | [self.navigationController presentViewController:imagePickerController animated:YES completion:nil]; 155 | } 156 | 157 | 158 | #pragma mark - UITableViewDataSource 159 | 160 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 161 | return 1; 162 | } 163 | 164 | 165 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 166 | return self.optionInfos.count; 167 | } 168 | 169 | 170 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 171 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kBUKViewControllerCellIdentifier forIndexPath:indexPath]; 172 | [self configureCell:cell forRowAtIndexPath:indexPath]; 173 | 174 | return cell; 175 | } 176 | 177 | 178 | #pragma mark - UITableViewDelegate 179 | 180 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 181 | NSString *selectorName = self.optionInfos[indexPath.row][@"selectorName"]; 182 | SEL selector = NSSelectorFromString(selectorName); 183 | if ([self respondsToSelector:selector]) { 184 | [self performSelector:selector withObject:nil]; 185 | } 186 | } 187 | 188 | 189 | #pragma mark - Private 190 | 191 | - (BUKImagePickerController *)imagePickerController { 192 | BUKImagePickerController *imagePickerController = [[BUKImagePickerController alloc] init]; 193 | imagePickerController.mediaType = BUKImagePickerControllerMediaTypeImage; 194 | imagePickerController.delegate = self; 195 | return imagePickerController; 196 | } 197 | 198 | 199 | - (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 200 | cell.textLabel.text = self.optionInfos[indexPath.row][@"text"]; 201 | } 202 | 203 | 204 | #pragma mark - BUKImagePickerControllerDelegate 205 | 206 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didSelectAsset:(ALAsset *)asset { 207 | 208 | } 209 | 210 | 211 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didDeselectAsset:(ALAsset *)asset { 212 | 213 | } 214 | 215 | 216 | - (void)buk_imagePickerControllerDidCancel:(BUKImagePickerController *)imagePickerController { 217 | [imagePickerController dismissViewControllerAnimated:YES completion:nil]; 218 | } 219 | 220 | 221 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController saveImages:(NSArray *)images withProgress:(NSUInteger)currentCount totalCount:(NSUInteger)totalCount { 222 | NSLog(@"Save images...progress: %ld/%ld", currentCount, totalCount); 223 | } 224 | 225 | 226 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishPickingAssets:(NSArray *)assets { 227 | NSLog(@"didFinishPickingAssets: %@", assets); 228 | [imagePickerController dismissViewControllerAnimated:YES completion:nil]; 229 | } 230 | 231 | 232 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishPickingImages:(NSArray *)images { 233 | NSLog(@"didFinishPickingImages: %@", images); 234 | [imagePickerController dismissViewControllerAnimated:YES completion:nil]; 235 | } 236 | 237 | @end 238 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Other Sources/BUKImagePickerController-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | @import Darwin.Availability; 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Other Sources/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BUKImagePickerController 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "BUKAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([BUKAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/BUKImagePickerController-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Launch Screen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/Images.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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "60x60", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "29x29", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "40x40", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "76x76", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | "NSCameraUsageDescription" = "Take Photos"; 4 | "NSPhotoLibraryUsageDescription" = "Select Photos"; 5 | -------------------------------------------------------------------------------- /Example/BUKImagePickerController/Resources/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | "NSCameraUsageDescription" = "拍照片"; 4 | "NSPhotoLibraryUsageDescription" = "选照片"; 5 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'BUKImagePickerController-Example' do 4 | pod "BUKImagePickerController", :path => "../" 5 | 6 | target 'Tests' do 7 | inherit! :search_paths 8 | 9 | pod 'Specta' 10 | pod 'Expecta' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - BUKImagePickerController (0.1.11): 3 | - FastttCamera (~> 0.3) 4 | - Expecta (1.0.5) 5 | - FastttCamera (0.3.4): 6 | - FastttCamera/Default (= 0.3.4) 7 | - FastttCamera/Default (0.3.4) 8 | - Specta (1.0.5) 9 | 10 | DEPENDENCIES: 11 | - BUKImagePickerController (from `../`) 12 | - Expecta 13 | - Specta 14 | 15 | EXTERNAL SOURCES: 16 | BUKImagePickerController: 17 | :path: "../" 18 | 19 | SPEC CHECKSUMS: 20 | BUKImagePickerController: cef01a8794d016b51ed36a2ee34f8b6a170f09d1 21 | Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe 22 | FastttCamera: 51f16712cbd5bdb08bc6231b957325c6be5e478d 23 | Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 24 | 25 | PODFILE CHECKSUM: dd66b5ec687df2fdc6c0c62bf558976668b56b40 26 | 27 | COCOAPODS: 1.1.1 28 | -------------------------------------------------------------------------------- /Example/Tests/Tests-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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BUKImagePickerControllerTests.m 3 | // BUKImagePickerControllerTests 4 | // 5 | // Created by Yiming Tang on 07/08/2015. 6 | // Copyright (c) 2015 Yiming Tang. All rights reserved. 7 | // 8 | 9 | // https://github.com/Specta/Specta 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | SpecBegin(InitialSpecs) 16 | 17 | describe(@"these will pass", ^{ 18 | 19 | it(@"can do maths", ^{ 20 | expect(1).beLessThan(23); 21 | }); 22 | 23 | it(@"can read", ^{ 24 | expect(@"team").toNot.contain(@"I"); 25 | }); 26 | 27 | it(@"will wait and succeed", ^{ 28 | waitUntil(^(DoneCallback done) { 29 | done(); 30 | }); 31 | }); 32 | }); 33 | 34 | SpecEnd 35 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Tests/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Yiming Tang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BUKImagePickerController 2 | 3 | [![CI Status](http://img.shields.io/travis/iException/BUKImagePickerController.svg?style=flat)](https://travis-ci.org/iException/BUKImagePickerController) 4 | [![Version](https://img.shields.io/cocoapods/v/BUKImagePickerController.svg?style=flat)](http://cocoapods.org/pods/BUKImagePickerController) 5 | [![License](https://img.shields.io/cocoapods/l/BUKImagePickerController.svg?style=flat)](http://cocoapods.org/pods/BUKImagePickerController) 6 | [![Platform](https://img.shields.io/cocoapods/p/BUKImagePickerController.svg?style=flat)](http://cocoapods.org/pods/BUKImagePickerController) 7 | 8 | BUKImagePickerController is a view controller that allows the user picking multiple assets from the photo libray or capturing images with a camera. It looks like the system built-in component UIImagePickerController at first glance . However, BUKImagePickerController is more powerful. Run the example project to take a tour. 9 | 10 | ![screenshot](screenshot.png) 11 | 12 | ## Usage 13 | 14 | ### Basic 15 | 16 | It's super easy to use BUKImagePickerController. The normal approach is: 17 | 18 | 1. Implement `BUKImagePickerControllerDelegate` methods. 19 | 2. Simply create a `BUKImagePickerController` instance. 20 | 3. Set its delegate and configure it according to you actual needs. 21 | 4. Show the image picker controller. 22 | 23 | ``` obj-c 24 | 25 | BUKImagePickerController *imagePickerController = [[BUKImagePickerController alloc] init]; 26 | imagePickerController.mediaType = BUKImagePickerControllerMediaTypeImage; 27 | imagePickerController.sourceType = BUKImagePickerControllerSourceTypeLibrary; 28 | imagePickerController.delegate = self; 29 | imagePickerController.allowsMultipleSelection = YES; 30 | [self presentViewController:imagePickerController animated:YES completion:nil]; 31 | 32 | ``` 33 | 34 | In your delegate: 35 | 36 | ``` obj-c 37 | 38 | - (void)buk_imagePickerController:(BUKImagePickerController *)imagePickerController didFinishPickingAssets:(NSArray *)assets { 39 | // Process assets 40 | 41 | [imagePickerController dismissViewControllerAnimated:YES completion:nil]; 42 | } 43 | 44 | ``` 45 | 46 | 47 | ### Advanced 48 | 49 | Run the example project and see the [full documentation](BUKImagePickerController/Classes/BUKImagePickerController.h) for more details. 50 | 51 | 52 | ## Requirements 53 | 54 | * iOS 7.0 and later 55 | * ARC 56 | 57 | 58 | ## Installation 59 | 60 | BUKImagePickerController is available through [CocoaPods](http://cocoapods.org). To install 61 | it, simply add the following line to your Podfile: 62 | 63 | ``` ruby 64 | pod "BUKImagePickerController" 65 | ``` 66 | 67 | 68 | ## Credits 69 | 70 | Inspired by [QBImagePicker](https://github.com/questbeat/QBImagePicker) 71 | 72 | 73 | ## Author 74 | 75 | Yiming Tang, [@yiming_t](https://twitter.com/yiming_t) 76 | 77 | 78 | ## License 79 | 80 | BUKImagePickerController is available under the MIT license. See the LICENSE file for more info. 81 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iException/BUKImagePickerController/1e772ef499db615d4f87769d8df63ec67062fcb3/screenshot.png --------------------------------------------------------------------------------