ImagePickerController(圖片選擇器)是iOS開發(fā)中一個很常用的UI控件。作為攝影愛好者婆廊,而Google出品的攝影后期App,Snapseed中那個圖片選擇器,好看又實用锡足,便嘗試著仿寫了一個忱屑。集成后一行代碼即可實現(xiàn)下圖效果蹬敲。
實現(xiàn)原理從界面說起。
界面可以分解為一個背景圖層用于點擊關(guān)閉(空白處點擊消失)莺戒,一個橫向可滾動的collectionview用于顯示圖庫里的圖片伴嗡,一個tableview用于存放功能按鈕。
1. 功能按鈕的tableview
先從簡單的tableview說起从铲,從小模塊到大模塊應該是以下方面瘪校。
- 自定義數(shù)組數(shù)據(jù)對象item方便調(diào)整按鈕的文字圖片數(shù)量功能
- 自定義tableview的cell方便樣式調(diào)整,cell中屬性根據(jù)對應item設置
- 自定義tableview的datasourece在之中設置cell和item的對應關(guān)系
- tableview的deleagte名段,設置點擊事件
- 點擊功能實現(xiàn)阱扬,設置UIImagePickerController
- tableview的view初始化,設置datasource伸辟,view繪制
自定義item對象麻惶,item對象定義了action的顯示名字,類型信夫,圖片
@property(nonatomic, copy) NSString *actionTitle;
@property(nonatomic, strong) UIImage *actionImage;
@property(nonatomic, assign) CDZImagePickerActionType actionType;
其中CDZImagePickerActionType可用來定義按鈕動作類型窃蹋,可以用枚舉來定義
typedef NS_ENUM(NSInteger, CDZImagePickerActionType) {
CDZImagePickerCameraAction,
CDZImagePickerLibraryAction,
CDZImagePickerRecentAction,
CDZImagePickerCloseAction
};
定義一個init方法用來快捷初始化item對象
- (instancetype)initWithTitle:(NSString *)titele withActionType:(CDZImagePickerActionType)type withImage:(UIImage *)image{
self = [super init];
if (self) {
_actionTitle = titele;
_actionType = type;
_actionImage = image;
}
return self;
}
自定義cell對象卡啰,封裝一個方法來對應item和cell的關(guān)系
- (void)setCellFromItem:(CDZImagePickerActionsItem *)item{
self.textLabel.text = item.actionTitle;
self.imageView.image = item.actionImage;
}
可以重寫layoutSubviews方法達到cell中文字,圖片樣式自定義的效果警没,比如可以寫有圖片和無圖片的樣式布局
- (void)layoutSubviews{
[super layoutSubviews];
self.selectionStyle = UITableViewCellSelectionStyleNone;//點擊不變色
self.imageView.frame = CGRectMake(20, 15, 22, 22);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
self.textLabel.font = [UIFont systemFontOfSize:16.0f];
self.textLabel.textColor = [UIColor colorWithRed:0.30 green:0.30 blue:0.30 alpha:1.00];
if (self.imageView.image){
self.textLabel.frame = CGRectMake(60, 2, 200, 48);
self.textLabel.textAlignment = NSTextAlignmentLeft;
}
else{
self.textLabel.textAlignment = NSTextAlignmentCenter;
}
}
自定義datasource匈辱,這一步可以根據(jù)個人選擇,個人習慣是把datasource從viewcontroller里抽離杀迹,可以讓controller更少代碼,datasource有數(shù)組對象用于存放item
@property (nonatomic, strong) NSMutableArray *itemArray;
實現(xiàn)datasource方法
#pragma mark - tableViewDataSourceRequried
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.itemArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CDZImagePickerActionsItem *item = self.itemArray[indexPath.row];
CDZImagePickerActionsCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CDZImagePickerActionsCell class])];
if (!cell) {
cell = [[CDZImagePickerActionsCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass([CDZImagePickerActionsCell class])];
}
[cell setCellFromItem:item];//將cell和item對應起來
return cell;
}
實現(xiàn)delegate和點擊事件
#pragma mark - tableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return actionsViewCellHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
CDZImagePickerActionsItem *item = self.actionArray[indexPath.row];
[self doActionsWithType:item.actionType];
}
#pragma mark - actions
- (void)doActionsWithType:(CDZImagePickerActionType)type{
switch (type) {
case CDZImagePickerCameraAction:
[self openCamera];
break;
case CDZImagePickerRecentAction:
[self openRecentImage];
break;
case CDZImagePickerLibraryAction:
[self openLibrary];
break;
case CDZImagePickerCloseAction:
[self closeAction];
break;
}
}
- (void)openCamera{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
UIImagePickerController *pickerController = [[UIImagePickerController alloc]init];
pickerController.delegate = self;
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:pickerController animated:NO completion:nil];
NSLog(@"打開相機");
}
}
- (void)openLibrary{
UIImagePickerController *pickerController = [[UIImagePickerController alloc]init];
pickerController.delegate = self;
pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:pickerController animated:NO completion:nil];
NSLog(@"打開圖庫");
}
- (void)closeAction{
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(@"關(guān)閉按鈕");
}
- (void)openRecentImage{
[[PHImageManager defaultManager]requestImageForAsset:self.photosDataSource.itemArray[0] targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
self.resultImage = result;
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(@"打開最新圖片");
}];
}
實現(xiàn)imagepicker的delegate(記得實現(xiàn)UINavigationControllerDelegate亡脸,不可只實現(xiàn)UIImagePickerControllerDelegate,會報錯)
#pragma mark - imagePickerController delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info{
UIImage *image = info[UIImagePickerControllerOriginalImage];
if(picker.sourceType == UIImagePickerControllerSourceTypeCamera){
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
self.resultImage = image;
[picker dismissViewControllerAnimated:NO completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(@"從相機或圖庫獲取圖片");
}
iOS10還要檢查一下相機和圖庫的權(quán)限树酪,記得在info.plist中添加兩行浅碾,不然會崩潰
最后在是tableview初始化與繪制,在datasource初始化自己需要的按鈕item數(shù)組(acctionArray)
- (CDZImagePickerActionsDataSource *)actionsDataSource{
if (!_actionsDataSource) {
_actionsDataSource = [[CDZImagePickerActionsDataSource alloc]init];
_actionsDataSource.itemArray = self.actionArray;
}
return _actionsDataSource;
}
- (UITableView *)actionView{
if (!_actionView) {
CGFloat actionsViewHeight = actionsViewCellHeight * self.actionArray.count;
_actionView = [[UITableView alloc]initWithFrame:CGRectMake(0,SCREEN_HEIGHT - actionsViewHeight ,SCREEN_WIDTH, actionsViewHeight) style:UITableViewStylePlain];
_actionView.scrollEnabled = NO; //不需要滑動
_actionView.separatorStyle = UITableViewCellSeparatorStyleNone; //分割線去除
_actionView.delegate = self;
_actionView.dataSource = self.actionsDataSource;
}
return _actionView;
}
2.展示照片的collecionview
collecionview的實現(xiàn)思路和tableview類似
- 自定義collectionview的cell實現(xiàn)樣式調(diào)整
- 自定義Datasource解析并設置當前的cell對應的圖片
- 用Photokit的方法抓取圖庫的照片
- 實現(xiàn)collectionviewflowlayout的delegate實時調(diào)整cell的大小嗅回,邊距
- 實現(xiàn)collecitonview的delegate及穗,設置點擊事件
- 實現(xiàn)collectionview的初始化和視圖繪制
cell的自定義主要是重寫其initWithFrame方法添加imageview(注意collectionviewcell只用initWithFrame方式初始化,tableviewcell初始化方法有多種)绵载,并在layoutSubviews里改變imageview的大小,和tableview類似寫一個setCellFromItem的方法解析item數(shù)據(jù)埂陆。而Photokit獲取的圖片對象為phasset,用PHImageMange的方法根據(jù)將其解析為size為cell的大小uimage娃豹,加快加載速度焚虱,按需加載(關(guān)于Photokit的優(yōu)點不再贅述,iOS8以后蘋果只保留了Photokit用于獲取系統(tǒng)圖片)
photokit獲取時懂版,注意兩個地方鹃栽,targetSize傳入的是pixel,而不是平時用的size躯畴,且option里的resizeMode默認為None(不縮放)民鼓,不重寫resizeMode屬性那么只會抓取不縮放的圖,在意質(zhì)量可以設置resizeMode為Exact(精確)蓬抄,追求速度可以設置為Fast(這樣不會完全按照設置的targetSize去獲取圖片)
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
[self.contentView addSubview:self.photoImageView];
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
self.photoImageView.frame = self.contentView.bounds;
}
- (void)setCellFromItem:(PHAsset *)asset{
PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
options.resizeMode = PHImageRequestOptionsResizeModeFast;
[[PHImageManager defaultManager]requestImageForAsset:asset targetSize:[UIScreen mainScreen].bounds.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
self.photoImageView.image = result;
}];
}
- (UIImageView *)photoImageView{
if (!_photoImageView) {
_photoImageView = [[UIImageView alloc]init];
_photoImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _photoImageView;
}
datasource也類似丰嘉,定義一個itemArray用于存放圖片對象phasset
@property (nonatomic, strong) NSMutableArray *itemArray;
實現(xiàn)datasource方法,和tableview類似(不用判斷cell是nil是因為collectionviewcell的初始化只有一種)
#pragma mark - collectionViewDataSourceRequried
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return self.itemArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
PHAsset *item = self.itemArray[indexPath.row];
CDZImagePickerPhotosCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([CDZImagePickerPhotosCell class]) forIndexPath:indexPath];
[cell setCellFromItem:item];
return cell;
}
在controller中獲取全部照片
再用photokit的方法把所有圖片獲取到數(shù)組里
- (NSMutableArray *)getImageAssets{
self.imageAssetsResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
NSMutableArray *assets = [NSMutableArray new];
for (PHAsset *asset in self.imageAssetsResult){
[assets insertObject:asset atIndex:0];
}
return assets;
}
collectionview的布局嚷缭,cell的大小排列和間距等饮亏,都是由collectionviewlayout決定的,布局是線性的話阅爽,推薦用官方的子類collectionviewflowlayout路幸,其deleagate是collectionviewdeleagateflowlayout,是collectionviewdelegate的子delegate付翁。
- (UICollectionViewFlowLayout *)photosFlowLayout{
if (!_photosFlowLayout) {
_photosFlowLayout = [UICollectionViewFlowLayout new];
_photosFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; //水平滾動
}
return _photosFlowLayout;
}
#pragma mark - collectionViewDelegateFlowLayout
//根據(jù)asset的長寬設定每個cell的size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
PHAsset *asset = self.photosDataSource.itemArray[indexPath.row];
CGFloat height = photosViewHeight - 2 * photosViewInset;
CGFloat aspectRatio = asset.pixelWidth / (CGFloat)asset.pixelHeight;
CGFloat width = height * aspectRatio;
CGSize size = CGSizeMake(width, height);
return size;
}
//設置整個collectionview上下左右間距
- (UIEdgeInsets) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(photosViewInset, photosViewInSet, photosViewInset, photosViewInset);
}
//設置cell之間的間距
- (CGFloat) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
return photosViewInset;
}
設置collecionview的delegate和點擊事件
#pragma mark - collectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
[[PHImageManager defaultManager]requestImageForAsset:self.photosArray[indexPath.row] targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
self.resultImage = result;
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(@"已選擇圖片");
}];
}
最后就是collectionview的初始化和繪制和tableview類似简肴,多了一步注冊collectionview的cell(collectionviewcell必須讓collecionview注冊重用標識,而tableviewcell可以在自己init方法里注冊)
- (UICollectionView *)photosView{
if (!_photosView){
CGFloat actionsViewHeight = actionsViewCellHeight * self.actionArray.count;
_photosView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, SCREEN_HEIGHT - actionsViewHeight - photosViewHeight , SCREEN_WIDTH, photosViewHeight) collectionViewLayout:self.photosFlowLayout];
_photosView.delegate = self;
_photosView.dataSource = self.photosDataSource;
_photosView.backgroundColor = [UIColor whiteColor];
_photosView.showsHorizontalScrollIndicator = NO;
[_photosView registerClass:[CDZImagePickerPhotosCell class] forCellWithReuseIdentifier:NSStringFromClass([CDZImagePickerPhotosCell class])];
}
return _photosView;
}
- (CDZImagePickerPhotosDataSource *)photosDataSource{
if (!_photosDataSource){
_photosDataSource = [[CDZImagePickerPhotosDataSource alloc]init];
_photosDataSource.itemArray = [self getImageAssets];
}
return _photosDataSource;
}
3.透明圖層(用于實現(xiàn)空白處點擊消失)
比較簡單百侧,增加一個tap手勢即可砰识,直接上代碼(記得實現(xiàn)UIGestureRecognizerDelegate)
- (UIView *)backgroundView{
if (!_backgroundView){
CGFloat actionsViewHeight = actionsViewCellHeight * self.actionArray.count;
_backgroundView =[[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - photosViewHeight - actionsViewHeight)];
_backgroundView.backgroundColor.opaque = YES;//設置為透明
_backgroundView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(dissPicker:)];
[_backgroundView addGestureRecognizer:tap];
}
return _backgroundView;
}
4.收尾&封裝
定義一個block用于回調(diào)傳照片
typedef void (^CDZImageResultBlock) (UIImage *image);
一個內(nèi)部block屬性
@property (nonatomic ,copy) CDZImageResultBlock block;
封裝方法
- (void)openPickerInController:(UIViewController *)controller withImageBlock:(CDZImageResultBlock)imageBlock{
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;//iOS8上默認presentviewcontroller不透明杂伟,需設置style
self.block = imageBlock;
[controller presentViewController:self animated:YES completion:nil];
}
在Dealloc方法里調(diào)用block回調(diào)
- (void)dealloc{
self.block(self.resultImage);
NSLog(@"ImagePicker已銷毀");
}
5.使用方法
將CDZImagePicker文件夾拖入項目
CDZImagePickerViewController *imagePickerController = [[CDZImagePickerViewController alloc]init];
[imagePickerController openPickerInController:self withImageBlock:^(UIImage *image) {
if (image) { //如果沒選照片會回調(diào)nil,若想讓之前照片不變就加上判斷
//yourcode
}
// yourcode
}];
也可以自定義CDZActionsItem自定義文字和圖片
imagePickerController.actionArray = [NSMutableArray arrayWithObjects:
[[CDZImagePickerActionsItem alloc]initWithTitle:@"打開設備上的圖片" withActionType:CDZImagePickerLibraryAction withImage:[UIImage imageNamed:@"phone-icon.png"]],
[[CDZImagePickerActionsItem alloc]initWithTitle:@"相機" withActionType:CDZImagePickerCameraAction withImage:[UIImage imageNamed:@"camera-icon.png"]],
[[CDZImagePickerActionsItem alloc]initWithTitle:@"打開最新圖片" withActionType:CDZImagePickerRecentAction withImage:[UIImage imageNamed:@"clock-icon.png"]],
nil];
效果就和snapseed很像啦仍翰!
最后
所有源碼和Demo
雖然不是什么很難的輪子,但希望能分享給有需要的人观话。
關(guān)于后續(xù)的處理照片變更和獲取相冊權(quán)限另寫了一篇簡書文章予借,歡迎閱讀。
如果您覺得有幫助,不妨給個star鼓勵一下,歡迎關(guān)注&交流
有任何問題歡迎評論私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz