65.9K
CodeProject 正在变化。 阅读更多。
Home

用于选择个人资料图片的类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (3投票s)

2016 年 3 月 2 日

CPOL

11分钟阅读

viewsIcon

19207

downloadIcon

194

使用 UIImagePickerController 来复制通讯录应用中的个人资料选择功能需要解决一些棘手的问题。本文介绍了一种使用 UIImagePickerController 来复制此功能的方法。

引言

选择和编辑类似通讯录应用中的图片似乎是类 UIImagePickerController (图像选择器)的正确用例。它甚至似乎很好地支持自定义编辑叠加层。 不幸的是,当我开始使用它时,我发现它不够灵活。 复制这些功能比预期的要面临更多的挑战。 

本文介绍了一个名为 MMSProfileImagePicker(个人资料选择器)的类,用于支持与通讯录应用完全相同的图像选择和编辑功能。 

也有其他人解决了这个问题,并将他们的类提供给开发者社区。 那么你可能会问,有什么新东西? 我写这篇文章的目的是揭示一些可以重复使用来解决其他棘手问题的技术。

此解决方案借助图像选择器支持这些功能。本文重点介绍与图像选择器集成以及如何在自己的应用中使用个人资料选择器的技术。 可下载的示例实现了与通讯录应用完全相同的图像选择解决方案。 

图 1 - 示例应用程序

背景

在探索如何使用图像选择器的叠加层功能时,我遇到了一些障碍。 遇到的一些问题是如何正确地定位和调整叠加层的大小,如何使圆形仅在从相机选择时显示在编辑屏幕上,以及如何正确地将其放置在 z 轴顺序中? 这些是我能回忆起的几个问题。 

有些问题没有解决方案,有些很复杂,还有些与类的内部实现耦合太紧密。 例如,stackoverflow 上的这个解决方案 操纵了图像选择器创建的私有视图来显示圆形叠加层。 

这无疑很巧妙,但它非常容易受到未来 iOS 版本变化的影响,而且它只适用于选择照片,而不适用于拍照或编辑照片: 在显示相机以在“移动和缩放”屏幕(编辑屏幕)出现之前插入叠加层时,无法拦截导航代理的调用。   

有许多有趣的问题需要解决:一个文章无法全部包含。 关于裁剪位图的挑战和解决方案,请阅读我的文章 一个用于裁剪图片的视图类。 它描述了此解决方案使用的 UIImage+Cropping 类别。

请注意,我没有审查任何开源解决方案。 它们之间的任何相似之处纯属巧合。

方法

策略是充分利用图像选择器,因为它已经具备了许多所需的功能。 不幸的是,该类的一些功能为了实现与通讯录应用相同的功能而需要重新实现。 一个具有挑战性的方面是仅在编辑屏幕上显示圆形叠加层。 当配置为显示相机时,叠加层会同时显示在图像获取屏幕和编辑屏幕上。 因此,解决这个问题需要重新发明图像选择器已经解决的问题的解决方案。 

为了满足这一需求,我得出结论,最好的方法是让图像选择器负责呈现相机和从照片库选择图像,并让新类负责创建和显示编辑屏幕。 

这种方法揭示了其他需要解决的复杂性

  • 如何阻止图像选择器显示其编辑屏幕?
  • 如何从图像选择器过渡到编辑屏幕?
  • 如何从图像选择器获取相机图像以交接给编辑屏幕?
  • <licreen< li="">
  •  
  •  
  •  

禁用图像选择器的编辑屏幕

此解决方案完全重新创建了编辑屏幕功能,并显示它而不是图像选择器的编辑屏幕。 当配置为从相机选择时,图像选择器会在图像获取屏幕和编辑屏幕上显示 cameraOverlay 属性。 此方法提供了一种解决方案,可以防止圆形叠加层在相机的图像获取屏幕上显示。   

从照片库配置中呈现自定义编辑屏幕非常简单。 图像选择器有一个属性可以禁用编辑屏幕的呈现。 将 allowsEditing 属性设置为 NO 会在不显示的情况下返回用户选择的图像。 这为显示自定义编辑屏幕提供了一个简单的钩子。

不幸的是,当配置为相机选择时,图像选择器不遵守此属性,无论其值如何都会显示编辑屏幕。 这里事情变得棘手起来。 我不想重写整个相机功能,但没有文档说明如何禁用它,但我也不想放弃这种方法。

如果我能找到一种方法让图像选择器调用我的操作方法而不是它自己的方法,个人资料选择器就可以阻止其编辑屏幕的显示。 由于图像选择器是一个导航控制器,个人资料选择器可以通过支持 UINavigationControllerDelegate 接口来观察它转换到的视图。 但是,如果图像选择器是从应用程序的视图控制器呈现的,UIKit 会调用应用程序视图控制器的方法。 

为了从应用程序中移除处理导航代理的任何责任,个人资料选择器创建了一个代理视图控制器,其任务是处理导航代理并将调用转发给个人资料选择器。

在导航代理方法 navigationController:didShowViewController:animated: 中,个人资料选择器会在视图层次结构中搜索当用户点击时会拍照的按钮。 步骤如下:

它会检查相机是否即将显示其视图。 

 if (imagePicker.sourceType == UIImagePickerControllerSourceTypeCamera && !isSnapPhotoTargetAdded && isPresentingCamera)

它会在底部视图栏的子视图列表的索引 8 处找到“拍照”按钮视图。

        UIView* bottomBarView = [viewController.view.subviews objectAtIndex:2];

        UIButton* buttonView = [bottomBarView.subviews objectAtIndex:8];

并移除其 UIControlEventTouchUpInside 事件的操作方法。 

        [buttonView removeTarget:viewController.view action:NULL forControlEvents:UIControlEventTouchUpInside];

并添加自己的处理程序到按钮。

        [buttonView addTarget:self action:@selector(takePhoto:) forControlEvents:UIControlEventTouchUpInside];

现在,当用户点击按钮拍照时,将调用个人资料选择器的操作方法 takePhoto:,并且图像选择器无法显示其编辑屏幕。

过渡到编辑屏幕

个人资料选择器是负责呈现自定义编辑屏幕的视图控制器。 它创建视图和子视图,显示圆形叠加层和图像,并处理屏幕的按钮事件。 虽然布局这个屏幕并不算太难,但有很多棘手的布局计算需要进行,以呈现和居中图像,居中叠加层,以及创建滚动视图的 contentInset。 

由于本文的目的是描述此对象与图像选择器的集成,因此我将细节留给读者下载代码并审查实现。 

有三种过渡到编辑屏幕的路径。

  1. 应用程序呈现一个现有的图像进行编辑。
  2. 图像将从相册中选择。
  3. 相机将捕获图像。

呈现现有图像

对于应用程序请求个人资料选择器编辑现有图像的用例,它将在 presentEditScreen:withImage: 方法中使用模态演示样式来呈现编辑屏幕。 

/* presentEditScreen: presents the move and scale window for a supplied image.  This use case is for when all that's required is to crop an image not to select one from the camera or photo album before cropping.
 */

-(void)presentEditScreen:(UIViewController* _Nonnull)vc withImage:(UIImage* _Nonnull)image{ 

    isDisplayFromPicker =  isPresentingCamera = NO;

    imageToEdit = image;

    presentingVC = vc;

    self.modalPresentationStyle = UIModalPresentationFullScreen;

    [presentingVC presentViewController:self animated:YES completion:nil];

}

从相册选择图像

当应用程序请求个人资料选择器显示相册选择时,它将图像选择器的 sourceType 属性设置为 UIImagePickerControllerSourceTypePhotoLibrary。 个人资料选择器不依赖于图像选择器来显示其编辑屏幕,因此它将图像选择器的 allowsEditing 属性设置为 NO

当用户选择图像时,图像选择器会在个人资料选择器控制器上调用代理方法 imagePickerController:didFinishPickingMediaWithInfo。 个人资料选择器引用图像并调用其 editImage 方法来显示带图像的编辑屏幕。

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {

    UIImage* tempImage = [info objectForKey:UIImagePickerControllerOriginalImage];

    [self editImage:tempImage];
}

由于图像选择器是一个导航控制器,它会将编辑屏幕推送到堆栈上以支持取消导航。 

    [imagePicker pushViewController:self animated:NO]

除非导航栏隐藏,否则它会在编辑屏幕上显示。 在将其推入之前,它会设置隐藏导航栏的属性。

    [imagePicker setNavigationBarHidden:YES]

editImage 的完整实现如下。

/* editImage: presents the move and scale view initialized with the input image.  This is only to be called from the presentCamera and presentPhotoPicker workflows after the user has captured an image or selected one.
 */

-(void)editImage:(UIImage*)image {    

    imageToEdit = image;

    self.modalPresentationStyle = UIModalPresentationFullScreen;

    [imagePicker setNavigationBarHidden:YES];

    [imagePicker pushViewController:self animated:NO];

}

从相机选择图像

与从相册选择类似,要显示相机,将 sourceType 属性设置为 UIImagePickerControllerSourceTypeCamera。 当照片被捕获时,个人资料选择器调用 editImage 来显示编辑屏幕。

获取相机图像

由于个人资料选择器替换了图像选择器的操作方法以阻止其显示编辑屏幕,因此它还会阻止其获取图像。 个人资料选择器负责将相机视图中的图像传输到设备。

对于对这些算法感兴趣的读者,我将留给您审查代码,但如果您有任何问题,请提交评论。 请参阅 MMSProfileImagePicker.m 文件中的以下方法:

  • prepareToCaptureStillImage: - 它负责创建 AVCaptureSession 并对其进行初始化。 
  • captureStillImage:  - 它负责启动从相机视图到数据缓冲区的图像传输。 
  • cameraConnection: - 查找相机的 AVCaptureConnection 并返回它。
  • getCameraDevice: - 返回手机背面相机的 AVCaptureDevice。
  • captureInpute: - 将设备的 AVCaptureDeviceInput 添加到会话。

captureStillImage 方法获取相机图像并使用接收到的照片调用 editImage

关于获取图像有一个特别的挑战我想在此说明。 在实现的第一个草稿中,图像质量很低: 比用相机应用拍摄的要暗。 

我搜遍了互联网寻找其他人的经验,其中一个解释建议在启动 AVCaptureSession 后延迟调用 captureStillImageAsynchronouslyFromConnection。 插入 0.75 秒的延迟解决了这个问题。 我认为还有其他缺失的配置可以使这个要求失效。 插入一个未公开的延迟来纠正这个问题是不可靠的。 目前它能工作,但如果任何读者有其他解决方案,我很想听到。 

使用 MMSProfileImagePicker

为了让您的应用程序拥有类似通讯录应用的功能,该类支持三个公共接口: 

调用 presentEditScreen: 方法来编辑应用程序已有的图像。

-(void)presentEditScreen:(UIViewController* _Nonnull)vc withImage:(UIImage* _Nonnull)image; 

调用 selectFromPhotoLibrary: 方法从照片库中选择图像。

-(void)selectFromPhotoLibrary:(UIViewController* _Nonnull)vc;

调用 selectFromCamera: 方法通过拍照来选择图像。 

-(void)selectFromCamera:(UIViewController* _Nonnull)vc;

要接收选择和编辑后的图像,应用程序应在呈现个人资料选择器的视图控制器上实现代理 MMSProfileImagePickerDelegate(个人资料选择器代理)。 在调用所需的图像选择方法之前,它会设置个人资料选择器的代理属性。

- (IBAction)moveAndScale{

    profilePicker = [[MMSProfileImagePicker alloc] init];

    profilePicker.delegate = self;

    [profilePicker presentEditScreen:self withImage:originalImage];

}

个人资料选择器代理是一个类似于 UIImagePickerControllerDelegate(图像选择器代理)的接口,其目的相似:接收选择和编辑后的图像。 在退出方法之前,应用程序应关闭个人资料选择器。 

-(void)mmsImagePickerController:(MMSProfileImagePicker *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {        

    cropImage = [info objectForKey:UIImagePickerControllerEditedImage];

    originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];

    self.circleImageView.image = cropImage;

    self.squareImageView.image = cropImage;

    self.btnAdd.hidden = YES;

    self.circleImageView.hidden = NO;

    self.btnEdit.hidden = NO;

    [picker dismissViewControllerAnimated:YES completion:nil];

}

字典参数支持图像选择器代理等效实现中定义的所有编辑信息键。 请参阅 iOS 开发者文档中的编辑信息键。 

另一方面,用户可能已经开始选择图像的过程但决定中止操作。 在这种情况下,个人资料选择器会调用代理方法 mmsImagePickerControllerDidCancel:。 作为响应,应用程序应调用 dismissModalViewControllerAnimated: 来关闭个人资料选择器。

-(void)mmsImagePickerControllerDidCancel:(MMSProfileImagePicker *)picker {

    [self dismissViewControllerAnimated:YES completion:nil];

}

摘要

软件中很少有解决问题的“最佳”方法。 哦,我们都有自己的坚定观点。 尽管如此,好的方法、更好的方法、不同的方法和糟糕的方法都存在。 如果你深入探究任何应用程序,你都会找到它们的所有例子。 竞争性约束、团队文化、经验和个人风格的力量塑造了我们的解决方案。

我写这篇文章是为了揭示一种解决此问题的新方法。 我在开发应用程序时,我的优先事项是充分利用框架,编写最少的代码,寻找将重点放在业务逻辑上的方法,而不是创建像本文所述的启用小部件和工具,并在可用时使用第三方库。

在这种情况下,我期望 Cocoa Touch 框架能提供解决方案。 当我开始使用它时,我发现它不能开箱即用地支持该功能。 但是,它似乎支持通过叠加层支持来扩展功能。 因此,为了编写更少的代码,我探索了这种方法。 

我发现它太笨拙和僵化了。 由于我已经在这条路上花费了一些时间,我相信我可以编写自己的编辑屏幕版本并将其插入。 但缺乏经验撞上了现实的墙壁,我发现事情并没有那么简单。 当我解决一个问题时,就像剥洋葱一样,一个新的问题就会显现出来,直到我最终找到了这里描述的解决方案。

希望通过这篇文章,我能让读者欣赏一种解决此问题的方法,并获得一些您可以在自己的开发中使用到的技术。 如果您喜欢它并将其 pod 用于您的应用程序,那就太好了。 如果您喜欢它并为其贡献您自己的增强功能,那就更好了。 

此类可在 cocoapods.org 上作为 cocoapod 使用。 搜索 MMSProfileImagePicker 即可开始使用。 如果您希望报告错误并为小部件贡献您自己的改进,您可以在 github 上找到代码:https://github.com/miller-ms/MMSProfileImagePicker

祝您编码愉快!

历史

  • 2016 年 3 月 7 日 - 更新了摘要。
  • 2016 年 3 月 3 日 - 重写了一些读起来不太顺畅的句子。
  • 2016 年 3 月 2 日 - 初始发布。
© . All rights reserved.