iOS开发tips-PhotoKit
概述
PhotoKit应该是iOS 8 开始引入为了替代之前ALAssetsLibrary的相册资源访问的标准库,后者在iOS 9开始被弃用。当然相对于ALAssetsLibrary其扩展性更高,api使用起来也更加的强大,但这并非今天讨论的重点,这里主要讨论PhotoKit使用的一些技巧和容易踩的坑。
PHImageManager or Custom
访问相册资源常用的操作还是获取资源,比如获取一张相册的图片,可以通过PHImageManager.default().requestImage(xxx)一个回调就可以了,其实除了简单的图片拉取苹果建议大家使用一个自己维护的PHCachingImageManager(PHImageManager子类)单例,因为从名字可以看出它可以进行资源缓存(具体用法参见:PHCachingImageManager),这个在列表滑动过程中比较有用,因为可以使用startCachingImages(for:targetSize:contentMode:options:)预加载还未显示的资源,在不需要使用的时候调用stopCachingImages(for:targetSize:contentMode:options:)移除缓存,除此之外其实它更多的是使用父类的功能。
关于requestImage(for:targetSize:contentMode:options:resultHandler:)方法其实它的options是更容易让大家使用的时候经常迷糊的地方,因为iOS相册的照片很可能并不在本地而是在iCloud上面存储,本地只有缩略图:
- isNetworkAccessAllowed:是不是打开网络加载,对于iCloud优化的图片本地只有缩略图,需要了开启网络加载,默认是false,但是建议非特殊情况下不要关闭网络加载设为true
- isSynchronous:获取图片时是否同步获取,否则异步获取,非特殊情况下不建议使用同步获取会阻塞线程,默认为false
- version:使用资源的版本,例如说一个视频获取过程中是要获取编辑过的还是获取原始视频(因为iOS的相册编辑时可以恢复的,所以其实里面包含了编辑信息),默认current对于未编辑的则获取原始资源,编辑过则获取编辑过的资源。
- deliveryMode:字面意思资源交付模式,就是当发起一个请求后系统如何提供请求的资源(不过很可能这个资源并不在本地),默认是opportunistic同步调用只返回一种资源异步调用返回多种资源(例如不同尺寸的图片,这也就是说和上面的isSynchronous相关);highQualityFormat任何情况下只返回高质量的一种资源;fastFormat任何情况下只返回一种结果,它可能是低清图片(是不是低清晰度可以通过resultHandler结果中的info字段PHImageResultIsDegradedKey来判断是不是低清晰度)。
- resizeMode:重设尺寸模式,这个其实关系到方法中targetSize参数,默认是fast代表当本地是原图则返回原图,本地是缩略图则使用targetSize来获取一个最优图片,但是尺寸可能比targetSize要略大(注意如果targetSize是PHImageManagerMaximumSize则会拉取原图此属性此时没有意义直接忽略);none则返回原图大小;exact同fast但是返回的高清图。但是在异步情况下因为可能存在两种图片会稍有不同,下面分两种情况介绍。
isSynchronous和deliveryMode、resizeMode之间的关系(前提是开启网络加载isNetworkAccessAllowed = true):
isSynchronous = true同步加载:此时deliveryMode会被忽略掉,所以只要看resizeMode
- none:返回原图尺寸
- fast:原图为缩略图时使用targetSize优化,返回一个可能比targetSize稍大的图片
- exact:返回指定targetSize的高清图片
isSynchronous = false异步加载:此时要看deliveryMode和resizeMode两者的变化情况
deliveryMode = opportunistic
- none:先返回低清图片,再返回原图
- fast:先返回低清图片,再返回使用targetSize优化,可能比targetSize稍大的图片
- exact:先返回低清图片,再返回指定targetSize的高清图片
deliveryMode = highQualityFormat
- none:返回原图
- fast:返回使用targetSize优化,可能比targetSize稍大的图片
- exact:返回指定targetSize的高清图片
deliveryMode = fastFormat
- none:返回低清图片
- fast:返回低清图片
- exact:返回低清图片
对于是否返回两次结果总结起来对于返回结果只有异步请求在不设置deliveryMode或者deliveryMode = opportunistic 时会发送两次请求。
没错上面的情况如果了解不清楚很容易掉进坑里,一般的情况下只要打开网络,使用默认值即可。但是话说没有网络的情况下是什么情况呢?
无网络情况
上面说了那么多配置那么对于没有网络的情况呢?因为PHImageRequestOptions默认其实就是没有网络的。先看一下下面的请求:
let requestOption = PHImageRequestOptions()
requestOption.resizeMode = .exact
let scale:CGFloat = UIScreen.main.scale
let newSize = CGSize(width: 200.0, height: 200)
PHImageManager.default().requestImage(for: asset, targetSize: newSize, contentMode: .aspectFill, options: requestOption,resultHandler: completeHandler)
首先没有设置isNetworkAccessAllowed属性,那么默认值就是false,这个时候也就是没有任何网络请求,其次resizeMode = .exact也就是请求比较高清的图片。但是一个重要的问题是这个图片可能会是一个低清图片,甚至达不到一个200200的缩略图的预期,有可能特别模糊。
按照前面说的,deliveryMode 没有设置默认是opportunistic,isSynchronous*没有设置默认是false,在异步情况下是 首先这种情况下也会返回两个结果,打印info信息:
第一次:
[AnyHashable("PHImageResultIsDegradedKey"): 1, AnyHashable("PHImageResultRequestIDKey"): 1100]
第二次:
[AnyHashable("PHImageResultIsDegradedKey"): 0, AnyHashable("PHImageErrorKey"): Error Domain=NSCocoaErrorDomain Code=-1 "(null)", AnyHashable("PHImageResultIsInCloudKey"): 1, AnyHashable("PHImageResultRequestIDKey"): 1100]
可以看出第一次是低清图片,第二次是高清,但是此时出错了PHImageResultIsInCloudKey = 1,本地并没有高清图,但是因为无法访问网络去获取就报错了,抛出PHImageErrorKey信息,同时此时返回的image = nil,这样的结果就是只能看到低清图片。因此是否返回清晰照片除了上面说的,还和是否允许网络请求有直接关系。
综上来看除非特殊情况下,请打开网络请求使用异步请求,并且在合适的时机判断是否请求返回两次。
视频获取
对于视频资源的获取和图片还有些不同,因为使用PhotoKit上传视频的过程并不是直接获取到视频路径来做的,当然如果不熟悉这个过程很可能使用下面的方式:
let option = PHVideoRequestOptions()
option.isNetworkAccessAllowed = true
option.version = .current
option.deliveryMode = .highQualityFormat
PHImageManager.default().requestAVAsset(forVideo: phAsset, options: option) { (avAsset, _, _) in
if let avAsset = avAsset as? AVURLAsset {
let fileURL = avAsset.url
// upload by fileURL
}
}
这套代码遇到的问题很可能是iOS 10之前的系统上传失败,之后的基本还是可以传成功的,主要是因为这个url路径在低版本系统访问受限。
正确的姿势应该是先把视频导出到沙盒然后从本地上传,那么如何copy到本地呢?答案是使用PHAssetResourceManager,这个类出现的比较晚在iOS 9才引入的,就是为了方便资源管理,可以通过他将本地相册的资源写入沙盒然后上传沙盒的资源。
let resources = PHAssetResource.assetResources(for: phAsset)
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
PHAssetResourceManager.default().writeData(for: assetResource, toFile: videoURL, options: options, completionHandler: { (error) in
// upload by videoURL
})
但是这并非就万事大吉了,接下来的一个bug就是,通过这种方式如果上传用户已经编辑过的资源会发现它上传的是编辑之前的(比如说用户将60s的视频通过系统相册剪辑到50s,它上传的还是60s的),因为它相比较于PHImageManager请求可以设置PHVideoRequestOptions.version它缺少了这个信息,查找api也没有发现可以用的设置,所以这种方式还是不可取(如果有朋友知道怎么使用这种方式获取不同的version可以留言告诉我)。所以这时可以采用PHImageManager的另一个方法requestExportSession(xxx)将视频导出到沙盒然后上传,但是这么做的另一个小问题就是如果想要导出原始视频就不太可能了,只能尽可能拿到一个更高清晰度的视频。
let videoOption = PHVideoRequestOptions()
videoOption.version = PHVideoRequestOptionsVersion.current
videoOption.isNetworkAccessAllowed = true
PHImageManager.default().requestExportSession(forVideo: phAsset, options: videoOption, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in
if let exportSession = exportSession {
exportSession.outputURL = videoURL
exportSession.outputFileType = AVFileType.mp4
exportSession.exportAsynchronously {
if exportSession.status == AVAssetExportSession.Status.completed {
// upload by videoURL
}
}
}
话说到了这里是不是就真的没事了?试着用下面的视频上传试试:
TestVideo.zip
exportSession.status应该会失败,当然如果你是iOS 13应该是可以的,但是如果是iOS 13以下在当前环境测试都是通不过的,这个视频查看meta信息也没有什么特殊的,但是这个是Android导出的一个视频,失败信息是:无法完成此操作。除此之外并没有有用的信息,初步猜测导出时应该是编解码出错了。
其实到了这里,用上面的方式应该就无法解决这个问题了,至少目前没有找到有效的办法,兜底策略就是失败后采用上面的第二种方式。
iOS开发tips-PhotoKit的更多相关文章
- iOS开发tips总结
tip 1 : 给UIImage添加毛玻璃效果 func blurImage(value:NSNumber) -> UIImage { let context = CIContext(opti ...
- iOS 开发 Tips
1.MVVM 的优点 MVVM 兼容 MVC,可以先创建一个简单的 View Model,再慢慢迁移. MVVM 使得 app 更容易测试,因为 View Model 部分不涉及 UI. MVVM 最 ...
- IOS开发-提升app性能的25条建议和技巧
前言 这篇文章介绍了作者开发工作中总结的25个iOS开发tips, 多年之前读过这篇文章.收益良多,基本每一个tips在我的应用开发过程中都使用过.今天把这篇文章又一次整理转发下,与大家一起学习,不论 ...
- iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)
本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 这里接着前文<iOS ...
- iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)
转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...
- iOS开发200个tips总结(一)
tip 1 : 给UIImage添加毛玻璃效果 func blurImage(value:NSNumber) -> UIImage { let context = CIContext(opti ...
- iOS开发:创建真机调试证书及描述文件
iOS开发:创建真机调试证书及描述文件 关于苹果iOS开发,笔者也是从小白过来的,经历过各种困难和坑,其中就有关于开发证书,生产证书,in_house证书,add_Hoc证书申请过程中的问题,以及上架 ...
- ios开发中的小技巧
在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...
- fir.im Weekly - 94 个 iOS 开发资源推荐
距离 2016 年还有 17 个日夜,而你和回家只隔了一张 12306 验证码的距离,祝大家抢票顺利.本期 fir.im Weekly 收集了一些优秀的 GitHub 源码.开发工具和动画特效,希望对 ...
- fir.im Weekly - iOS开发中的Git流程
本期 fir.im Weekly 收集了微博上的热转资源,包含 Android.iOS 开发工具.源码等好用的轮子,还有一些 APP 设计的 Tips,希望对你有用. 精仿知乎日报 iOS 端 @我偏 ...
随机推荐
- TextBox各种设置
前台: <StackPanel> <TextBlock Margin=" TextWrapping="Wrap"> TextBlock with ...
- 面试官:"准备用HashMap存1w条数据,构造时传10000还会触发扩容吗?"
// 预计存入 1w 条数据,初始化赋值 10000,避免 resize. HashMap<String,String> map = new HashMap<>(10000) ...
- mp-vue实现小程序回顶操作踩坑,wx.pageScrollTo使用无效填坑
本来项目都写的差不多了,测试测着侧着就冒出了新的想法,我因为做的是问卷,因此会有用户必答题未答完的可能存在,本来市场部给的需求就是做一个弹窗就好了,她说想要做出跳回到用户未答的第一道题,好吧,既然都这 ...
- label 标签的 for 属相
我的github iSAM2016 一开始学html 标签的时候,知道有label 这个标签的,但是并没有注意到他的for 属性的作用,看一下MDN的介绍 for 可标记的 form相关元素的ID,在 ...
- go map数据结构和源码详解
目录 1. 前言 2. go map的数据结构 2.1 核心结体体 2.2 数据结构图 3. go map的常用操作 3.1 创建 3.2 插入或更新 3.3 删除 3.4 查找 3.5 range迭 ...
- SpringBoot2.X整合Actuator
一 说明 Actuator 的定义 actuator 是一个制造术语,指的是用于移动或控制某物的机械装置.执行器可以通过一个小的变化产生大量的运动. 要将 actuator 添加到基于 Maven 的 ...
- 【aliyun】学java,看这里,不迷茫!1460道Java热门问题
阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 ...
- vue学习之插槽
插槽 插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性. 个人理解:我感觉插槽就是父组件控制插槽 ...
- 原生JS实现集合结构
1. 前言 集合是由一组无序且唯一(即不能重复)的项组成的.你可以把集合想象成一个既没有重复元素,也没有顺序概念的数组.在ES6中已经内置了集合这一数据结构--Set.接下来,我们就用原生JS来实现这 ...
- 考试T1护花
传送门 这题的提议似乎有什么问题,只要约翰选好了要抓那头牛,他就不会吃草了,站在原地傻等? 这题就是贪心,但在用cmp中比较单位时间吃草数量时,要用double型,不然可能会有点一样... 还有就是主 ...