¿Guardar imágenes en NSUserDefaults?

Respuestas:

84

¡ATENCIÓN! SI ESTÁ TRABAJANDO CON iOS8 / XCODE6, VER MI ACTUALIZACIÓN A CONTINUACIÓN

Para aquellos que todavía buscan una respuesta, aquí hay un código de forma "recomendable" para guardar la imagen en NSUserDefaults. ¡NO DEBE guardar datos de imágenes directamente en NSUserDefaults!

Escribir datos:

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

Leer datos:

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

Para iOS8 / XCODE6 Como tmr y DevC se mencionan en los comentarios a continuación, hay un problema con xcode6 / ios8. La diferencia entre el proceso de instalación de xcode5 y xcode 6 es que xcode6 cambia el UUID de las aplicaciones después de cada ejecución en xcode (consulte la parte resaltada en la ruta: / var / mobile / Containers / Data / Application / B0D49CF5-8FBE-4F14-87AE-FA8C16A678B1 / Documents / image.jpg).

Entonces hay 2 soluciones alternativas:

  1. Omita ese problema, ya que una vez que la aplicación se instala en un dispositivo real, nunca cambia el UUID (de hecho, lo hace, pero es una aplicación nueva)
  2. Guarde la ruta relativa a la carpeta requerida (en nuestro caso, a la raíz de la aplicación)

Aquí hay una versión rápida del código como un bono (con un segundo enfoque):

Escribir datos:

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

Leer datos:

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
}
Nikita tomó
fuente
2
el código no me funcionó la primera vez, no pude recuperar la imagen del disco. La ruta a la carpeta del documento era diferente entre las ejecuciones de la aplicación. ¿Podría ser un problema de rutas absolutas versus relativas? el código se estaba escribiendo en una * imagePath, pero en la siguiente ejecución de la aplicación, necesitaba una * imagePath diferente para obtener el archivo. La solución simple fue guardar solo el nombre de archivo en nsuserdefaults, y en la próxima ejecución de la aplicación, volver a obtener la ruta de la carpeta de documentos, agregar el nombre del archivo y el resto del código funcionó muy bien. (gracias Nikita Took)
tmr
Entonces, ¿esencialmente el problema de iOS8 / Xcode 6 es completamente nulo si se ejecuta principalmente en el dispositivo?
micnguyen
Solo quiero agregar: El uso de PNGRepresentation no guardará los metadatos de orientación automáticamente. Tendrá que hacerlo manualmente o simplemente usar la versión JPEG.
micnguyen
¿Guardar imágenes en esa ubicación afectará los datos de iCloud, lo que significa que si no son generados por el usuario no deberíamos guardar en esa ubicación?
Chris Harrison
1
@NikitaTook ¿Qué pasa con una serie de imágenes? ¿Cómo haría yo para hacer eso?
GJZ
138

Para guardar una imagen en NSUserDefaults:

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

Para recuperar una imagen de NSUserDefaults:

NSData* imageData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
UIImage* image = [UIImage imageWithData:imageData];
Hermoso
fuente
17
Técnicamente correcto, positivamente no recomendado. NSUserDefaults genera un plist, que obviamente NO es el lugar para los datos de imagen sin procesar.
chris stamper
@cdstamper Leí que un plist se almacena en formato binario estos días en mac osx. Si se usa entre una extensión y la aplicación que la contiene, se usa IPC (un archivo puede o no puede usarse en medio). ¿Entonces, cuál es el problema?
Todd
Esta pregunta se refiere a iPhone / iPad. Independientemente, hasta que el documento de Apple recomiende lo contrario, NSUserDefaults es para preferencias de aplicaciones pequeñas y no para almacenamiento. Como se especifica ( developer.apple.com/Library/mac/documentation/Cocoa/Reference/… ), "La clase NSUserDefaults proporciona métodos convenientes para acceder a tipos comunes como flotantes, dobles, enteros, booleanos y URL"
chris stamper
@cdstamper, entonces, ¿cuál es la forma recomendada de almacenar, digamos, la imagen de perfil para una aplicación en la que el usuario está conectado? No quiero usar CoreData solo para almacenar 1 imagen y tengo ganas de acceder al almacenamiento en la nube / db cada vez que necesito mostrar que la imagen es costosa / innecesaria.
AnBisw
2
@annjawn simplemente escríbalo en el sistema de archivos y almacene la URL en NSUserDefaults
chris stamper
29

Si bien es posible guardar un UIImageen NSUserDefaults, a menudo no se recomienda ya que no es la forma más eficiente de guardar imágenes; una forma más eficiente es guardar su imagen en la aplicación Documents Directory.

A los efectos de esta pregunta, he adjuntado la respuesta a su pregunta, junto con la forma más eficiente de guardar un archivo UIImage.


NSUserDefaults (no recomendado)

Guardar en NSUserDefaults

Este método le permite guardar cualquier UIImagearchivo 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];
}

Así es como lo llamas:

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

Cargando desde NSUserDefaults

Este método le permite cargar cualquier UIImagearchivo NSUserDefaults.

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

Así es como lo llamas:

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

Una mejor alternativa

Guardar en el directorio de documentos

Este método le permite guardar cualquier UIImagea la Documents Directorydentro de la aplicación.

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

Así es como lo llamas:

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

Cargando desde el directorio de documentos

Este método le permite cargar cualquier archivo UIImagede la aplicación Documents Directory.

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

    return result;
}

Así es como lo llamas:

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

Una alternativa diferente

Guardar UIImage en la biblioteca de fotos

Este método le permite guardar cualquiera UIImageen el dispositivo Photo Libraryy se llama de la siguiente manera:

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

Guardar múltiples imágenes de UII en la biblioteca de fotos

Este método le permite guardar varios archivos UIImagesen el dispositivo 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);
    }
}

Así es como lo llamas:

[self saveImagesToPhotoAlbums:images];

¿De dónde imagesestá tu NSArraycompuesto UIImages?

Fernando Cervantes
fuente
¡Buena publicación! ¿Cómo averiguas qué directorio establecer para la entrada inDirectory:(NSString *)directoryPath? ¿Podrías llamarlo como quieras, como "myAppData"?
Supertecnoboff
Bueno, en este caso, utilicé la ruta del directorio de documentos como argumento. Sin embargo, puede usar cualquier directorio al que tenga acceso dentro de la caja de arena / paquete de su aplicación.
Fernando Cervantes
11

Para Swift 4

Casi probé todo en esta pregunta, pero nadie me funcionó. y encontré mi solución. Primero creé una extensión para UserDefaults como a continuación, luego llamé a los métodos get y 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)
    } 
}

para configurar la imagen como fondo en settingsVC utilicé el siguiente código.

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

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

en mainVC:

let bgImage = imageDefaults.imageForKey(key: "imageDefaults")!
Bilal Şimşek
fuente
imageDefaults?)
Oleksandr
let imageDefaults = UserDefaults.standard
Bilal Şimşek
8

Para swift 2.2

Para almacenar:

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

Para recuperar:

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

Sí, técnicamente posible como en

[[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation(image) forKey:@"foo"];

Pero no es aconsejable porque los plists no son lugares apropiados para grandes cantidades de datos binarios, especialmente User Prefs. Sería mejor guardar la imagen en la carpeta de documentos del usuario y almacenar la referencia a ese objeto como una URL o ruta.

Warren Burton
fuente
4

Para Swift 3 y JPG formato

Registrar imagen predeterminada:

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

Guardar imagen :

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

Cargar imagen :

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

De la documentación de Apple,

La clase NSUserDefaults proporciona métodos convenientes para acceder a tipos comunes como flotantes, dobles, enteros, booleanos y URL. Un objeto predeterminado debe ser una lista de propiedades, es decir, una instancia de (o para colecciones una combinación de instancias de): NSData, NSString, NSNumber, NSDate, NSArray o NSDictionary. Si desea almacenar cualquier otro tipo de objeto, normalmente debe archivarlo para crear una instancia de NSData.

Puede guardar una imagen como esta: -

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

Y lea así: -

 NSData* imageData = [[NSUserDefaults standardUserDefaults]objectForKey:@"key_for_your_image"];
    UIImage* image = [UIImage imageWithData:imageData];
Suraj K Thomas
fuente
2

Es técnicamente posible, pero no es recomendable. En su lugar, guarde la imagen en el disco. NSUserDefaults está diseñado para configuraciones pequeñas, no grandes archivos de datos binarios.

Benjamín Mayo
fuente
1

Guardar imagen en NSUserDefault:

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

Cargar imagen de 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]];
Chetan Bhalara
fuente
1

Sí, puedes usar. Pero como es para el almacenamiento de preferencias, puede guardar mejor las imágenes en la carpeta de documentos.

Y puede tener la ruta en el NSUserDefaults, si es necesario.

Ilanchezhian
fuente
1

Dado que esta pregunta tiene un alto índice de búsqueda en Google, aquí está la respuesta de @ NikitaTook en la época actual, es decir, Swift 3 y 4 (con manejo de excepciones).

Nota: Esta clase está escrita únicamente para leer y escribir imágenes de formato JPG en el sistema de archivos. Las Userdefaultscosas deben manipularse fuera de él.

writeFiletoma el nombre de archivo de su imagen jpg (con extensión .jpg) y el UIImagemismo y devuelve verdadero si puede guardar o devuelve falso si no puede escribir la imagen, momento en el que puede almacenar la imagen en la Userdefaultsque sería su plan de respaldo o simplemente vuelva a intentarlo una vez más. La readFilefunción toma el nombre del archivo de imagen y devuelve un UIImage, si se encuentra el nombre de la imagen pasado a esta función, entonces devuelve esa imagen; de lo contrario, solo devuelve una imagen de marcador de posición predeterminada de la carpeta de activos de la aplicación (de esta manera puede evitar bloqueos desagradables u otros comportamientos extraños).

import Foundation
import UIKit

class ReadWriteFileFS{

    func writeFile(_ image: UIImage, _ imgName: String) -> Bool{
        let imageData = UIImageJPEGRepresentation(image, 1)
        let relativePath = imgName
        let path = self.documentsPathForFileName(name: relativePath)

        do {
            try imageData?.write(to: path, options: .atomic)
        } catch {
            return false
        }
        return true
    }

    func readFile(_ name: String) -> UIImage{
        let fullPath = self.documentsPathForFileName(name: name)
        var image = UIImage()

        if FileManager.default.fileExists(atPath: fullPath.path){
            image = UIImage(contentsOfFile: fullPath.path)!
        }else{
            image = UIImage(named: "user")!  //a default place holder image from apps asset folder
        }
        return image
    }
}

extension ReadWriteFileFS{
    func documentsPathForFileName(name: String) -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let path = paths[0]
        let fullPath = path.appendingPathComponent(name)
        return fullPath
    }
}
AnBisw
fuente
1

Swift 4.x
Xcode 11.x

func saveImageInUserDefault(img:UIImage, key:String) {
    UserDefaults.standard.set(img.pngData(), forKey: key)
}

func getImageFromUserDefault(key:String) -> UIImage? {
    let imageData = UserDefaults.standard.object(forKey: key) as? Data
    var image: UIImage? = nil
    if let imageData = imageData {
        image = UIImage(data: imageData)
    }
    return image
}
Abdul Karim
fuente