42

I am saving an image using 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
}

New info:

Saving file does not work properly ("[-] ERROR SAVING FILE" is printed)--

            // 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")
        }

Why doesn't the saveImage function save the image? Access rights?

Older info:

The debug info says:

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

Then I retrieve this location using

fileInDocumentsDirectory("tempImage")

The result is correct.

Then I am loading the file using this path

    let image = UIImage(contentsOfFile: path)

    if image == nil {

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

The path is correct, but the message is "missing image at..". Is the file somehow inaccessible or not stored? What can be a reason for this behavior?

I am testing this code on iphone 4 with ios 7 and iphone 5 with ios 7 simulator.

Edit: 1. The fileInDocumentsDirectory function

func fileInDocumentsDirectory(filename: String) -> String {

    let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let fileURL = documentsURL.URLByAppendingPathComponent(filename).absoluteString
    return fileURL        
}
tesgoe
  • 812
  • 3
  • 9
  • 19
  • Check the image in finder, It's available or not. and add some extensions to image *.png or *.jpeg. – Ashish Kakkad May 20 '16 at 10:55
  • dont save full path of image just save the image name only and append the name with real time document directory-path. that's it because document directory path never return same with each run. – Nitin Gohel May 20 '16 at 13:12
  • @NitinGohel I just saving the file and then trying to load it like that loadImageFromPath(fileInDocumentsDirectory("abc.png")) – tesgoe May 20 '16 at 13:21
  • fineInDocumentDirectory is the realtime path or you load from database? – Nitin Gohel May 20 '16 at 13:22
  • @NitinGohel I added the code of this function. Just three lines. – tesgoe May 20 '16 at 13:24
  • simply just follow the : https://www.hackingwithswift.com/example-code/media/how-to-save-a-uiimage-to-a-file-using-uiimagepngrepresentation – Nitin Gohel May 20 '16 at 13:29
  • @NitinGohel, Unfortunately that cannot work in Swift 2.0, because stringByAppendingPathComponent is deprecated. – tesgoe May 20 '16 at 13:31
  • Be careful about where you save images and other data. Documents/ should be used for user created data. Look at this guide: http://stackoverflow.com/questions/37331529/my-app-got-rejected-due-to-not-following-ios-app-data-storage-guidelines/37331530#37331530 – Starlord May 20 '16 at 13:52
  • @Joakim Thank you. It looks like I want to use the Documents directory, because I am storing user profile images. – tesgoe May 20 '16 at 13:57

10 Answers10

62

This function will save an image in the documents folder:

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
    }
}

To use:

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

This function will get that image:

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
}

To use:

if let image = getSavedImage(named: "fileName") {
    // do something with image
}
Bobby
  • 5,406
  • 3
  • 30
  • 35
46

iOS 13+ Swift 5.1

iOS 12 introduced some API Changes.

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
}
Sam Bing
  • 2,437
  • 17
  • 27
6

Details

  • Xcode Version 10.2 (10E125), Swift 5

Solution

// 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
        }
    }
}

Usage

// 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)

Check results

open the iOS simulator directory

Vasily Bodnarchuk
  • 19,860
  • 8
  • 111
  • 113
4

You should save image name with extension so your path should be like,

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

And second thing replace below line,

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

with

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

You need to set false as parameter of atomically.

atomically:

If true, the data is written to a backup file, and then—assuming no errors occur—the backup file is renamed to the name specified by path; otherwise, the data is written directly to path.

Hope this will help :)

Community
  • 1
  • 1
Ketan Parmar
  • 25,426
  • 9
  • 43
  • 67
  • Hi, I did that, but it still does not work -- !!!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 May 20 '16 at 11:52
  • Make sure your image is not nil!! and you are passing path in `let image = UIImage(contentsOfFile: path)` is exact same as you write it and this path should be with extension. And once try to retrieve in `NSData` instead of `UIImage` from this path. If you got data then that means there is a problem to convert it in image. – Ketan Parmar May 20 '16 at 11:56
  • Btw. I am using this code http://stackoverflow.com/questions/30953070/get-image-name-uiimagepickercontroller-in-swift -- from the @dharmesh-kheni 's answer. – tesgoe May 20 '16 at 12:26
  • try to save image like this `saveImage(tempImage, path: fileInDocumentsDirectory("tempImage.png"))` from that answer. – Ketan Parmar May 20 '16 at 12:45
  • I'm doing exactly that now and it still does not work. – tesgoe May 20 '16 at 12:49
  • It should work!! strange if not work!! once try to change different name like `abc.png` instead `tempImage.png` – Ketan Parmar May 20 '16 at 12:52
  • loadImageFromPath(fileInDocumentsDirectory("abc.png")) returns null when saveImage(tempImage, path: fileInDocumentsDirectory("abc.png")) used. – tesgoe May 20 '16 at 12:54
  • This is how it looks like from my perspective !!!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 May 20 '16 at 13:00
1

Ashish's comment has a clue to the answer. If you read the docs on UIImage(contentsOfFile:) they say

path The path to the file. This path should include the filename extension that identifies the type of the image data.

The imageNamed call is smart enough to try the .png and .jpg extensions, but the contentsOfFile call expects a full path including extension.

Duncan C
  • 115,063
  • 19
  • 151
  • 241
  • I added the extension, but that hasn't helped so far. Btw. I am using this code http://stackoverflow.com/questions/30953070/get-image-name-uiimagepickercontroller-in-swift -- from the @dharmesh-kheni 's answer. – tesgoe May 20 '16 at 12:24
1

If you want to load image from server you can do like below

 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()
guru
  • 2,201
  • 21
  • 31
0

You have to create a directory in the Documents directory to be able to store a file.

tesgoe
  • 812
  • 3
  • 9
  • 19
  • That's just not true. You can certainly create a directory inside the sandboxed Documents directory and save your images there, but you don't have to do so. It works just fine to save image directly into the Documents directory. – Duncan C May 23 '16 at 10:53
  • @DuncanC Thank you. Good to know that, but it currently works for me. I had a problem without this. – tesgoe May 23 '16 at 11:11
0

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
Josh Lee
  • 149,877
  • 34
  • 253
  • 263
John Ro
  • 21
  • 1
0

I found the solution on StackOverFlow some time ago. I didn't remember the author

Assuming yourImage is UIImage()

let ciImage = yourImage!.ciImage
let context = CIContext()
let cgImage = context.createCGImage(ciImage!, from: ciImage!.extent)
let uiImage = UIImage(cgImage: cgImage!)

UIImageWriteToSavedPhotosAlbum(uiImage, self, 
#selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)

and this function

@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
    // we got back an error!
    let ac = UIAlertController(title: "Save error", message: error.localizedDescription, preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "OK", style: .default))
    present(ac, animated: true)
} else {
    let ac = UIAlertController(title: "Saved!", message: "Your altered image has been saved to your photos.", preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "OK", style: .default))
    present(ac, animated: true)
}

}

ninahadi
  • 314
  • 2
  • 8
0

You can actually use PHPhotoLibrary to do that. Here is the code for saving the image and fetching the image 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)
            })

    }
}
Gurkan Soykan
  • 61
  • 1
  • 4