保存 uiimage 到相册
uikit
uikit 中一个古老的方法,objective-c 的形式
void uiimagewritetosavedphotosalbum(uiimage *image, id completiontarget, sel completionselector, void *contextinfo);
保存完成后,会调用 completiontarget 的 completionselector。如果 completiontarget 不为空,completiontarget 必须实现以下方法
- (void)image:(uiimage *)image didfinishsavingwitherror:(nserror *)error contextinfo:(void *)contextinfo;
objective-c 的写法
1
2
3
4
5
6
7
8
9
10
11
|
- ( void )saveimage:(uiimage *)image {
uiimagewritetosavedphotosalbum(image, self, @selector(image:didfinishsavingwitherror:contextinfo:), nil);
}
- ( void )image:(uiimage *)image didfinishsavingwitherror:(nserror *)error contextinfo:( void *)contextinfo {
if (error) {
// fail
} else {
// success
}
}
|
swift 的写法
1
2
3
4
5
6
7
8
9
10
11
|
func saveimage(_ image: uiimage) {
uiimagewritetosavedphotosalbum(image, self, #selector(image(_:didfinishsavingwitherror:contextinfo:)), nil)
}
func image(_ image: uiimage, didfinishsavingwitherror error: nserror?, contextinfo: anyobject) {
if error == nil {
// success
} else {
// fail
}
}
|
photos framework
ios 8 开始,可以用 photos framework。phassetchangerequest 的类方法可以保存 uiimage
class func creationrequestforasset(from image: uiimage) -> self
编辑相册需要在 phphotolibrary 的闭包中进行,有两种方法
func performchanges(_ changeblock: @escaping () -> void, completionhandler: ((bool, error?) -> void)? = nil)
func performchangesandwait(_ changeblock: @escaping () -> void) throws
以上两种方法,分别是异步和同步执行。一般用第一种异步执行的方法,不会阻塞主线程。
1
2
3
4
5
6
7
8
9
10
11
12
|
func saveimage(_ image: uiimage) {
phphotolibrary.shared().performchanges({
phassetchangerequest.creationrequestforasset(from: image)
}, completionhandler: { (success, error) in
// not on main thread
if success {
// success
} else if let error = error {
// handle error
}
})
}
|
编辑相册的闭包 changeblock 和完成的闭包 completionhandler,是在 serial queue 中执行,不在主线程。需要更新 ui 的话,要切换到主线程中执行。
保存图片的 data 到相册
如果有图片的数据(data 或 nsdata),可以用 photos framework 的方法保存到相册。从 ios 9 开始,可以使用 phassetcreationrequest 的方法
func addresource(with type: phassetresourcetype, data: data, options: phassetresourcecreationoptions?)
ios 8 比较麻烦,需要把数据写入临时文件,用临时文件的 url 作为参数,调用 phassetchangerequest 的类方法
class func creationrequestforassetfromimage(atfileurl fileurl: url) -> self?
以下是兼容 ios 8 的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
func saveimagedata(_ data: data) {
if #available(ios 9.0, *) {
phphotolibrary.shared().performchanges({
phassetcreationrequest.forasset().addresource(with: .photo, data: data, options: nil)
}, completionhandler: { (success, error) in
// not on main thread
if success {
// success
} else if let error = error {
// handle error
}
})
} else {
// write image data to temp file
let temppath = nstemporarydirectory().appending( "tempimagetosavetophoto.image" )
let tempurl = url(fileurlwithpath: temppath)
try ? data.write(to: tempurl)
phphotolibrary.shared().performchanges({
phassetchangerequest.creationrequestforassetfromimage(atfileurl: tempurl)
}, completionhandler: { (success, error) in
// not on main thread
if success {
// success
} else if let error = error {
// handle error
}
// remove temp file
try ? filemanager. default .removeitem(at: tempurl)
})
}
}
|
sdwebimage 缓存 uiimage、data
sdwebimage (目前版本 4.0.0) 有两个方法可以使用。
sdwebimagemanager 的方法
- (void)saveimagetocache:(nullable uiimage *)image forurl:(nullable nsurl *)url;
sdimagecache 的方法
1
2
3
4
5
|
- ( void )storeimage:(nullable uiimage *)image
imagedata:(nullable nsdata *)imagedata
forkey:(nullable nsstring *)key
todisk:( bool )todisk
completion:(nullable sdwebimagenoparamsblock)completionblock;
|
这个方法的 image、key 参数不能为空,否则直接执行 completionblock 就返回。
从相册获取 uiimage、data
uiimagepickercontroller 是常用的照片选取控制器。实现一个代理方法即可
optional func imagepickercontroller(_ picker: uiimagepickercontroller, didfinishpickingmediawithinfo info: [string : any])
通过 info 字典,可以获取 uiimage 等信息。这里用来查询 info 字典的 key 有
1
2
3
|
uiimagepickercontrolleroriginalimage // 原始 uiimage
uiimagepickercontrollereditedimage // 编辑后的 uiimage
uiimagepickercontrollerreferenceurl // alasset 的 url
|
通过 alasset 的 url 可获取 phasset。通过 phimagemanager 的方法可以获得相册图片的 data
func requestimagedata(for asset: phasset, options: phimagerequestoptions?, resulthandler: @escaping (data?, string?, uiimageorientation, [anyhashable : any]?) -> void) -> phimagerequestid
以下是代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func imagepickercontroller(_ picker: uiimagepickercontroller, didfinishpickingmediawithinfo info: [string : any]) {
picker.dismiss(animated: true , completion: nil)
if let image = info[uiimagepickercontrolleroriginalimage] as? uiimage {
// get original image
}
if let url = info[uiimagepickercontrollerreferenceurl] as? url,
let asset = phasset.fetchassets(withalasseturls: [url], options: nil).firstobject {
phimagemanager. default ().requestimagedata( for : asset, options: nil, resulthandler: { (imagedata, _, _, _) in
if let data = imagedata {
// get image data
}
})
}
}
|
从 sdwebimage 的缓存中获取 uiimage、data
sdwebimage 给 uiimageview 提供了方法,方便获取、显示网络图片。如果需要获取下载的图片(进行保存到相册、上传至服务器等操作),可以用以下方法
1
2
3
4
|
- (nullable id <sdwebimageoperation>)loadimagewithurl:(nullable nsurl *)url
options:(sdwebimageoptions)options
progress:(nullable sdwebimagedownloaderprogressblock)progressblock
completed:(nullable sdinternalcompletionblock)completedblock;
|
swift 的代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
sdwebimagemanager.shared().loadimage(with: url, options: sdwebimageoptions(rawvalue: 0), progress: nil, completed: { [weak self] (cachedimage, imagedata, error, _, _, _) in
guard self != nil else { return }
if let image = cachedimage {
// get image
}
if let data = imagedata {
// get image data
}
if error != nil {
// handle error
}
})
|
这个方法有个问题,对于静态图片,可能获取不到 data。如果需要获取图片 data 的话,不能直接这么写。查看源码可以找到原因。sdwebimagemanager 的 loadimage: 方法会调用 sdimagecache 的 querycacheoperationforkey: 方法
diskimagedatabysearchingallpathsforkey: 方法用来获取 disk 中图片的 data。当图片在 memory 中,只有 gif 图片才会提供 data,静态图的 data 为空;当图片在 disk 中,都会提供 data。如果能在外部直接调用 diskimagedatabysearchingallpathsforkey: 方法就很简单,但是不行,这是私有方法,只写在 .m 文件里,对外不可见。
改源码可以解决问题,将上图第一个箭头的 if 判断去掉,总是调用 diskimagedatabysearchingallpathsforkey: 方法。然而,改第三方库源码不好,可能会有想不到的糟糕后果。
一种方法是,根据 diskimageexistswithkey: 方法,获取 disk 上的 data。
判断 disk 的图片是否存在,就是查找两个路径。同样,拿到这两个路径的文件就可以获得 data。以下是 swift 代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
sdwebimagemanager.shared().diskimageexists( for : imageurl) { [weak self] (exist) in
// always on main thread
guard self != nil else { return }
if exist {
// find image data from disk
var data: nsdata?
// get cache key
let key = sdwebimagemanager.shared().cachekey( for : imageurl)
// get cache path
if let path = sdimagecache.shared().defaultcachepath(forkey: key) {
data = nsdata(contentsoffile: path)
if data == nil {
data = nsdata(contentsoffile: (path as nsstring).deletingpathextension)
}
}
if data != nil {
// get image data
} else {
// fail getting image data
}
} else {
// no disk image
}
}
|
这个方法缺点在于,代码复杂,可能会在 sdwebimage 版本升级后失效(例如,disk 缓存路径改变)。
推荐的方法是,将图片缓存从 memory 中移除,然后调用 sdwebimagemanager 的 loadimage: 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// get cache key
let key = sdwebimagemanager.shared().cachekey( for : imageurl)
// remove memory cache
sdimagecache.shared().removeimage(forkey: key, fromdisk: false , withcompletion: nil)
// load image and data
sdwebimagemanager.shared().loadimage(with: imageurl, options: sdwebimageoptions(rawvalue: 0), progress: nil) { [weak self] (_, data, _, _, _, _) in
guard self != nil else { return }
if data != nil {
// get image data
} else {
// fail getting image data
}
}
|
这样写比较简洁。即使 sdwebimage 版本升级后改变 disk 缓存路径,依然有效。以上代码执行之后,当前图片又会存在 memory 中。
遗留问题
将 jpg 图片的 data 保存至相册,然后再取出的 data 与保存的 data 可能不一样。requestimagedata: 方法传入 phimagerequestoptions,phimagerequestoptions 的 version 试了三种值(current、unadjusted、original)都不行。png、gif 图片还没遇到这个问题。可能保存 jpg 图片的过程会修改原始数据。如何使存取的数据一致?欢迎交流!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://www.cnblogs.com/silence-cnblogs/p/6763928.html?utm_source=tuicool&utm_medium=referral