在NSUserDefaults中保存图片?

83

是否可以将图像保存为对象并存储在NSUserDefaults中,以供进一步使用?

13个回答

139

要将图像保存在NSUserDefaults中:

[[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation(image) forKey:key];
从NSUserDefaults检索图像的方法:
NSData* imageData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
UIImage* image = [UIImage imageWithData:imageData];

18
技术上是正确的,但绝不推荐。NSUserDefaults会输出到一个属性列表(plist)中,这显然不适合用于存储原始图像数据。 - chris stamper
@cdstamper 我看到在Mac OSX上,现在的plist是以二进制格式存储的。如果在扩展和包含应用程序之间使用IPC,则可能会使用文件(也可能不使用文件)。那么问题是什么? - Kind Contributor
这个问题涉及到iPhone/iPad。然而,在苹果的文档推荐之前,NSUserDefaults仅用于小型应用程序的首选项而非存储。正如指定的(https://developer.apple.com/Library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html),"NSUserDefaults类提供了访问常见类型(如浮点数、双精度浮点数、整数、布尔值和URL)的便捷方法"。 - chris stamper
@cdstamper 那么,在一个已登录用户的应用程序中,什么是存储头像的推荐方式?我不想只为存储一张图片而使用CoreData,并且每次需要显示图片时都要访问云存储/数据库感觉很费钱/不必要。 - Anjan Biswas
3
@annjawn 只需将其写入文件系统,并将URL存储在NSUserDefaults中。 - chris stamper

85

注意!如果您正在使用iOS8/XCODE6,请查看下面的更新

对于那些仍在寻找答案的人,以下是“可行”的代码,用于在NSUserDefaults中保存图像。 您不应该直接将图像数据保存到NSUserDefaults中!

写入数据:

// 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];

读取数据:

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

针对iOS8/XCODE6的问题 如下方评论中所述,有一个关于xcode6/ios8的问题。xcode5和xcode6安装过程的区别在于,xcode6在每次在xcode中运行后更改应用程序UUID(请参见路径中突出显示的部分:/var/mobile/Containers/Data/Application/B0D49CF5-8FBE-4F14-87AE-FA8C16A678B1/Documents/image.jpg)。

因此,有两个解决方法:

  1. 忽略该问题,因为一旦应用程序在真实设备上安装,它的UUID就不会发生变化(实际上是会变化的,但这是一个新的应用程序)
  2. 保存到所需文件夹的相对路径(在我们的情况下是应用程序根目录)

以下是使用第二种方法的Swift代码版本(额外提供的):

写入数据:

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

读取数据:

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
}

2
代码第一次对我不起作用,无法从磁盘中检索图像。应用程序运行时文档文件夹的路径不同。可能是绝对路径与相对路径的问题?代码在一个imagePath中写入,但在下一次运行应用程序时,需要一个不同的imagePath来获取文件。简单的解决方案是只在nsuserdefaults中保存文件名,并在下一次运行应用程序时重新获取文档文件夹路径,附加文件名,然后剩下的代码就可以正常工作了!(谢谢Nikita Took) - tmr
那么,如果主要在设备上运行,iOS8/Xcode 6的问题基本上就不存在了? - micnguyen
根据苹果的建议,您不应将非用户生成的内容保存到文档目录中。上面的代码只是存储图像的示例。如果您将它们保存到缓存目录中,可能会被删除一次,您需要额外的代码来处理这种情况。 - Nikita Took
@NikitaTook 谢谢。我在文档目录中创建了一个新目录,并添加了 NSURLIsExcludedFromBackupKey 资源值(如此处所述:https://developer.apple.com/library/ios/qa/qa1719/_index.html)。我需要它位于文档目录中,因为它必须高度持久化。你认为这样可以吗?或者你有其他推荐的位置吗? - Chris Harrison
1
@NikitaTook 那么图片数组怎么办?我该怎么做? - GJZ
显示剩余3条评论

32

尽管可以将UIImage保存到NSUserDefaults中,但通常不建议这样做,因为这不是保存图像最有效的方法;更有效的方法是将图像保存在应用程序的Documents目录中。

针对这个问题,我已经附上了答案,并提供了更有效的保存UIImage的方法。


NSUserDefaults(不建议使用)

保存到NSUserDefaults中

该方法允许您将任何UIImage保存到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];
}
这是你如何称呼它的方式:
[self saveImageToUserDefaults:image ofType:@"jpg" forKey:@"myImage"];
[[NSUserDefaults standardUserDefaults] synchronize];

从NSUserDefaults中读取

这种方法允许您从NSUserDefaults中加载任何UIImage

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

这是如何称呼它:

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

更好的替代方案

保存到文档目录

这种方法允许你将任何UIImage保存到应用程序内的文档目录中。

-(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);
    }
}

这就是如何称呼它:

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

从文档目录加载

该方法允许您从应用程序的文档目录加载任何UIImage

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

    return result;
}

这是如何称呼它:

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

不同的替代方案

将UIImage保存到照片库

此方法允许您将任何UIImage保存到设备的照片库中,并按以下方式调用:

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

保存多张UIImage到相册

这个方法允许您将多张UIImages保存到设备的相册中。

-(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);
    }
}

这是如何称呼它:

[self saveImagesToPhotoAlbums:images];

其中images是由UIImages组成的NSArray


很棒的帖子!你如何确定要设置哪个目录作为输入 inDirectory: (NSString *)directoryPath - 你可以随便取一个名字,比如“myAppData”吗? - Supertecnoboff
在这种情况下,我使用了文档目录路径作为参数。然而,你可以使用应用程序沙盒/捆绑包内任何你有访问权限的目录。 - Fernando Cervantes

11

针对Swift 4

我尝试了这个问题中几乎所有的方法,但没有一个适用于我。最终,我找到了解决方案。 首先我创建了一个UserDefaults的扩展,如下所示,然后只需调用get和set方法。

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

我使用下面的代码将图像设置为SettingsVC的背景。

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

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

在 mainVC 中:

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

imageDefaults? - J A S K I E R
让imageDefaults等于UserDefaults.standard。 - Bilal Şimşek

8

针对 Swift 2.2 版本

存储:

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

要检索:

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

6
是的,从技术上讲是可能的,就像这样:
[[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation(image) forKey:@"foo"];

但是不建议在plist文件中存储大量的二进制数据,特别是用户偏好设置。最好将图像保存到用户文档文件夹中,并将该对象的引用作为URL或路径存储。


5

针对 Swift 3JPG 格式:

注册默认图像:

UserDefaults.standard.register(defaults: ["key":UIImageJPEGRepresentation(image, 100)!])

保存图像:

UserDefaults.standard.set(UIImageJPEGRepresentation(image, 100), forKey: "key")

加载图片:

let imageData = UserDefaults.standard.value(forKey: "key") as! Data
let imageFromData = UIImage(data: imageData)!

3

从苹果文档中获得的信息:

NSUserDefaults类提供了方便的方法来访问常见类型,例如floats、doubles、integers、Booleans和URLs。默认对象必须是属性列表,也就是NSData、NSString、NSNumber、NSDate、NSArray或NSDictionary的实例(对于集合则是实例的组合)。如果您想存储任何其他类型的对象,则通常应将其归档以创建NSData的实例。

您可以像这样保存图像:

[[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation([UIImage imageNamed:@"yourimage.gif"])forKey:@"key_for_your_image"];

请按如下方式阅读:

 NSData* imageData = [[NSUserDefaults standardUserDefaults]objectForKey:@"key_for_your_image"];
    UIImage* image = [UIImage imageWithData:imageData];

3

从技术上讲,这是可能的,但不建议这样做。相反,应该将图像保存到磁盘中。NSUserDefaults适用于小型设置,而不是大型二进制数据文件。


1

将图像保存到 NSUserDefaults:

NSData *imageData; 
// create NSData-object from image
imageData = UIImagePNGRepresentation([dic objectForKey:[NSString stringWithFormat:@"%d",i]]); 
// save NSData-object to UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:[NSString stringWithFormat:@"%d",i]];

从 NSUserDefault 中加载图像:

NSData *imageData;
// Load NSData-object from NSUserDefault
imageData = [[NSUserDefaults standardUserDefaults] valueForKey:[NSString stringWithFormat:@"%d",i]];
// get Image from NSData
[image setObject:[UIImage imageWithData:imageData] forKey:[NSString stringWithFormat:@"%d",i]];

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