现在的App都非常流行圆形的头像,比如QQ右上角的头像,今日头条的头像等等。这已经成为App设计的趋势了。今天我们就来简单实现一下这个功能,我还会把从手机拍照中或者图库中取出作为头像的照片存储到应用程序沙盒中。下次进入应用的时候还会显示该头像。示例代码上传至:https://github.com/chenyufeng1991/AvatarPhoto 。
(1)该demo使用storyboard进行实现。首先拖入一个ImageView用来显示头像和一个按钮。并拖拽到代码中进行绑定。图片绑定IBOutlet,按钮绑定IBAction。storyboard的设计效果如下:
(2)现在要设置矩形的ImageView为圆形,同时可以设置该控件的边框颜色和宽度。实现代码如下:
- (void)setCirclePhoto{ //avatarImage是图片控件; [self.avatarImage.layer setCornerRadius:CGRectGetHeight([self.avatarImage bounds]) / 2]; self.avatarImage.layer.masksToBounds = true; //可以根据需求设置边框宽度、颜色 self.avatarImage.layer.borderWidth = 1; self.avatarImage.layer.borderColor = [[UIColor blackColor] CGColor]; //设置图片; self.avatarImage.layer.contents = (id)[[UIImage imageNamed:@"avatar.png"] CGImage]; }
- (IBAction)selectPhoto:(id)sender { if ([self.imagePickerPopover isPopoverVisible]) { [self.imagePickerPopover dismissPopoverAnimated:YES]; self.imagePickerPopover = nil; return; } UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.editing = YES; //如果设备支持相机,就使用拍照技术 //否则让用户从照片库中选择照片 if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; } else{ imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; } imagePicker.delegate = self; //允许编辑图片 imagePicker.allowsEditing = YES; //创建UIPopoverController对象前先检查当前设备是不是ipad if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { self.imagePickerPopover = [[UIPopoverController alloc] initWithContentViewController:imagePicker]; self.imagePickerPopover.delegate = self; [self.imagePickerPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } else { [self presentViewController:imagePicker animated:YES completion:nil]; } } -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info { //通过info字典获取选择的照片 UIImage *image = [info valueForKey:UIImagePickerControllerEditedImage]; //以itemKey为键,将照片存入ImageStore对象中 [[MyImageStore sharedStore] setImage:image forKey:@"CYFStore"]; //将照片放入UIImageView对象 self.avatarImage.image = image; //判断UIPopoverController对象是否存在 if (self.imagePickerPopover) { [self.imagePickerPopover dismissPopoverAnimated:YES]; self.imagePickerPopover = nil; } else { //关闭以模态形式显示的UIImagePickerController [self dismissViewControllerAnimated:YES completion:nil]; } }
(4)运行以上程序,就已经可以从照相机或者图库中取出照片放到圆形ImageView中了。我解释一下上面的一行代码,
[[MyImageStore sharedStore] setImage:image forKey:@"CYFStore"];这行代码是把该照片存储到应用沙盒中,也使用键值对的方式来存储。下次程序启动后,直接会读取该图片。等下我会来实现这个MyImageStore类。
同时,也要声明一个属性:
@property (strong, nonatomic) UIPopoverController *imagePickerPopover;UIPopoverController对象用来打开照相机。
(5)下面将要来实现把图片存储到沙盒(文件存储)中(代码较多,可直接参考源代码)
+(instancetype)sharedStore { static MyImageStore *instance = nil; //确保多线程中只创建一次对象,线程安全的单例 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] initPrivate]; }); return instance; } -(instancetype)initPrivate { self = [super init]; if (self) { _dictionary = [[NSMutableDictionary alloc] init]; //注册为低内存通知的观察者 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(clearCaches:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self; } -(void)setImage:(UIImage *)image forKey:(NSString *)key { [self.dictionary setObject:image forKey:key]; //获取保存图片的全路径 NSString *path = [self imagePathForKey:key]; //从图片提取JPEG格式的数据,第二个参数为图片压缩参数 NSData *data = UIImageJPEGRepresentation(image, 0.5); //以PNG格式提取图片数据 //NSData *data = UIImagePNGRepresentation(image); //将图片数据写入文件 [data writeToFile:path atomically:YES]; } -(UIImage *)imageForKey:(NSString *)key { //return [self.dictionary objectForKey:key]; UIImage *image = [self.dictionary objectForKey:key]; if (!image) { NSString *path = [self imagePathForKey:key]; image = [UIImage imageWithContentsOfFile:path]; if (image) { [self.dictionary setObject:image forKey:key]; } else { NSLog(@"Error: unable to find %@", [self imagePathForKey:key]); } } return image; } -(NSString *)imagePathForKey:(NSString *)key { NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDirectory = [documentDirectories firstObject]; return [documentDirectory stringByAppendingPathComponent:key]; } -(void)clearCaches:(NSNotification *)n { NSLog(@"Flushing %ld images out of the cache", (unsigned long)[self.dictionary count]); [self.dictionary removeAllObjects]; }
(6)运行程序,实现效果如下:
总结,当选择完图片之后,就会存储到文件中,当下次程序重新启动,就会从文件中自动读出该图片进行显示。在实际开发中,图片可能是从网络获取的,并且选择完图片后也会传到服务器,当然你也可以在本地做一个缓存,提高效率。该模块可以进行扩展,也可以直接拿到项目中使用。
博客更新:
如果我不想让图片在取完之后进行截取编辑,可以设置:
imagePicker.allowsEditing = false;
同时把:
//通过info字典获取选择的照片 UIImage *image = [info valueForKey:UIImagePickerControllerEditedImage];
//通过info字典获取选择的照片 UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
上面都没有涉及把一张图片存储到手机的图库中,以下方法可以把拍照后的Image保存到用户手机中:其中第三个参数可以写一个回调方法,也就是把照片存储到图库后的方法回调。
//把一张照片保存到图库中,此时无论是这张照片是照相机拍的还是本身从图库中取出的,都会保存到图库中; UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
如果我们要把一张图片进行保存或者使用网络传输,使用NSData较为合适,并进行压缩:
//压缩图片,如果图片要上传到服务器或者网络,则需要执行该步骤(压缩),第二个参数是压缩比例,转化为NSData类型; NSData *fileData = UIImageJPEGRepresentation(image, 1.0);
应用优化与更新:
其实在让用户选择图片的时候,应该也要用户选择是打开照相机还是图库。下面的代码优化是弹出选择框(UIAlertController),主要就是设置sourceType属性。现把点击按钮的事件处理修改如下:代码更新已经提交至:https://github.com/chenyufeng1991/AvatarPhoto。
- (IBAction)selectPhoto:(id)sender { if ([self.imagePickerPopover isPopoverVisible]) { [self.imagePickerPopover dismissPopoverAnimated:YES]; self.imagePickerPopover = nil; return; } UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.editing = YES; imagePicker.delegate = self; /* 如果这里allowsEditing设置为false,则下面的UIImage *image = [info valueForKey:UIImagePickerControllerEditedImage]; 应该改为: UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage]; 也就是改为原图像,而不是编辑后的图像。 */ //允许编辑图片 imagePicker.allowsEditing = YES; /* 这里以弹出选择框的形式让用户选择是打开照相机还是图库 */ //初始化提示框; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请选择打开方式" message:nil preferredStyle: UIAlertControllerStyleActionSheet]; [alert addAction:[UIAlertAction actionWithTitle:@"照相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;//设置为照相机打开; //创建UIPopoverController对象前先检查当前设备是不是ipad if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { self.imagePickerPopover = [[UIPopoverController alloc] initWithContentViewController:imagePicker]; self.imagePickerPopover.delegate = self; [self.imagePickerPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } else{ [self presentViewController:imagePicker animated:YES completion:nil]; } }]]; [alert addAction:[UIAlertAction actionWithTitle:@"相册" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;//设置为图库打开; //创建UIPopoverController对象前先检查当前设备是不是ipad if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { self.imagePickerPopover = [[UIPopoverController alloc] initWithContentViewController:imagePicker]; self.imagePickerPopover.delegate = self; [self.imagePickerPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } else{ [self presentViewController:imagePicker animated:YES completion:nil]; } }]]; [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) { //取消; }]]; //弹出提示框; [self presentViewController:alert animated:true completion:nil]; }
github主页:https://github.com/chenyufeng1991 。欢迎大家访问!