当前位置: 移动技术网 > 移动技术>移动开发>IOS > iOS实现相册和网络图片的存取

iOS实现相册和网络图片的存取

2019年07月24日  | 移动技术网移动技术  | 我要评论

保存 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 的写法

- (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 的写法

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

以上两种方法,分别是异步和同步执行。一般用第一种异步执行的方法,不会阻塞主线程。

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 的写法

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 的方法

- (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 有

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

以下是代码示例

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 提供了方法,方便获取、显示网络图片。如果需要获取下载的图片(进行保存到相册、上传至服务器等操作),可以用以下方法

- (nullable id <sdwebimageoperation>)loadimagewithurl:(nullable nsurl *)url
                       options:(sdwebimageoptions)options
                       progress:(nullable sdwebimagedownloaderprogressblock)progressblock
                      completed:(nullable sdinternalcompletionblock)completedblock;

swift 的代码示例

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 代码示例

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: 方法。

// 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 图片的过程会修改原始数据。如何使存取的数据一致?欢迎交流!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网