欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-31 09:46:16
保存 uiimage 到相册 uikit uikit 中一个古老的方法,objective-c 的形式 复制代码 代码如下: void uiimagewriteto...

保存 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;

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

这个方法的 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: 方法

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

diskimagedatabysearchingallpathsforkey: 方法用来获取 disk 中图片的 data。当图片在 memory 中,只有 gif 图片才会提供 data,静态图的 data 为空;当图片在 disk 中,都会提供 data。如果能在外部直接调用 diskimagedatabysearchingallpathsforkey: 方法就很简单,但是不行,这是私有方法,只写在 .m 文件里,对外不可见。

改源码可以解决问题,将上图第一个箭头的 if 判断去掉,总是调用 diskimagedatabysearchingallpathsforkey: 方法。然而,改第三方库源码不好,可能会有想不到的糟糕后果。

一种方法是,根据 diskimageexistswithkey: 方法,获取 disk 上的 data。

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

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

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