访问iPhone照片库对象
背景:
我正在构建一个iPhone应用程序(XCode 9.2,iOS 11+),在该应用程序中,用户可以选择照片库图像作为笔记记录过程的一部分。 主要问题是,当他们回到笔记中时,如果将其与图像相关联,则必须从照片库中加载这些图像。 我不想将每个图像都转换为持久数据的数据对象,因为它们已经存在于设备中。 仅参考图像并在需要时显示它会容易得多。
本教程将介绍:
•如何访问照片库和选择对象
•存储有关该对象的照片资产元数据
•以后通过使用存储的元数据检索对象
完成后,应用程序应如下运行:
我们在这里所做的是让我们的应用程序知道,当我们请求用户允许其照片库显示我们提供的文本消息时。
链接到照片框架
苹果有一个非常强大的框架来处理照片资产,称为照片。 我们将需要将此包含在我们的项目中。
- 返回项目导航器,然后单击顶部的项目图标,这会将项目设置加载到主要内容区域。
- 在顶部将出现几个导航选项,选择Build Phases。 与info.plist一样,“构建阶段”将具有多个选项,这些三角形的右上角指向三角形。
- 单击“将二进制文件与库链接”旁边的一个以显示其属性。 该区域允许您向应用程序中添加框架和库。
- 单击+图标,将显示一个弹出框,显示可用的框架。 开始在文本字段中输入“ 照片” ,列表将随着您输入的内容进行调整以显示匹配项。
- 选择Photos.framework选项,然后单击“添加”按钮以将其包括在项目中。
将UI元素添加到情节提要
现在,我们已经配置了应用程序,可以添加一些UI元素,以便我们可以从用户的设备中选择并显示照片。
- 返回到项目浏览器,然后选择Main.storyboard文件。 这将在内容区域显示应用程序的主故事板。
- 如果“ 实用程序”面板未打开,则显示它,并从按钮的下一行中选择“ 对象库” 。
- 使用下面的过滤文本字段或滚动浏览对象列表,选择一个UIButton并将其拖动到视图控制器顶部附近。 您可以根据需要在此处调整大小。 使用属性检查器将标题设置为“ 显示库”,您还可以在此处编辑按钮的字体,文本颜色和背景颜色。 使用另一个UIButton重复此过程,将其均匀放置在第一个按钮的右侧,将标题设置为Show Images。 第一个按钮将用于显示照片库,第二个按钮将用于在UICollectionView中查看选定的图像。
- 再次滚动浏览对象库,然后选择UICollectionView 。 选择它并将其拖到情节提要板上,将其放在按钮下方。
将UI元素连接到ViewController
- 通过单击右上角的Show Assistant Editor按钮来显示Assistant Editor 。
- 确保在助手编辑器中选择了ViewController文件。 在情节提要中,控制单击添加的UIButton。 将显示一个选择弹出窗口,选择“ 引用出口” ,并将其拖到上面具有Class ViewController:ViewController的ViewController中的行下方。
- 释放控制键时,将显示另一个弹出窗口,对于UIButton,可让您选择是创建Outlet还是Action。 选择“出口”,然后为IBOutlet属性命名。 重复第二个按钮和UICollectionView。
这只是个人喜好,但我给对象加上前缀,对于按钮,我使用btnCamelCaseName,对于集合视图,我使用cCamelCaseName。 我发现在重构或调试时了解对象是或至少应该是什么类型会有所帮助。 - 在按钮上再次执行此过程,但是这次从“已发送事件”选项中选择“ Touch Up Inside ”。 拖动到viewDidLoad函数下方,然后选择操作,将函数命名为accessLibrary。 这将是用户触摸按钮时调用的功能。 使用第二个按钮重复此过程,然后调用函数showImages。
- 控件上单击UICollectionView,然后从显示从数据源选项到视图控制器的拖动的弹出窗口中单击。 对代理执行相同的操作。 (请参见下图)。 您正在确定视图控制器将充当UICollectionView的委托。
现在,由于我们的ViewController不符合UICollectionViewDelegate协议,因此编译器将引发错误。 暂时忽略该错误,我们将在“符合UICollectionViewDelegate协议”部分中解决该错误。
- 在情节提要中 ,打开添加的UICollectionView (单击向右的三角形),然后选择UICollectionViewCell 。 在“ 实用工具”面板(最右边的面板)中,单击“ 属性”检查器 。 在“集合可重用视图”部分下,将“标识符”设置为imageCell。 返回到ViewController并将此代码添加到viewDidLoad函数:
cImages.register(UICollectionViewCell.self,forCellWithReuseIdentifier:“ imageCell”)
在ViewController中,您现在应该具有三个IBOutlet,两个具有UIButton超类,另一个具有UICollectionView超类。 现在,您的代码中还应该具有两个IBAction函数funcNameName() 。
@IBOutlet弱var btnAccessLibrary:UIButton!
@IBOutlet弱var btnShowImages:UIButton!
@IBOutlet弱var cImages:UICollectionView!
...
@IBAction func accessLibrary(_ sender:Any){
//代码在这里
}
@IBAction func showImages(_ sender:Any){
//代码在这里
}
符合UICollectionViewDelegate协议
委托模式是一对一的通信模式,在整个Apple框架中广泛使用。 我学会这种方式的方式是将这种关系像老板和实习生一样思考。 老板将把一些过程委派给实习生,然后由实习生完成该过程。
有时,在使用委托模式时,需要一些方法和属性,例如,在使用UICollectionView委托时。 这些已在授权机构的协议中列出。 您可以将协议视为需求文档,让代表知道他们至少需要完成哪些任务才能完成任务。 如果不包含它们,则XCode将抛出编译器错误消息。
除了或代替必需的方法和属性,某些委托还具有可选的方法和属性。 如果不包括这些内容,它们将不会引发错误。
对于某些对象,例如UICollectionView,也有一个数据源。 可以将数据源视为委托,除了将数据源委托给数据之外,它不是委托给用户界面控制。 像委托一样,数据源具有带有必需和可选方法及属性的协议。
让我们将UICollectionView委托和数据源协议添加到类声明中。
转到我们的ViewController的Class ViewController:ViewController行,并添加以下内容:
ViewController类别:UIViewController,UICollectionViewDelegate,UICollectionViewDataSource
为了符合UICollectionView委托和数据源协议,需要三种方法:
func numberOfSections(在collectionView中:UICollectionView)-> Int
func collectionView(_ collectionView:UICollectionView,numberOfItemsInSection部分:Int)-> Int {
func collectionView(_ collectionView:UICollectionView,cellForItemAt indexPath:IndexPath)-> UICollectionViewCell {
前两个是数据源功能。 他们让UICollectionView知道要显示多少个部分以及每个部分中有多少个项目。 对于此项目,只有一个部分。 项目的数量将取决于arrImageViews数组中对象的数量。 我们可以将这两个功能设置为:
func numberOfSections(在collectionView:UICollectionView中)-> Int {
返回1
}
func collectionView(_ collectionView:UICollectionView,numberOfItemsInSection部分:Int)-> Int {
返回arrImageViews.count
}
最后一个功能确定将在每个单元格中显示什么。 UICollectionViews和UITableViews背后的思想是单元可重用。 当单元格滚动到屏幕外时,其内容将被破坏,并将该单元格放置在重用队列中。 当新内容在屏幕上滚动时,单元格视图将从队列中删除,并与新内容一起重新使用。
当我们将UICollectionView添加到情节提要中时,我们还为默认的UICollectionViewCell提供了imageCell的重用标识符,并在ViewController中的UICollectionView中注册了该单元格。
与UITableView单元格不同,UICollectionViewCells是空画布,只是空白的UIView。 由开发人员确定什么内容进入单元格以及如何显示。 将以下代码添加到您的ViewController
func collectionView(_ collectionView:UICollectionView,cellForItemAt indexPath:IndexPath)-> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:“ imageCell”,for:indexPath)
cell.backgroundColor = UIColor.clear
让ivThisImage = arrImageViews [indexPath.row]
var ivFrame = ivThisImage.frame
ivFrame.origin.x = 5.0
ivFrame.origin.y = 5.0
ivThisImage.frame = ivFrame
cell.addSubview(ivThisImage)
返回单元
}
添加照片框架
将“照片”框架导入到文件中。 在类ViewController:ViewController上方,添加以下代码行:
导入照片
创建照片委托
Photos框架使用委托模式。
该协议还将列出可选的方法和属性。 对于Photos框架,它利用UIImagePickerController协议和UINavigationController协议,这两个协议的方法和属性都是可选的。
为了访问委托方法,我们需要让ViewController知道它应该采用Photos协议。 这是通过在超类名称后的类声明中列出用逗号分隔的协议来实现的。
返回“ 类ViewController:ViewController”行并添加以下内容:
ViewController类:UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
为了“将所有东西连接在一起”,我们需要告知Photos框架“实习生”的位置。 将此属性添加到您先前声明的IBOutlet下。
让imagePicker = UIImagePickerController()
在viewDidLoad函数中,添加以下代码行,以使UIImagePickerControllerDelegate知道其委托位置。
imagePicker.delegate =自我
最后,添加两个容器属性以存储图像标识符。
var arrImageIdentifiers = [String]()
var arrImageViews = [UIImageView]()
配置和UI设置已完成。
访问照片库并存储图像数据
在将所有对象从情节提要板连接到代码并配置了项目的情况下,下一步是访问照片库并选择图像。
该过程从用户触摸我们添加的按钮开始。 收到触摸事件时,它将调用accessLibrary函数。 在该功能中,我们将:
- 确认用户已授予我们对照片库的许可
- 如果没有请求权限
- 授予权限后,显示照片库
- 保存选择的图像数据
确认照片库访问权限
将以下代码添加到accessLibrary函数:
让状态= PHPhotoLibrary.authorizationStatus()
if(状态== PHAuthorizationStatus.denied ||状态== PHAuthorizationStatus.notDetermined){
PHPhotoLibrary.requestAuthorization({(newStatus)在
如果(newStatus == PHAuthorizationStatus.authorized){
self.showLibrary()
}其他{
让myAlert = UIAlertController(title:“照片库访问被拒绝”,消息:“照片资源加载程序需要访问您的照片库。没有它,您将无法访问任何图像。请进入您的应用程序隐私设置并允许访问。”,preferredStyle:.alert)
让okAction = UIAlertAction(title:“ OK”,样式:.default,处理程序:nil)
myAlert.addAction(okAction)
self.present(myAlert,动画:true,完成:nil)
}
})
}否则,如果status == PHAuthorizationStatus.authorized {
showLibrary()
}
第一行:
让状态= PHPhotoLibrary.authorizationStatus()
要求“照片”框架返回照片库的当前授权状态。 如果状态等于已授权,则将调用showLibrary函数。
如果状态为拒绝或未确定,则请求用户授予权限
PHPhotoLibrary.requestAuthorization
该请求返回一个闭包,其中包含用户所做的选择(newStatus)。
PHPhotoLibrary.requestAuthorization({(newStatus)在
检查newStatus值,如果已授权,则调用showLibrary ;如果未授权,则向用户显示警报,通知他们如果不授予许可,他们将无权访问照片库。
如果(newStatus == PHAuthorizationStatus.authorized){
self.showLibrary()
}其他{
self.myController.alert.presentAlert(“拒绝照片库访问”,“ CheetSheet需要访问您的照片库。没有它,您将无法访问任何图像。请进入应用程序隐私设置并允许访问。” ,自我)
}
显示照片库
showLibrary为UIImagePickerController设置了一些属性,然后通过将UIViewController推入导航堆栈将其呈现给用户(这是需要UINavigationControllerDelegate的地方)。
func showLibrary(){
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for:.photoLibrary)!
存在(imagePicker,动画:true,完成:nil)
}
在模拟器中,您的应用现在应该显示照片库。
保存图像数据
用户选择图像后,将开始保存图像数据过程。 UIImagePickerControl委托具有方法didFinishPickingMediaWithInfo ,一旦用户从照片库中选择了图像,就会调用该方法。 此方法将返回具有5个键的字典(信息),即UIImagePickerControllerImageURL,UIImagePickerControllerMediaType,UIImagePickerControllerPHAsset,UIImagePickerControllerReferenceURL,UIImagePickerControllerOriginalImage。
我们需要UIImagePickerControllerPHAsset。 在accessLibrary函数下方,添加didFinishPickingMediaWithInfo委托方法和以下代码:
func imagePickerController(__ picker:UIImagePickerController,didFinishPickingMediaWithInfo信息:[String:任何]){
如果picker.sourceType == .photoLibrary {
如果让thisAsset:PHAsset = info [UIImagePickerControllerPHAsset]作为? PHAsset {
self.arrImageIdentifiers.append(thisAsset.localIdentifier)
}
self.dismiss(动画:true,完成:nil)
}
}
我们将检查选择器是否已从照片库中选择图像
如果picker.sourceType == .photoLibrary {
如果是,我们将查看信息字典中的键UIImagePickerControllerPHAsset是否具有可选值,并且该可选值是否不是nil且为PHAsset类型。
如果让thisAsset:PHAsset = info [UIImagePickerControllerPHAsset]作为? PHAsset {
如果通过了条件检查,我们将采用PHAsset的属性localIdentifier (它是一个字符串)并将其存储在我们的arrImageIdentifiers数组中。
self.arrImageIdentifiers.append(thisAsset.localIdentifier)
代码完成后,我们将关闭UIImagePickerController。
self.dismiss(动画:true,完成:nil)
显示图像数据
要显示选定的图像,用户将触摸“显示图像”按钮。 这将启动对showImages函数的调用。 在showImages函数中,我们将:
- 清除arrImageViews数组并设置PHFetchOptions。
- 在选项中添加排序描述符,以按日期对所选图像进行排序。
- 使用我们设置的选项以及存储在arrImageIdentifiers中的标识符执行fetchAssets。
- 创建一个PHImageManager。
- 循环浏览我们的PHAsset结果,并让PHImageManager请求每个图像。
- 将具有返回图像的UIImageView添加到arrImageViews数组。
- 使用arrImageViews作为数据源,重新加载我们的UICollectionView。
将以下代码添加到showImages函数。
@IBAction func showImages(_ sender:Any){
arrImageViews.removeAll()
让选项= PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key:“ creationDate”,升序:true)]
让结果= PHAsset.fetchAssets(withLocalIdentifiers:arrImageIdentifiers,选项:选项)
让经理= PHImageManager.default()
results.enumerateObjects {(thisAsset,_,_)在
manager.requestImage(for:thisAsset,targetSize:CGSize(width:80.0,height:80.0),contentMode:.aspectFit,options:nil,resultHandler:{{thisImage,_)in
self.arrImageViews.append(UIImageView(image:thisImage))
})
}
self.cImages.reloadData()
}
从arrImageView数组中删除所有对象。
arrImageViews.removeAll()
设置PHFetchOptions对象,并向其添加排序描述符以按日期排序。
让选项= PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key:“ creationDate”,升序:true)]
执行fetchAssets请求,传递我们存储在arrImageIdentifiers中的图像标识符和我们刚刚设置并实例化PHImageManager的PHFetchOptions。
让结果= PHAsset.fetchAssets(withLocalIdentifiers:arrImageIdentifiers,选项:选项)
让经理= PHImageManager.default()
枚举返回的结果,要求PHImageManager为每个返回的资产获取图像。 为每个返回的图像表示提供一些属性。
- targetSize:CGSize,用于设置图像的高度和宽度
- contentMode:关于如何使图像适合所请求大小的宽高比的选项。
- 选项:用于指定照片应如何处理请求,格式化请求的图像以及将进度或错误通知您的应用的选项。
manager.requestImage(for:thisAsset,targetSize:CGSize(width:80.0,height:80.0),contentMode:.aspectFit,选项:nil,resultHandler:{{thisImage,_)in
使用返回的图像作为图像源创建一个UIImageView。
self.arrImageViews.append(UIImageView(image:thisImage))self.cImages.reloadData()
重新加载UICollectionView
self.cImages.reloadData()
清理项目(Command-K),然后生成并运行它(Command-R)。 现在,您应该能够从“照片库”中选择照片,将其存储然后显示。