在Swift(iOS)中保存图像并加载它

59

我正在使用saveImage保存一张图片。

func saveImage (image: UIImage, path: String ) -> Bool{

    let pngImageData = UIImagePNGRepresentation(image)
    //let jpgImageData = UIImageJPEGRepresentation(image, 1.0)   // if you want to save as JPEG

    print("!!!saving image at:  \(path)")

    let result = pngImageData!.writeToFile(path, atomically: true)

    return result
}

最新信息:

保存文件无法正常工作(会打印出“[-] ERROR SAVING FILE”)--

            // save your image here into Document Directory
        let res = saveImage(tempImage, path: fileInDocumentsDirectory("abc.png"))
        if(res == true){
            print ("[+] FILE SAVED")
        }else{
            print ("[-] ERROR SAVING FILE")
        }

为什么saveImage函数不能保存图像?是访问权限的问题吗?

旧信息:

调试信息如下:

!!!saving image at:  file:///var/mobile/Applications/BDB992FB-E378-4719-B7B7-E9A364EEE54B/Documents/tempImage

然后我使用以下方法检索这个位置

fileInDocumentsDirectory("tempImage")

结果是正确的。

然后,我正在使用这个路径加载文件

    let image = UIImage(contentsOfFile: path)

    if image == nil {

        print("missing image at: \(path)")
    }else{
        print("!!!IMAGE FOUND at: \(path)")
    }

路径是正确的,但是显示“缺少图片...”。这个文件是否无法访问或未存储?造成这种行为的原因是什么?

我正在使用iOS 7上的iPhone 4和iOS 7模拟器上的iPhone 5进行测试。

编辑: 1. fileInDocumentsDirectory函数。

func fileInDocumentsDirectory(filename: String) -> String {

    let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let fileURL = documentsURL.URLByAppendingPathComponent(filename).absoluteString
    return fileURL        
}

检查Finder中是否有该图像,并将一些扩展名添加到图像*.png或*.jpeg。 - Ashish Kakkad
不要保存图像的完整路径,只保存图像名称,并将名称附加到实时文档目录路径。这样做是因为每次运行时文档目录路径都不相同。 - Nitin Gohel
@NitinGohel 我只是保存文件,然后尝试像这样加载它 loadImageFromPath(fileInDocumentsDirectory("abc.png"))。 - tesgoe
fineInDocumentDirectory是实时路径还是从数据库加载的? - Nitin Gohel
@Joakim 谢谢。看起来我想要使用“文档”目录,因为我正在存储用户个人资料图片。 - tesgoe
显示剩余4条评论
11个回答

77

此函数将保存一张图片在文档文件夹中:

func saveImage(image: UIImage) -> Bool {
    guard let data = UIImageJPEGRepresentation(image, 1) ?? UIImagePNGRepresentation(image) else {
        return false
    }
    guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
        return false
    }
    do {
        try data.write(to: directory.appendingPathComponent("fileName.png")!)
        return true
    } catch {   
        print(error.localizedDescription)
        return false
    }
}

使用方法:

let success = saveImage(image: UIImage(named: "image.png")!)

这个函数将获取那张图片:

func getSavedImage(named: String) -> UIImage? {
    if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
        return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path)
    }
    return nil
}

使用方法:

if let image = getSavedImage(named: "fileName") {
    // do something with image
}

1
同样适用于Swift 4.2! :) - uplearned.com
如何处理 let success?我该如何存储或插入,或者您能否给出一个示例,在按钮单击时保存并在按下其他按钮时显示在图像视图中。 - iOS Developer

59

iOS 13+ Swift 5.1

iOS 12引入了一些API更改。

func saveImage(imageName: String, image: UIImage) {


 guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }

    let fileName = imageName
    let fileURL = documentsDirectory.appendingPathComponent(fileName)
    guard let data = image.jpegData(compressionQuality: 1) else { return }

    //Checks if file exists, removes it if so.
    if FileManager.default.fileExists(atPath: fileURL.path) {
        do {
            try FileManager.default.removeItem(atPath: fileURL.path)
            print("Removed old image") 
        } catch let removeError {
            print("couldn't remove file at path", removeError)
        }

    }

    do {
        try data.write(to: fileURL)
    } catch let error {
        print("error saving file with error", error) 
    }

}



func loadImageFromDiskWith(fileName: String) -> UIImage? {

  let documentDirectory = FileManager.SearchPathDirectory.documentDirectory

    let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
    let paths = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)

    if let dirPath = paths.first {
        let imageUrl = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
        let image = UIImage(contentsOfFile: imageUrl.path)
        return image

    }

    return nil
}

7

细节

  • Xcode 版本 10.2 (10E125),Swift 5

解决方案

// save
extension UIImage {

    func save(at directory: FileManager.SearchPathDirectory,
              pathAndImageName: String,
              createSubdirectoriesIfNeed: Bool = true,
              compressionQuality: CGFloat = 1.0)  -> URL? {
        do {
        let documentsDirectory = try FileManager.default.url(for: directory, in: .userDomainMask,
                                                             appropriateFor: nil,
                                                             create: false)
        return save(at: documentsDirectory.appendingPathComponent(pathAndImageName),
                    createSubdirectoriesIfNeed: createSubdirectoriesIfNeed,
                    compressionQuality: compressionQuality)
        } catch {
            print("-- Error: \(error)")
            return nil
        }
    }

    func save(at url: URL,
              createSubdirectoriesIfNeed: Bool = true,
              compressionQuality: CGFloat = 1.0)  -> URL? {
        do {
            if createSubdirectoriesIfNeed {
                try FileManager.default.createDirectory(at: url.deletingLastPathComponent(),
                                                        withIntermediateDirectories: true,
                                                        attributes: nil)
            }
            guard let data = jpegData(compressionQuality: compressionQuality) else { return nil }
            try data.write(to: url)
            return url
        } catch {
            print("-- Error: \(error)")
            return nil
        }
    }
}

// load from path

extension UIImage {
    convenience init?(fileURLWithPath url: URL, scale: CGFloat = 1.0) {
        do {
            let data = try Data(contentsOf: url)
            self.init(data: data, scale: scale)
        } catch {
            print("-- Error: \(error)")
            return nil
        }
    }
}

使用方法

// save image (way 1)
let path = "photo/temp/album1/img.jpg"
guard   let img = UIImage(named: "img"),
        let url = img.save(at: .documentDirectory,
                           pathAndImageName: path) else { return }
print(url)

// get image from directory
guard let img2 = UIImage(fileURLWithPath: url) else { return }

// save image (way 2)
let tempDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(path)
guard let url2 = img2.save(at: tempDirectoryUrl) else { return }
print(url2)

检查结果

打开iOS模拟器目录


4

你应该保存带有扩展名的图像名称,因此您的路径应该如下所示:

///var/mobile/Applications/BDB992FB-E378-4719-B7B7-E9A364EEE54B/Documents/tempImage.png

第二件事是替换以下行:

   let result = pngImageData!.writeToFile(path, atomically: true)

使用

    let result = pngImageData!.writeToFile(path, atomically: false)

你需要将atomically的参数设置为false。

atomically:

如果为true,则数据将被写入备份文件,然后(假设没有错误发生)备份文件将重命名为path指定的名称;否则,数据将直接写入path。

希望这可以帮助你 :)

嗨,我已经做了那个,但它仍然不起作用--!!!save: file:///var/mobile/Applications/E78F2521-929E-4E4D-834F-57C07A62DB84/Documents/tempImage.png!!!miss: file:///var/mobile/Applications/E78F2521-929E-4E4D-834F-57C07A62DB84/Documents/tempImage.png!!!miss: file:///var/mobile/Applications/E78F2521-929E-4E4D-834F-57C07A62DB84/Documents/tempImage.png - tesgoe
确保您的图像不为nil!并且在let image = UIImage(contentsOfFile: path)中传递的路径与您编写的完全相同,并且此路径应带有扩展名。一旦尝试从此路径中检索NSData而不是UIImage。如果您获得了数据,则表示将其转换为图像存在问题。 - Ketan Parmar
顺便说一下,我正在使用这段代码http://stackoverflow.com/questions/30953070/get-image-name-uiimagepickercontroller-in-swift -- 来自@dharmesh-kheni的答案。 - tesgoe
我现在正在做这件事,但它仍然不起作用。 - tesgoe
这是从我的角度看起来的样子!!!save: file:///var/mobile/Applications/AE7C3783-C25C-4C4A-8ABB-650400CB32A7/Documents/abc.png !!!miss: file:///var/mobile/Applications/AE7C3783-C25C-4C4A-8ABB-650400CB32A7/Documents/abc.png - tesgoe
显示剩余3条评论

3

将图片保存在本地Xcode文档目录中

将您的图像和您想要命名的名称(您选择fileName)传递。

func saveImageLocally(image: UIImage, fileName: String) {
    
 // Obtaining the Location of the Documents Directory
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    
    // Creating a URL to the name of your file
    let url = documentsDirectory.appendingPathComponent(fileName)
    
    if let data = image.pngData() {
        do {
            try data.write(to: url) // Writing an Image in the Documents Directory
        } catch {
            print("Unable to Write \(fileName) Image Data to Disk")
        }
    }
}

阅读

使用与保存时相同的文件名

func getImageFromName(fileName: String) {
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let url = documentsDirectory.appendingPathComponent(fileName)
    
    if let imageData = try? Data(contentsOf: url) {
        let image = UIImage(data: imageData) // HERE IS YOUR IMAGE! Do what you want with it!
        
    } else {
        print("Couldn't get image for \(fileName)")
    }
}

1
Ashish的评论提供了一个答案线索。如果您阅读有关UIImage(contentsOfFile:)的文档,它们会说:
路径-文件的路径。此路径应包括标识图像数据类型的文件名扩展名。 imageNamed调用足够聪明,可以尝试.png和.jpg扩展名,但contentsOfFile调用需要包括扩展名在内的完整路径。

我已经添加了扩展名,但是目前还没有帮助。顺便说一下,我正在使用@dharmesh-kheni的答案中提供的代码http://stackoverflow.com/questions/30953070/get-image-name-uiimagepickercontroller-in-swift。 - tesgoe

1
如果您想从服务器加载图像,可以按照以下方式进行。
 let url = URL(string: "http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg")
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil
                else { return }
                DispatchQueue.main.async() { () -> Void in
                let fileManager = FileManager.default
                let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("apple.jpg")
                print(paths)
                fileManager.createFile(atPath: paths as String, contents: data, attributes: nil)

            }}.resume()

他的意思是从本地加载和检索数据。 - ayan chakraborty

1

Swift 5

func saveImage(image: UIImage) -> Bool{
    guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
        return false
    }
    guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
        return false
    }
    do{
        try data.write(to: directory.appendingPathComponent("\(txtNom.text!).png")!)
        print(directory)
        print(data)
        print("si se pudo")
        return true
    } catch {
        print(error.localizedDescription)
        return false
    }
} // saveImage

4
你应该在你的回答中添加评论或描述正在发生的事情。 - Michael

1
你可以使用PHPhotoLibrary来实现这个功能。以下是保存图片和获取图片url的代码。
extension UIImage {
func saveToPhotoLibrary(completion: @escaping (URL?) -> Void) {
    var localeId: String?
    PHPhotoLibrary.shared().performChanges({
        let request = PHAssetChangeRequest.creationRequestForAsset(from: self)
        localeId = request.placeholderForCreatedAsset?.localIdentifier
    }) { (isSaved, error) in
        guard isSaved else {
            debugPrint(error?.localizedDescription)
            completion(nil)
            return
        }
        guard let localeId = localeId else {
            completion(nil)
            return
        }
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        let result = PHAsset.fetchAssets(withLocalIdentifiers: [localeId], options: fetchOptions)
        guard let asset = result.firstObject else {
            completion(nil)
            return
        }
        getPHAssetURL(of: asset) { (phAssetUrl) in
            completion(phAssetUrl)
        }
    }
}

static func getPHAssetURL(of asset: PHAsset, completionHandler : @escaping ((_ responseURL : URL?) -> Void))
    {
            let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
            options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
                return true
            }
            asset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
                completionHandler(contentEditingInput!.fullSizeImageURL)
            })

    }
}

0

你需要在“文档”目录中创建一个文件夹,才能存储文件。


这并不是真的。你可以在沙盒化的文档目录中创建一个目录并将图像保存在那里,但你不必这样做。直接将图像保存到文档目录中也可以正常工作。 - Duncan C
@DuncanC 谢谢。很高兴知道这个,但目前对我来说它是有效的。我之前遇到了一个问题,没有这个功能。 - tesgoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接