Zapisywać obrazy w NSUserDefaults?

80

Czy można zapisać obrazy NSUserDefaultsjako obiekt, a następnie pobrać je do dalszego wykorzystania?

Florik
źródło

Odpowiedzi:

84

UWAGA! JEŚLI PRACUJESZ W SYSTEMIE iOS8 / XCODE6, ZOBACZ MOJĄ AKTUALIZACJĘ PONIŻEJ

Dla tych, którzy wciąż szukają odpowiedzi tutaj jest kod "zalecanego" sposobu zapisywania obrazu w NSUserDefaults. NIE POWINIENEŚ zapisywać danych obrazu bezpośrednio w NSUserDefaults!

Zapisz dane:

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

Czytaj dane:

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

Dla iOS8 / XCODE6 Jak wspomnieliśmy w komentarzach tmr i DevC, występuje problem z xcode6 / ios8. Różnica między procesem instalacji xcode5 i xcode 6 polega na tym, że xcode6 zmienia UUID aplikacji po każdym uruchomieniu w xcode (patrz wyróżniona część w ścieżce: / var / mobile / Containers / Data / Application / B0D49CF5-8FBE-4F14-87AE-FA8C16A678B1 / Documents / image.jpg).

Istnieją więc 2 obejścia:

  1. Pomiń ten problem, ponieważ po zainstalowaniu aplikacji na prawdziwym urządzeniu nigdy nie zmienia UUID (w rzeczywistości tak, ale jest to nowa aplikacja)
  2. Zapisz ścieżkę względną do wymaganego folderu (w naszym przypadku do katalogu głównego aplikacji)

Oto szybka wersja kodu jako bonus (z drugim podejściem):

Zapisz dane:

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

Czytaj dane:

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 Took
źródło
2
kod nie działał u mnie za pierwszym razem, nie był w stanie pobrać obrazu z dysku. ścieżka do folderu dokumentu była różna w różnych uruchomieniach aplikacji. może być kwestią ścieżek bezwzględnych i względnych? kod zapisywał się do jednego * imagePath, ale w następnym uruchomieniu aplikacji potrzebował innego * imagePath, aby pobrać plik. prostym rozwiązaniem było zapisanie tylko nazwy pliku w nsuserdefaults, a przy następnym uruchomieniu aplikacji ponowne pobranie ścieżki folderu dokumentów, dołączenie nazwy pliku, a reszta kodu działała świetnie! (dziękuję Nikita Took)
tmr
Więc zasadniczo problem iOS8 / Xcode 6 jest całkowicie nieważny, jeśli działa głównie na urządzeniu?
micnguyen
Chcę tylko dodać: Używanie PNGRepresentation nie zapisuje automatycznie metadanych Orientation. Będziesz musiał to zrobić ręcznie lub po prostu użyć wersji JPEG.
micnguyen
Czy zapisywanie obrazów w tej lokalizacji wpłynie na dane iCloud - co oznacza, że ​​jeśli nie są one wygenerowane przez użytkownika, nie powinniśmy zapisywać w tej lokalizacji?
Chris Harrison,
1
@NikitaTook A co z tablicą obrazów? Jak bym to zrobił?
GJZ
138

Aby zapisać obraz w NSUserDefaults:

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

Aby pobrać obraz z NSUserDefaults:

NSData* imageData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
UIImage* image = [UIImage imageWithData:imageData];
Sympatyczny
źródło
17
Technicznie poprawne, zdecydowanie nie zalecane. NSUserDefaults wyprowadza do pliku plist, który oczywiście NIE jest miejscem dla nieprzetworzonych danych obrazu.
chris stamper
@cdstamper Czytałem, że plist jest obecnie przechowywany w formacie binarnym na Mac OSX. Jeśli jest używany między rozszerzeniem a zawierającym aplikację IPC, jest używany (plik może być używany lub nie). Więc w czym problem?
Todd
To pytanie dotyczy iPhone'a / iPada. Niezależnie od tego, dopóki dokument Apple nie zaleci inaczej, NSUserDefaults służy do preferencji małych aplikacji, a nie do przechowywania. Jak określono ( developer.apple.com/Library/mac/documentation/Cocoa/Reference/… ), „Klasa NSUserDefaults zapewnia wygodne metody uzyskiwania dostępu do typowych typów, takich jak liczby zmiennoprzecinkowe, liczby podwójne, liczby całkowite, wartości logiczne i adresy URL”
chris stamper
@cdstamper, więc jaki jest zalecany sposób przechowywania, powiedzmy, obrazu profilu aplikacji, w której użytkownik jest zalogowany? Nie chcę używać CoreData tylko do przechowywania 1 obrazu i mam ochotę uderzać w chmurę / db za każdym razem, gdy muszę pokazać, że obraz jest po prostu drogi / niepotrzebny.
AnBisw
2
@annjawn po prostu zapisz go w systemie plików i zapisz adres URL w NSUserDefaults
chris stamper
29

Choć możliwe jest, aby zapisać UIImagesię NSUserDefaults, że często nie jest zalecane, ponieważ nie jest to najbardziej skuteczny sposób na zapisywanie obrazów; bardziej wydajnym sposobem jest zapisanie obrazu w aplikacji Documents Directory.

Na potrzeby tego pytania załączam odpowiedź na Twoje pytanie wraz z bardziej efektywnym sposobem zapisania pliku UIImage.


NSUserDefaults (niezalecane)

Zapisywanie do NSUserDefaults

Ta metoda umożliwia zapisanie dowolnego pliku UIImagedo 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];
}

Tak to nazywasz:

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

Ładowanie z NSUserDefaults

Ta metoda umożliwia załadowanie dowolnego pliku UIImagez NSUserDefaults.

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

Tak to nazywasz:

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

Lepsza alternatywa

Zapisywanie do katalogu dokumentów

Metoda ta pozwala na zapisanie dowolnego UIImagedo Documents Directorywewnątrz aplikacji.

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

Tak to nazywasz:

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

Ładowanie z katalogu dokumentów

Ta metoda umożliwia załadowanie dowolnego UIImagez plików aplikacji Documents Directory.

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

    return result;
}

Tak to nazywasz:

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

Inna alternatywa

Zapisywanie UIImage w bibliotece zdjęć

Ta metoda umożliwia zapisanie dowolnego UIImagena urządzeniu Photo Libraryi jest wywoływana w następujący sposób:

UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

Zapisywanie wielu UIImage w bibliotece zdjęć

Ta metoda umożliwia zapisanie wielu plików UIImagesna urządzeniu 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);
    }
}

Tak to nazywasz:

[self saveImagesToPhotoAlbums:images];

Gdzie imagesjest twój NSArrayskład UIImages.

Fernando Cervantes
źródło
Wspaniały post! Jak dowiedzieć się, jaki katalog ustawić jako dane wejściowe inDirectory:(NSString *)directoryPath- czy możesz nazwać go jak chcesz, np. „MyAppData”?
Supertecnoboff
W tym przypadku jako argumentu użyłem ścieżki katalogu dokumentów. Możesz jednak użyć dowolnego katalogu, do którego masz dostęp w ramach piaskownicy / pakietu aplikacji.
Fernando Cervantes,
11

Dla Swift 4

Prawie próbowałem wszystkiego w tej kwestii, ale nikt nie pracuje dla mnie. i znalazłem swoje rozwiązanie. najpierw utworzyłem rozszerzenie dla UserDefaults, jak poniżej, a potem właśnie wywołałem metody get and 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)
    } 
}

aby ustawić obraz jako tło w settingsVC Użyłem kodu poniżej.

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

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

w mainVC:

let bgImage = imageDefaults.imageForKey(key: "imageDefaults")!
Bilal Şimşek
źródło
imageDefaults?)
Oleksandr
let imageDefaults = UserDefaults.standard
Bilal Şimşek
8

Dla szybkiej 2.2

Przechować:

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

Aby odzyskać:

if let imageData = NSUserDefaults.standardUserDefaults().objectForKey(kKeyImage),
            let image = UIImage(data: imageData as! NSData){
            // use your image here...
}
alicanbatur
źródło
5

Tak, technicznie możliwe, jak w

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

Ale nie jest to zalecane, ponieważ plists nie są odpowiednimi miejscami dla dużych blobów danych binarnych, zwłaszcza Prefs użytkownika. Byłoby lepiej zapisać obraz w folderze dokumentów użytkownika i przechowywać odniesienie do tego obiektu jako adres URL lub ścieżkę.

Warren Burton
źródło
4

Dla formatu Swift 3 i JPG

Zarejestruj domyślny obraz:

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

Zapisać obraz :

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

Załaduj obraz:

let imageData = UserDefaults.standard.value(forKey: "key") as! Data
let imageFromData = UIImage(data: imageData)!
Mc.Lover
źródło
3

Z dokumentacji Apple,

Klasa NSUserDefaults zapewnia wygodne metody uzyskiwania dostępu do typowych typów, takich jak liczby zmiennoprzecinkowe, liczby podwójne, liczby całkowite, wartości logiczne i adresy URL. Obiekt domyślny musi być listą właściwości, czyli instancją (lub dla kolekcji kombinacją instancji): NSData, NSString, NSNumber, NSDate, NSArray lub NSDictionary. Jeśli chcesz przechowywać dowolny inny typ obiektu, zazwyczaj powinieneś zarchiwizować go w celu utworzenia instancji NSData.

Możesz zapisać obraz w ten sposób: -

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

I przeczytaj tak: -

 NSData* imageData = [[NSUserDefaults standardUserDefaults]objectForKey:@"key_for_your_image"];
    UIImage* image = [UIImage imageWithData:imageData];
Suraj K. Thomas
źródło
2

Jest to technicznie możliwe, ale nie jest zalecane. Zamiast tego zapisz obraz na dysku. NSUserDefaults jest przeznaczone dla małych ustawień, a nie dużych plików danych binarnych.

Benjamin Mayo
źródło
1

Zapisz obraz w 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]];

Załaduj obraz z 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
źródło
1

Tak, możesz użyć. Ale ponieważ służy do przechowywania preferencji, możesz lepiej zapisywać obrazy w folderze dokumentów.

W NSUserDefaultsrazie potrzeby możesz mieć ścieżkę w pliku.

Ilanchezhian
źródło
1

Ponieważ to pytanie ma wysoki indeks wyszukiwania Google - oto odpowiedź @ NikitaTook w dzisiejszych czasach, tj. Swift 3 i 4 (z obsługą wyjątków).

Uwaga: Ta klasa jest przeznaczona wyłącznie do odczytu i zapisu obrazów w formacie JPG w systemie plików. UserdefaultsRzeczy powinny być obsługiwane poza nim.

writeFilepobiera nazwę pliku obrazu jpg (z rozszerzeniem .jpg) i UIImagesamego siebie i zwraca true, jeśli jest w stanie zapisać, lub zwraca false, jeśli nie może zapisać obrazu, w którym to momencie możesz zapisać obraz, w Userdefaultsktórym byłby planem tworzenia kopii zapasowych lub po prostu spróbuj jeszcze raz. readFileFunkcja przyjmuje w nazwie pliku obrazu i zwraca UIImage, jeśli nazwa obrazu przekazywane do tej funkcji znajduje się wówczas zwraca ten obraz innego po prostu zwraca domyślny obraz zastępczy z folderu aktywami o aplikacji (w ten sposób można uniknąć nieprzyjemnych wypadków lub innych dziwne zachowania).

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
źródło
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
źródło