How to save data using NSCoding - ios

I got Score.swift and ScoreManager.swift.
My Score.swift looks like this:
class Score: NSObject, NSCoding {
let score:Int;
let dateOfScore:NSDate;
init(score:Int, dateOfScore:NSDate) {
self.score = score;
self.dateOfScore = dateOfScore;
}
required init(coder: NSCoder) {
self.score = coder.decodeObjectForKey("score") as! Int;
self.dateOfScore = coder.decodeObjectForKey("dateOfScore") as! NSDate;
super.init()
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.score, forKey: "score")
coder.encodeObject(self.dateOfScore, forKey: "dateOfScore")
}
}
My ScoreManager.swift looks like this:
class ScoreManager {
var scores:Array<Score> = [];
init() {
// load existing high scores or set up an empty array
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let path = documentsPath.stringByAppendingPathComponent("Scores.plist")
let fileManager = NSFileManager.defaultManager()
// check if file exists
if !fileManager.fileExistsAtPath(path) {
// create an empty file if it doesn't exist
if let bundle = NSBundle.mainBundle().pathForResource("Scores", ofType: "plist") {
do {
try fileManager.copyItemAtPath(bundle, toPath: path)
} catch {
}
}
}
if let rawData = NSData(contentsOfFile: path) {
// do we get serialized data back from the attempted path?
// if so, unarchive it into an AnyObject, and then convert to an array of Scores, if possible
let scoreArray: AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(rawData);
self.scores = scoreArray as? [Score] ?? [];
}
}
func save() {
// find the save directory our app has permission to use, and save the serialized version of self.scores - the Scores array.
let saveData = NSKeyedArchiver.archivedDataWithRootObject(self.scores);
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
let documentsDirectory = paths.objectAtIndex(0) as! NSString;
let path = documentsDirectory.stringByAppendingPathComponent("Scores.plist");
saveData.writeToFile(path, atomically: true);
}
// a simple function to add a new high score, to be called from your game logic
// note that this doesn't sort or filter the scores in any way
func addNewScore(newScore:Int) {
let newScore = Score(score: newScore, dateOfScore: NSDate());
self.scores.append(newScore);
self.save();
}
}
My question is this:
How do I call these NSCoding stuff to save data from the actual gameView scene?

I strongly urged you to read a book or at least a tutorial on iOS programming. But here's the low down of it.
class ViewController: UIViewController {
let scoreManager = ScoreManager()
// Save the score to file. Hook it up to a button or label in your view
#IBAction func save (sender: AnyObject) {
scoreManager.save()
}
}
If you don't know how to connect a button/label to an #IBAction, please find a tutorial on Google.

Related

swift: Load multiple files from documents directory

I have folder with many files in documents directory. In my folder I have files with different extensions (jpg, png, mp3, zip). In my code I have stringArray. I want to load all files in someArray and after that add files with .png extension from someArray in stringArray How to do it?
Is it possible to do this? Or I should find another way to load multiple files from documents directory?
I find answer for Load multiple images from the folder or directory. - Swift 4
I tried to use this code from answer:
func loadImagesFromAlbum(folderName:String) -> [String]{
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
var theItems = [String]()
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(folderName)
do {
theItems = try FileManager.default.contentsOfDirectory(atPath: imageURL.path)
return theItems
} catch let error as NSError {
print(error.localizedDescription)
return theItems
}
}
return theItems
}
But I can't get files from theItems. Because theItems it a string array. What I do wrong? How to get files from loadImagesFromAlbum?
I tried to use this code:
images = loadImagesFromAlbum(folderName: "/folder1")
But it is not help me. I get only names. But I need to get files.
Update
I use this code to load image from documents directory:
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent("/folder1/1.png")
myImageView.image = UIImage(contentsOfFile: imageURL.path)
}
But I need to load 100 images from /folder1 in var images = [String](). Because I use var images = [String]() to show images: contentVC.imageName = images[index] // and etc...
And the code I use above is not very convenient if I need add 40-100 images.
Update 1
I have this code to load images from my project and show it:
class PageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for page in 1...pageNumber[indexPage] {
images.append("page\(page).png")
}
}
func getContentViewController(withIndex index: Int) -> ContentViewController? {
if index < images.count{
let contentVC = self.storyboard?.instantiateViewController(withIdentifier: "ContentViewController") as! ContentViewController
contentVC.itemIndex = index
contentVC.imageName = images[index]
return contentVC
}
return nil
}
}
import UIKit
class ContentViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var itemIndex: Int = 0
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
if let currentImage = imageName{
imageView.image = UIImage(named: currentImage)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
But I need to load images from documents directory and show it. How to do it?
If I understand your question correctly you want to load an image for a specific name in a specific folder in the Documents folder.
Please try this, the method takes two parameters, the file name (with extension!) and the folder name and returns an UIImage or nil if the image cannot be found or created.
func loadImage(withName name : String, from folderName: String) -> UIImage? {
do {
let documentsFolderURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let imageURL = documentsFolderURL.appendingPathComponent(folderName).appendingPathComponent(name)
let data = try Data(contentsOf: imageURL)
return UIImage(data: data)
} catch {
return nil
}
}
There is no benefit to get all file paths with contentsOfDirectory because you have to load the image data one by one anyway.

Save and read arrays Image into UserDefaults swift

I want to save images in an array using UserDefaults in Swift 3
var arrayImage = [UIImage]()
arrayImage.append(UIImage(data: dataImage as! Data)!) //add image to array
globalD.set(arrayImage, forKey: "FavoritesContactStandardImage")
I'm doing it that way, but it does not save, how can I save it and then read it?
Maybe you could try these. You just use saveImages & loadImages functions.
But you need to implement delete function by yourself.
let key = "FavoritesContactStandardImage"
func getImageKey(_ index:Int) -> String {
return "\(key)\(index)"
}
func saveImages(_ images:[UIImage]) {
var list = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
var index = list.count
for image in images {
let imgKey = getImageKey(index)
saveImage(imgKey, image)
list.append(imgKey)
UserDefaults.standard.set(list, forKey: key)
UserDefaults.standard.synchronize()
index += 1
}
}
func saveImage(_ imageName:String, _ image:UIImage) {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let imgPath = URL(fileURLWithPath: path.appendingPathComponent(imageName))
do {
try UIImagePNGRepresentation(image)?.write(to: imgPath, options: .atomic)
} catch let error {
print(error.localizedDescription)
}
}
func loadImages() -> [UIImage] {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let list = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
var imageList = [UIImage]()
for (index, _) in list.enumerated() {
let imageName = getImageKey(index)
let imgPath = URL(fileURLWithPath: path.appendingPathComponent(imageName))
if let image = UIImage(contentsOfFile: imgPath.path) {
imageList.append(image)
}
}
return imageList
}
Hope that's helpful.
That's a really bad idea to save image data on UserDefault , maybe better if you save the image name (if you put your image on assets catalog) / image path (if your put your image on custom directory on your project ). And if you want to save the downloaded image from services maybe you can save your downloaded image to a directory before you get the paath and save to UserDefault 😉

Swift: NSCoding Implementation For An Object Without Instance Variables

Case in point: Beginner here please bear with me. I just learnt how to persist objects by encoding/decoding & archiving/unarchiving. Problem is now I want to persist a single UIImage. What is the recommended way to do that?
My current implementation that works but looks really weird:
class Photo: NSObject, NSCoding {
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask).first!
static let archiveURL = documentsDirectory.appendingPathComponent("photo")
static func saveToDisk(selectedPhoto: UIImage) {
NSKeyedArchiver.archiveRootObject(selectedPhoto, toFile: archiveURL.path)
}
static func loadFromDisk() -> UIImage? {
guard let unarchivedPhoto = NSKeyedUnarchiver.unarchiveObject(withFile: archiveURL.path) as? UIImage
else {return nil}
return unarchivedPhoto
}
func encode(with aCoder: NSCoder) {
}
convenience required init?(coder aDecoder: NSCoder) {
self.init()
}
}
Is there a better way to do it? Many thanks.
Hope this helps
Please add correct file url in place of "filename"
func convertImageToDataAndWrite()
{
let img:UIImage = UIImage(named: "img1.png")!
let data:NSData? = UIImageJPEGRepresentation(img, 0.7)
if data != nil
{
var check:Bool = NSKeyedArchiver.archiveRootObject(data!, toFile: "filename")
}
}
func readImagefromFile()
{
let data:NSData = NSKeyedUnarchiver.unarchiveObjectWithFile("filename") as! NSData!
var img:UIImage = UIImage(data: data)!
}
Thanks.
I completely misunderstood the use of NSCoding and Archive. "Archives provide a means to convert objects and values into an architecture-independent stream of bytes that preserves the identity of and the relationships between the objects and values."
So to save/load a UIImage simply:
// Define path
let documentsDirectory = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask).first!
let photoURL = documentsDirectory.appendingPathComponent("photo.jpg")
// Convert selectedPhoto to Data and write to path
if let data = UIImageJPEGRepresentation(selectedPhoto, 1) {
try? data.write(to: photoURL)
}
// Load Data and init UIImage
UIImage(contentsOfFile: photoURL.path)

Swift 3: cannot write data to plist file

I am trying to use a file called Data.plist to store some simple unstructured data, and I placed this file at the root folder of my app. To make it simple to read/write to this file, I created the following DataManager struct. It can read Data.plist file with no problem, but it cannot write data to the file. I am not sure where the problem is, could anyone spot where might be wrong?
struct DataManager {
static var shared = DataManager()
var dataFilePath: String? {
return Bundle.main.path(forResource: "Data", ofType: "plist")
}
var dict: NSMutableDictionary? {
guard let filePath = self.dataFilePath else { return nil }
return NSMutableDictionary(contentsOfFile: filePath)
}
let fileManager = FileManager.default
fileprivate init() {
guard let path = dataFilePath else { return }
guard fileManager.fileExists(atPath: path) else {
fileManager.createFile(atPath: path, contents: nil, attributes: nil) // create the file
print("created Data.plist file successfully")
return
}
}
func save(_ value: Any, for key: String) -> Bool {
guard let dict = dict else { return false }
dict.setObject(value, forKey: key as NSCopying)
dict.write(toFile: dataFilePath!, atomically: true)
// confirm
let resultDict = NSMutableDictionary(contentsOfFile: dataFilePath!)
print("saving, dict: \(resultDict)") // I can see this is working
return true
}
func delete(key: String) -> Bool {
guard let dict = dict else { return false }
dict.removeObject(forKey: key)
return true
}
func retrieve(for key: String) -> Any? {
guard let dict = dict else { return false }
return dict.object(forKey: key)
}
}
You cannot modify the files inside your app bundle. So all the files that you get with Bundle.main.path(forResource:ofType:) are readable but not writable.
If you want to modify this file you will need to copy it inside your app's document directory first.
let initialFileURL = URL(fileURLWithPath: Bundle.main.path(forResource: "Data", ofType: "plist")!)
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
let writableFileURL = documentDirectoryURL.appendingPathComponent("Data.plist", isDirectory: false)
do {
try FileManager.default.copyItem(at: initialFileURL, to: writableFileURL)
} catch {
print("Copying file failed with error : \(error)")
}
// You can modify the file at writableFileURL

Cannot retrieve correctly saved NSKeyArchived Object in Swift3

I cannot retrieve an object saved as a NSkeyed archive in Swift 3, and am scracthcing my head. The object is successfully saved as a plist, but returned as nil when loading back in.
Here is the code I use:
The class itself to be saved as an object is fairly easy:
import Foundation
class ItemList:NSObject, NSCoding {
var name: String = "" //Name of the Item list
var contents: [Int] = [] //Ints referencing the CoreData PackItems
init (listname:String, ContentItems:[Int]) {
self.name=listname
self.contents=ContentItems
}
//MARK: NSCoding
public convenience required init?(coder aDecoder: NSCoder) {
let thename = aDecoder.decodeObject(forKey: "name") as! String
let thecontents = aDecoder.decodeObject(forKey: "contents") as! [Int]
self.init(listname: thename,ContentItems: thecontents)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name,forKey:"name")
aCoder.encode(self.contents, forKey: "contents")
}
}
The code to load and save the object:
class FileHandler: NSObject {
class func getDocumentsDirectory() -> URL {
let filemgr = FileManager.default
let urls = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let result:URL = urls.first!
return result
}
///This returns the contents of the handed file inside the Documents directory as the object it was saved as.
class func getFileAsObject(filename:String) -> AnyObject? {
let path = getDocumentsDirectory().appendingPathComponent(filename)
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.absoluteString) {
//Success
print("Loaded file '"+filename+"' from storage")
return result as AnyObject?
} else {
print("Error: Couldn't find requested object '"+filename+"' in storage at "+path.absoluteString)
return nil
}
}
///This saves the handed object under the given filename in the App's Documents directory.
class func saveObjectAsFile(filename:String, Object:AnyObject) {
let data = NSKeyedArchiver.archivedData(withRootObject: Object)
let fullPath = getDocumentsDirectory().appendingPathComponent(filename)
do {
try data.write(to: fullPath)
print("Wrote file '"+filename+"' to storage at "+fullPath.absoluteString)
} catch {
print("Error: Couldn't write file '"+filename+"' to storage")
}
}
}
...and finally, this is what I do to call it all up:
let testobject:ItemList = ItemList.init(listname: "testlist", ContentItems: [0,0,1,2])
FileHandler.saveObjectAsFile(filename:"Test.plist",Object:testobject)
let tobi = FileHandler.getFileAsObject(filename:"Test.plist") as! ItemList
Alas, I get this as output:
Wrote file 'Test.plist' to storage at file:///…/data/Containers/Data/Application/6747B038-B0F7-4B77-85A8-9EA02BC574FE/Documents/Test.plist
Error: Couldn't find requested object 'Test.plist' in storage at file:///…/data/Containers/Data/Application/6747B038-B0F7-4B77-85A8-9EA02BC574FE/Documents/Test.plist
Note that this is my own output -- so I do (and have checked) that the file was created correctly. But it just won't load. Can anyone tell me what I am doing wrong?
The problem is with the path you pass to unarchiveObject(withFile:).
Change:
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.absoluteString) {
to:
if let result = NSKeyedUnarchiver.unarchiveObject(withFile: path.path) {
On a side note, you should use symmetric APIs for your writing and reading logic. When you write the data you archive a root object to a Data object and then write the Data object to a file. But when reading, you directly unarchive the object tree given a file path.
Either change your writing code to use archiveRootObject(_:toFile:) or change the reading code to load the Data from a file and then unarchive the data. Your current code works (once you fix the path issue) but it's not consistent.

Resources