When i'm trying to run the int(coder:) it shows this error, i don't know why ?? "Non failable initializer requirement init(coder:) cannot be satisfied by a failable initalizer ('init?')"
class Note: NSObject, NSCoding {
var name: String
var photo: UIImage?
var rating: Int
static let DocumentsDirectory: AnyObject = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("notes")
struct PropertyKey {
static let nameKey = "name"
static let photoKey = "photo"
static let ratingKey = "rating"
}
init?(name: String, photo: UIImage?, rating: Int) {
self.name = name
self.photo = photo
self.rating = rating
super.init()
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || rating < 0 {
return nil
}
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(photo, forKey: PropertyKey.photoKey)
aCoder.encodeInteger(rating, forKey: PropertyKey.ratingKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
// Because photo is an optional property of Meal, use conditional cast.
let photo = aDecoder.decodeObjectForKey(PropertyKey.photoKey) as? UIImage
let rating = aDecoder.decodeIntegerForKey(PropertyKey.ratingKey)
// Must call designated initializer.
self.init(name: name, photo: photo, rating: rating)
}
}
i'm using xcode 6 , and by the way when this code runs in xcode 7 it do not show any errors , what is the reason ?
So I found a way to make it work, the 'init(coder:)' method can't be failable so what I did was override the 'init()' method to be called by the 'init(coder:)' method because it needs to call 'self.init()'. So here is the code:
class Meal: NSObject, NSCoding {
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("meals")
// MARK: Properties
var name: String
var rating: Int
var photo: UIImage?
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let photoKey = "photo"
static let ratingKey = "rating"
}
// MARK: Initialization
init? (name: String, rating: Int, photo: UIImage?) {
// Intialize stored properties.
self.name = name
self.rating = rating
self.photo = photo
super.init()
// Initialization should fail if there is no name or if the rating is negative.
if self.name.isEmpty || (rating < 0) {
return nil
}
}
override private init () {
self.name = ""
self.rating = 0
self.photo = nil
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(self.photo, forKey: PropertyKey.photoKey)
aCoder.encodeObject(self.rating, forKey: PropertyKey.ratingKey)
}
convenience required init(coder aDecoder: NSCoder) {
self.init()
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
if let rating = aDecoder.decodeIntegerForKey(PropertyKey.ratingKey) {
self.rating = rating
}
// Because photo is an optional property of Meal, use conditional cast.
let photo = aDecoder.decodeObjectForKey(PropertyKey.photoKey) as? UIImage
self.name = name
self.photo = photo
}
}
I made the 'init()' method private so that only methods inside the class can call it.
I also had to optional unwrap the rating because the app was crashing complaining about not being able to unarchive Int with the ratingKey.
Speculation on my part as to specifics, but, generally - the precise syntax and semantics of Swift changed significantly between Xcode 6 (say, Xcode 6.4) and the new Xcode 7 betas. Xcode 6.4 supports / uses Swift language version 1.2; Xcode 7 uses Swift language version 2.0 (beta)
Found a solution to run above code in xcode6 , should remove "convenience" from the init(coder aDecoder) and use super.init() instead of self.init(name: name, photo: photo, rating: rating) then it works perfectly
class Note: NSObject, NSCoding {
var name: String
var photo: UIImage?
var rating: Int
struct PropertyKey {
static let nameKey = "name"
static let photoKey = "photo"
static let ratingKey = "rating"
}
init(name: String, photo: UIImage?, rating: Int) {
self.name = name
self.photo = photo
self.rating = rating
super.init()
// Initialization should fail if there is no name or if the rating is negative.
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(photo, forKey: PropertyKey.photoKey)
aCoder.encodeInteger(rating, forKey: PropertyKey.ratingKey)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
// Because photo is an optional property of Meal, use conditional cast.
self.photo = aDecoder.decodeObjectForKey(PropertyKey.photoKey) as? UIImage
self.rating = aDecoder.decodeIntegerForKey(PropertyKey.ratingKey) as Int
// Must call designated initializer.
super.init()
}
}
Related
I'm just trying coding in Swift and I am trying to modify this existing project in the Apple Dev Library re: Meals.
I was hoping to put in an additional subclass such as Ingredients into the main Meal class having it as an array or ingredients.
import UIKit
import os.log
class Meal: NSObject, NSCoding {
//MARK: Properties
var name: String
var photo: UIImage?
var rating: Int
var recipe: [ingredients]?
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("meals")
//MARK: Types
struct PropertyKey {
static let name = "name"
static let photo = "photo"
static let rating = "rating"
static let recipe = "recipe"
}
//MARK: Initialization
init?(name: String, photo: UIImage?, rating: Int, recipe: ingredients!) {
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || rating < 0 {
return nil
}
// Initialize stored properties.
self.name = name
self.photo = photo
self.rating = rating
self.recipe = recipe
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(photo, forKey: PropertyKey.photo)
aCoder.encode(rating, forKey: PropertyKey.rating)
aCoder.encode(recipe, forKey: PropertyKey.recipe)
}
required convenience init?(coder aDecoder: NSCoder) {
// The name is required. If we cannot decode a name string, the initializer should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
os_log("Unable to decode the name for a Meal object.", log: OSLog.default, type: .debug)
return nil
}
// Because photo is an optional property of Meal, just use conditional cast.
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
let rating = aDecoder.decodeInteger(forKey: PropertyKey.rating)
let recipe = aDecoder.decodeObject(forKey: PropertyKey.recipe)
// Must call designated initializer.
self.init(name: name, photo: photo, rating: rating, recipe: recipe)
}
}
Function calls for saving and loading the Meal items are as follows:
private func saveMeals() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)
if isSuccessfulSave {
os_log("Meals successfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save meals...", log: OSLog.default, type: .error)
}
}
private func loadMeals() -> [Meal]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Meal.ArchiveURL.path) as? [Meal]
}
I declared a new Class Ingredients.swift to capture the ingredients that I wanted.
import UIKit
import os.log
class Ingredients: NSObject {
struct PropertyKey {
static let name = "name"
static let quantity = "quantity"
}
var name: String!
var quantity: Double!
//MARK: Initialization
init?(name: Int, quantity: Double) {
self.name = name
self.quantity = quantity
}
}
The problem I am having now is that the XCode is throwing a "Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[MealTracker.Ingredients encodeWithCoder:]: unrecognized selector sent to instance
0x1c40a0e40'"
Can I please know how to successfully include the Ingredients array into the saved object?
You have to implement NSCoding protocol
func encode(with aCoder: NSCoder) {}
required convenience init?(coder aDecoder: NSCoder) {}
Inside the inner custom classes also
class Ingredients: NSObject , NSCoding {}
Good day,
I am on the verge of finishing the tutorial app from Code School, and I was really playing around with its archiving and unarchiving the data "to and with a file". So essentially the archiving works, which is the code below.
class func saveOrdersToArchive(cart: Orders) -> Bool {
print(archiveFilePath());
return NSKeyedArchiver.archiveRootObject(cart, toFile: archiveFilePath());
}
The the archiveFilePath() function is implemented this way, it basically creates a file called "cart.archive" and stores it on the simulator's local drive.
class func archiveFilePath() -> String {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
return documentsDirectory.appendingPathComponent("cart.archive").path;
}
So it creates the file correctly then stores the data of type Orders.
But when I try to retrieve the data with my implementation below, it seems that the returned data is marked as "nil".
class func readOrdersFromArchive() -> Orders? {
print(archiveFilePath());
return NSKeyedUnarchiver.unarchiveObject(withFile: archiveFilePath()) as? Orders
}
So, in the main ViewController file, the saving of the Object is implemented below.
//name
productNames = ["1907 Wall set", "1921 Dial phone"];
//cell Images
productImages = [ #imageLiteral(resourceName: "image-cell1"), #imageLiteral(resourceName: "image-cell2")];
//phone Images
phoneImages = [#imageLiteral(resourceName: "phone-fullscreen1"), #imageLiteral(resourceName: "phone-fullscreen2")];
//price
priceProducts = [1.99, 3.99]
oCartProducts = Product(names: productNames, productImages: productImages, cellImages: phoneImages, priceOfProducts: priceProducts);
order = Orders(order_id: 1, orders: oCartProducts);
print(Orders.saveOrdersToArchive(cart: order));
The function prints to true, to indicate successful archive.
The implementation for retrieving the data is implemented below,
if let order1 = Orders.readOrdersFromArchive(){
order = order1
if let o = order.orders{
if let n = o.names{
print(n.count)
}
}
}
The reason I want to print the "count" is to be able to make sure the unwrapped object has values, but the code doesn't go there meaning the object is nil.
I am doing so init in the ViewController before storing the variables as follows,
var oCartProducts = Product(names: [String](), productImages: [UIImage](), cellImages: [UIImage](), priceOfProducts: [Double]());
var order = Orders(order_id: Int(), orders: Product(names: [String](), productImages: [UIImage](), cellImages: [UIImage](), priceOfProducts: [Double]()));
Showing Orders Class,
class Orders : NSObject, NSCoding{
var order_id: Int?
var orders: Product?
init(order_id: Int?, orders: Product?){
self.order_id = order_id;
self.orders = orders;
}
required init?(coder aDecoder: NSCoder) {
self.orders = aDecoder.decodeObject(forKey: "orders") as? Product
self.order_id = aDecoder.decodeInteger(forKey: "order_id")
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.order_id);
aCoder.encode(self.orders);
}
class func archiveFilePath() -> String {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
return documentsDirectory.appendingPathComponent("cart.archive").path;
}
class func readOrdersFromArchive() -> Orders? {
print(archiveFilePath());
return NSKeyedUnarchiver.unarchiveObject(withFile: archiveFilePath()) as? Orders
}
class func saveOrdersToArchive(cart: Orders) -> Bool {
print(archiveFilePath());
return NSKeyedArchiver.archiveRootObject(cart, toFile: archiveFilePath());
}
Showing Product Class,
class Product: NSObject, NSCoding {
var names: [String]?
var productImages: [UIImage]?
var cellImages: [UIImage]?
var priceOfProducts: [Double]?
init(names: [String]?, productImages: [UIImage]?, cellImages: [UIImage]?, priceOfProducts: [Double]?) {
self.names = names;
self.productImages = productImages;
self.cellImages = cellImages;
self.priceOfProducts = priceOfProducts;
}
required init?(coder aDecoder: NSCoder) {
self.names = aDecoder.decodeObject(forKey: "names") as? [String];
self.productImages = aDecoder.decodeObject(forKey: "productNames") as? [UIImage];
self.cellImages = aDecoder.decodeObject(forKey: "cellImages") as? [UIImage];
self.priceOfProducts = aDecoder.decodeObject(forKey: "priceOfProducts") as? [Double];
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.names);
aCoder.encode(self.productImages);
aCoder.encode(self.cellImages);
aCoder.encode(self.priceOfProducts);
}
}
Hope you can shed some light.
Show us your Orders class. Does it conform to NSCoding?
func encode(with aCoder: NSCoder) {
if let id = self.order_id {
aCoder.encode(self.order_id, forKey: "order_id")
}
if let orders = self.orders {
aCoder.encode(self.orders, forKey: "orders")
}
}
When try to encode my custom object in iOS swift get this error from Xcode 8.3
unrecognized selector sent to instance 0x60800166fe80
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
And my code like this:
import UIKit
import Foundation
class Place: NSObject {
func setCustomObject(CustomObject obj:Any,Key key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
}
Here's an example how to make an object to conform to NSCoding. Basically you need to provide implementation of two methods - required convenience init?(coder decoder: NSCoder) and encode(with aCoder: NSCoder)
class Book: NSObject, NSCoding {
var title: String?
var pageCount: Int?
// Memberwise initializer
init(title: String,pageCount: Int) {
self.title = title
self.pageCount = pageCount
}
// MARK: NSCoding
// Here you will try to initialize an object from archve using keys you did set in `encode` method.
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else { return nil }
self.init(title: title, pageCount: decoder.decodeInteger(forKey: "pageCount"))
}
// Here you need to set properties to specific keys in archive
func encode(with aCoder: NSCoder) {
aCoder.encode(self.title, forKey: "title")
aCoder.encodeCInt(Int32(self.pageCount), forKey: "pageCount")
}
}
Also I would recommend changing your setCustomObject method to this:
func setCustomObject(obj:NSCoding, key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
This way compiler prevent you passing NSKeyedArchiver an object that does not conform to NSCoding protocol.
If you don't want to provide all properties in the init method you can use default values:
init(title : String? = nil, pageCount: Int? = nil){
self.title = title
self.pageCount = pageCount
}
Now you can just init your object without any properties. Like that Book()
Here is the solutions, you have to implement the two methods
Encode Method
func encode(with aCoder: NSCoder)
Decoding method
required init?(coder aDecoder: NSCoder)
Complete Example code
class User: NSObject , NSCoding
{
var userID : Int = 0
var name : String = ""
var firstName : String = ""
var lastName : String = ""
var username : String = ""
var email : String = ""
override init(){
super.init();
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.userID, forKey: "id");
aCoder.encode(self.firstName, forKey: "first_name");
aCoder.encode(self.lastName, forKey: "last_name");
aCoder.encode(self.name, forKey: "name");
aCoder.encode(self.username,forKey: "username");
aCoder.encode(self.email, forKey: "email");
}
required init?(coder aDecoder: NSCoder) {
super.init()
self.userID = aDecoder.decodeInteger(forKey: "id");
self.firstName = aDecoder.decodeObject(forKey: "first_name") as! String;
self.lastName = aDecoder.decodeObject(forKey: "last_name") as! String;
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.username = aDecoder.decodeObject(forKey: "username") as! String
self.email = aDecoder.decodeObject(forKey: "email") as! String;
}
init(data : [String: AnyObject]) {
super.init()
self.userID = String.numberValue(data["user_id"]).intValue;
self.firstName = String.stringValue(data["first_name"]);
self.lastName = String.stringValue(data["last_name"]);
self.email = String.stringValue(data["email"]);
self.username = String.stringValue(data["user_name"]);
}
class func loadLoggedInUser() -> User {
if let archivedObject = UserDefaults.standard.object(forKey:"CurrentUserAcc"){
if let user = NSKeyedUnarchiver.unarchiveObject(with: (archivedObject as! NSData) as Data) as? User {
return user;
}
}
return User()
}
func saveUser(){
let archivedObject : NSData = NSKeyedArchiver.archivedData(withRootObject: self) as NSData
UserDefaults.standard.set(archivedObject, forKey: "CurrentUserAcc");
UserDefaults.standard.synchronize();
}
func deleteUser(){
UserDefaults.standard.set(nil, forKey: "CurrentUserAcc")
UserDefaults.standard.synchronize();
}
}
Current Format
Optional(posterPrint.Frame(name: "RIBBA", productNumber: "303.016.24", productSize: (63.0, 93.0), pictureSize: (61.0, 91.0), pictureWithMatSize: (50.0, 70.0), frameColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1], matColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1]))
I want to add this one to array and convert any object. I tried a lot in this to stored in NSkeyedArchiver. I read somewhere tuples are not possible to store in NSKeyedArchiever. Anyone help in this
Thanks in advance .
To use NSKeyedArchiver, you make your object conform to NSCoding. And if you have tuples, you can encode/decode the elements separately:
class MyObject: NSObject, NSCoding {
var id: Int
var size: (Float, Float)
init(id: Int, size: (Float, Float)) {
self.id = id
self.size = size
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let id = aDecoder.decodeObjectForKey("id") as? Int else { return nil }
guard let size0 = aDecoder.decodeObjectForKey("size.0") as? Float else { return nil }
guard let size1 = aDecoder.decodeObjectForKey("size.1") as? Float else { return nil }
self.id = id
self.size = (size0, size1)
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(id, forKey: "id")
aCoder.encodeObject(size.0, forKey: "size.0")
aCoder.encodeObject(size.1, forKey: "size.1")
}
}
You mention arrays, so I'm not sure if you're talking about an array of your custom objects, but now that your object conforms to NSCoding, you can now archive and unarchive arrays of these objects with no further effort.
Likewise, if you have some other object that has a MyObject property, you can apply NSCoding to that, and it can take advantage of the fact that MyObject already conforms to NSCoding:
class ParentObject: NSObject, NSCoding {
var name: String
var value: Int
var child: MyObject
init(name: String, value: Int, child: MyObject) {
self.name = name
self.value = value
self.child = child
super.init()
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as! String
value = aDecoder.decodeObjectForKey("value") as! Int
child = aDecoder.decodeObjectForKey("child") as! MyObject
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(value, forKey: "value")
aCoder.encodeObject(child, forKey: "child")
}
}
import UIKit
class Event: NSObject, NSCoding {
// MARK: Properties
var name: String
var created_at: String
var stands:[Stand?]
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("events")
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let createdAtKey = "created_at"
static let standsAtKey = "stands"
}
// MARK: Initialization
init?(name: String, created_at: String, stands:[Stand?]) {
// Initialize stored properties.
self.name = name
self.created_at = created_at
self.stands = stands
super.init()
// Initialization should fail if there is no name or if no created_at.
if name.isEmpty || created_at.isEmpty {
return nil
}
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(created_at, forKey: PropertyKey.createdAtKey)
aCoder.encodeObject(stands, forKey: PropertyKey.standsAtKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let created_at = aDecoder.decodeObjectForKey(PropertyKey.createdAtKey) as! String
let stands = aDecoder.decodeObjectForKey(PropertyKey.standsAtKey) as! [Stand?]
// Must call designated initializer.
self.init(name: name, created_at: created_at, stands: stands)
}
}
I have got error from "aCoder.encodeObject(stands, forKey: PropertyKey.standsAtKey)" saying "Cannot convert value of type[Stand?] to expected argument type 'AnyObject?'"
I am using NSCoding to save the object with array of empty Stand, then later retrieve it and update the Stands property of this Class (Event)
You cannot encode your custom class "Event" unless your class "Stand" is also NSCoding compliant.
In Swift 4 you can conform to the protocol Codable which does the encoding en decoding
class Test: Codable {
}
For more information, read https://developer.apple.com/documentation/swift/codable