Save images in NSUserDefaults?

66,457

Solution 1

ATTENTION! IF YOU'RE WORKING UNDER iOS8/XCODE6 SEE MY UPDATE BELOW

For those who still looking for answer here is code of "advisable" way to save image in NSUserDefaults. You SHOULD NOT save image data directly into NSUserDefaults!

Write data:

// Get image data. Here you can use UIImagePNGRepresentation if you need transparency
NSData *imageData = UIImageJPEGRepresentation(image, 1);

// Get image path in user's folder and store file with name image_CurrentTimestamp.jpg (see documentsPathForFileName below)
NSString *imagePath = [self documentsPathForFileName:[NSString stringWithFormat:@"image_%f.jpg", [NSDate timeIntervalSinceReferenceDate]]];

// Write image data to user's folder
[imageData writeToFile:imagePath atomically:YES];

// Store path in NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:imagePath forKey:kPLDefaultsAvatarUrl];

// Sync user defaults
[[NSUserDefaults standardUserDefaults] synchronize];

Read data:

NSString *imagePath = [[NSUserDefaults standardUserDefaults] objectForKey:kPLDefaultsAvatarUrl];
if (imagePath) {
    self.avatarImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imagePath]];
}

documentsPathForFileName:

- (NSString *)documentsPathForFileName:(NSString *)name {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];

    return [documentsPath stringByAppendingPathComponent:name];
}

For iOS8/XCODE6 As tmr and DevC mentioned in comments below there is a problem with xcode6/ios8. The difference between xcode5 and xcode 6 installation process is that xcode6 changes apps UUID after each run in xcode (see hightlighted part in path: /var/mobile/Containers/Data/Application/B0D49CF5-8FBE-4F14-87AE-FA8C16A678B1/Documents/image.jpg).

So there are 2 workarounds:

  1. Skip that problem, as once app installed on real device it's never changes UUID (in fact it does, but it is new app)
  2. Save relative path to required folder (in our case to app's root)

Here is swift version of code as a bonus (with 2nd approach):

Write data:

let imageData = UIImageJPEGRepresentation(image, 1)
let relativePath = "image_\(NSDate.timeIntervalSinceReferenceDate()).jpg"
let path = self.documentsPathForFileName(relativePath)
imageData.writeToFile(path, atomically: true)
NSUserDefaults.standardUserDefaults().setObject(relativePath, forKey: "path")
NSUserDefaults.standardUserDefaults().synchronize()

Read data:

let possibleOldImagePath = NSUserDefaults.standardUserDefaults().objectForKey("path") as String?
if let oldImagePath = possibleOldImagePath {
    let oldFullPath = self.documentsPathForFileName(oldImagePath)
    let oldImageData = NSData(contentsOfFile: oldFullPath)
    // here is your saved image:
    let oldImage = UIImage(data: oldImageData)
}

documentsPathForFileName:

func documentsPathForFileName(name: String) -> String {
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true);
    let path = paths[0] as String;
    let fullPath = path.stringByAppendingPathComponent(name)

    return fullPath
}

Solution 2

To save an image in NSUserDefaults:

[[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation(image) forKey:key];

To retrieve an image from NSUserDefaults:

NSData* imageData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
UIImage* image = [UIImage imageWithData:imageData];

Solution 3

While it is possible to save a UIImage to NSUserDefaults, it is often not recommended as it is not the most efficient way to save images; a more efficient way is to save your image in the application's Documents Directory.

For the purpose of this question, I have attached the answer to your question, along with the more efficient way of saving a UIImage.


NSUserDefaults (Not Recommended)

Saving to NSUserDefaults

This method allows you to save any UIImage to NSUserDefaults.

-(void)saveImageToUserDefaults:(UIImage *)image ofType:(NSString *)extension forKey:(NSString *)key {
    NSData * data;

    if ([[extension lowercaseString] isEqualToString:@"png"]) {
        data = UIImagePNGRepresentation(image);
    } else if ([[extension lowercaseString] isEqualToString:@"jpg"]) {
        data = UIImageJPEGRepresentation(image, 1.0);
    }

    NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:data forKey:key];
    [userDefaults synchronize];
}

This is how you call it:

[self saveImageToUserDefaults:image ofType:@"jpg" forKey:@"myImage"];
[[NSUserDefaults standardUserDefaults] synchronize];

Loading From NSUserDefaults

This method allows you to load any UIImage from NSUserDefaults.

-(UIImage *)loadImageFromUserDefaultsForKey:(NSString *)key {
    NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
    return [UIImage imageWithData:[userDefaults objectForKey:key]];
}

This is how you call it:

UIImage * image = [self loadImageFromUserDefaultsForKey:@"myImage"];

A Better Alternative

Saving to Documents Directory

This method allows you to save any UIImage to the Documents Directory within the app.

-(void)saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
    if ([[extension lowercaseString] isEqualToString:@"png"]) {
        [UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", imageName, @"png"]] options:NSAtomicWrite error:nil];
    } else if ([[extension lowercaseString] isEqualToString:@"jpg"] || [[extension lowercaseString] isEqualToString:@"jpeg"]) {
        [UIImageJPEGRepresentation(image, 1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", imageName, @"jpg"]] options:NSAtomicWrite error:nil];
    } else {
        NSLog(@"Image Save Failed\nExtension: (%@) is not recognized, use (PNG/JPG)", extension);
    }
}

This is how you call it:

NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[self saveImage:image withFileName:@"Ball" ofType:@"jpg" inDirectory:documentsDirectory];

Loading From Documents Directory

This method allows you to load any UIImage from the application's Documents Directory.

-(UIImage *)loadImageWithFileName:(NSString *)fileName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
    UIImage * result = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@.%@", directoryPath, fileName, [extension lowercaseString]]];

    return result;
}

This is how you call it:

NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
UIImage * image = [self loadImageWithFileName:@"Ball" ofType:@"jpg" inDirectory:documentsDirectory];

A Different Alternative

Saving UIImage to Photo Library

This method allows you to save any UIImage to the device's Photo Library, and is called as follows:

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

Saving multiple UIImages to Photo Library

This method allows you to save multiple UIImages to the device's Photo Library.

-(void)saveImagesToPhotoAlbums:(NSArray *)images {
    for (int x = 0; x < [images count]; x++) {
        UIImage * image = [images objectAtIndex:x];

        if (image != nil) UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
    }
}

This is how you call it:

[self saveImagesToPhotoAlbums:images];

Where images is your NSArray composed of UIImages.

Solution 4

For Swift 4

I almost tried everything in this question but no one is worked for me. and I found my solution. first I created an extension for UserDefaults like below, then just called get and set methods.

extension UserDefaults {
    func imageForKey(key: String) -> UIImage? {
        var image: UIImage?
        if let imageData = data(forKey: key) {
            image = NSKeyedUnarchiver.unarchiveObject(with: imageData) as? UIImage
        }
        return image
    }
    func setImage(image: UIImage?, forKey key: String) {
        var imageData: NSData?
        if let image = image {
            imageData = NSKeyedArchiver.archivedData(withRootObject: image) as NSData?
        }
        set(imageData, forKey: key)
    } 
}

to set image as background in settingsVC I used code below.

let croppedImage = cropImage(selectedImage, toRect: rect, viewWidth: self.view.bounds.size.width, viewHeight: self.view.bounds.size.width)

imageDefaults.setImage(image: croppedImage, forKey: "imageDefaults")

in mainVC :

let bgImage = imageDefaults.imageForKey(key: "imageDefaults")!

Solution 5

For swift 2.2

To store:

NSUserDefaults.standardUserDefaults().setObject(UIImagePNGRepresentation(chosenImage), forKey: kKeyImage)

To retrieve:

if let imageData = NSUserDefaults.standardUserDefaults().objectForKey(kKeyImage),
            let image = UIImage(data: imageData as! NSData){
            // use your image here...
}
Share:
66,457

Related videos on Youtube

Florik
Author by

Florik

Updated on February 12, 2021

Comments

  • Florik
    Florik about 3 years

    Is it possible to save images into NSUserDefaults as an object and then retrieve for further use?

  • chris stamper
    chris stamper about 10 years
    Technically correct, positively not recommended. NSUserDefaults outputs to a plist, which is obviously NOT the place for raw image data.
  • tmr
    tmr over 9 years
    code did not work for me the first time, was not able to retrieve the image from disk. path to document folder was different between app runs. could be an issue of absolute versus relative paths? the code was writing to one *imagePath, but in next run of app, needed a different *imagePath to get the file. simple solution was to save only filename in nsuserdefaults, and on next app run, re-get documents folder path, append file name, and rest of code worked great! (thank you Nikita Took)
  • micnguyen
    micnguyen over 9 years
    So essentially the iOS8/Xcode 6 problem is completely void if primarily running on device?
  • micnguyen
    micnguyen over 9 years
    I just want to add: Using PNGRepresentation will not save Orientation metadata automatically. You will have to do that manually, or just use the JPEG version.
  • Kind Contributor
    Kind Contributor over 9 years
    @cdstamper I read that a plist is stored in binary format these days on mac osx. If used between an extension and containing app IPC is used (a file may or may not be used in between). So what's the problem?
  • chris stamper
    chris stamper over 9 years
    This question addresses iPhone/iPad. Regardless, until Apple's doc recommend otherwise, NSUserDefaults is for small application preferences and not storage. As specified (developer.apple.com/Library/mac/documentation/Cocoa/Referen‌​ce/…), "The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs"
  • Chris Harrison
    Chris Harrison about 9 years
    Will saving images to that location affect the iCloud data - meaning if it's not user generated we shouldn't be saving to that location?
  • Nikita Took
    Nikita Took about 9 years
    @ChrisHarrison according to Apple recommendations you should not save not-user-generated content to document directory. The code above is just an example of storing images. If you save them to cache directory it could be once deleted and you need additional code to handle that situation
  • Chris Harrison
    Chris Harrison about 9 years
    @NikitaTook Thanks. I created a new directory inside the documents directory and added the NSURLIsExcludedFromBackupKey resource value (as explained here: developer.apple.com/library/ios/qa/qa1719/_index.html). I needed it to be in the documents directory because it has to be highly persistent. Do you think it's OK? Or is there another location you'd recommend?
  • GJZ
    GJZ about 8 years
    @NikitaTook What about an array of images? How would I go about doing that?
  • Supertecnoboff
    Supertecnoboff almost 8 years
    Great post! How do you figure out what directory to set for the input inDirectory:(NSString *)directoryPath - could you just call it whatever you want like "myAppData"??
  • Fernando Cervantes
    Fernando Cervantes almost 8 years
    Well in this case, I used the Documents Directory path as the argument. However, you could use any directory you have access to within your app's sandbox/bundle.
  • AnBisw
    AnBisw over 6 years
    @cdstamper so what's the recommended way of storing, lets say, profile image for an app where the user is logged in? I don't want to use CoreData just for storing 1 image and I feel like hitting the cloud storage/db everytime i need to show the image is just expensive/un-necessary.
  • chris stamper
    chris stamper over 6 years
    @annjawn just write it to the filesystem, and store the URL in NSUserDefaults
  • Albert Renshaw
    Albert Renshaw almost 6 years
    Note: File Names may not be able to exceed 255 characters depending on OS. Not an issue for the specific script that was provided, but can be an issue for people who modify it (like me).
  • J A S K I E R
    J A S K I E R over 4 years
    imageDefaults?)
  • Bilal Şimşek
    Bilal Şimşek over 4 years
    let imageDefaults = UserDefaults.standard