I have a Swift 3 iOS app in which I want to be able to share an entity I store in Core Data. What I've done is implement NSCoding in the NSManagedObject class:
import Foundation
import CoreData
import UIKit
public class Event: NSManagedObject, NSCoding {
// MARK: NSCoding
override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
super.init(entity: entity, insertInto: context)
}
required public init(coder aDecoder: NSCoder) {
let ctx = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "Group", in: ctx)!
// Note: pass `nil` to `insertIntoManagedObjectContext`
super.init(entity: entity, insertInto: nil)
for attribute:String in self.entity.attributesByName.keys{
self.setValue([aDecoder.decodeObject(forKey: attribute)], forKey: attribute)
}
}
public func encode(with aCoder: NSCoder){
for attribute:String in self.entity.attributesByName.keys{
aCoder.encode(self.value(forKey: attribute), forKey: attribute)
}
}
}
I then try to the serialize the object using:
NSKeyedArchiver.archiveRootObject(selectedGroup, toFile: file)
But when this gets executed it fails with:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
So, even though I've implemented the NSCoding protocol, for some reason the NSCoding protocol doesn't seem to stick. Any idea what I'm doing wrong?
Related
This question already has answers here:
Attempt to insert non-property list object when trying to save a custom object in Swift 3
(6 answers)
Save custom objects into NSUserDefaults [duplicate]
(2 answers)
Closed 3 years ago.
I am not able to save the list of viewControllers in my user defaults, its getting crashed at this userDefault.set(vcList, forKey: "vcList").
The error log says:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object
import UIKit
class ViewController: UIViewController {
struct viewControllerList {
var vc1 = ViewController()
var vc2 = LoginViewController()
}
override func viewDidLoad() {
super.viewDidLoad()
let vcList = viewControllerList()
let userDefault = UserDefaults.standard
userDefault.set(vcList, forKey: "vcList")
}
}
Please help!
The UserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Boolean values, and URLs.
A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.
You can try this
//MARK:- Save viewController in UserDefault
func saveInDefaults(_ viewController : UIViewController) -> Void{
do {
let encodeData = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
UserDefaults.standard.set(encodeData, forKey: "vcList")
UserDefaults.standard.synchronize()
} catch {
print("error")
}
}
And Call function from viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
let vcList = viewControllerList()
self.saveInDefaults(vcList)
}
Other people have asked a similar question, but the answers given did not help me. I am trying to create a table view with core data and I keep getting an error message that my fetchRequest and/or managedObjectContext are nil
class CustomTableViewController: UITableViewController,
NSFetchedResultsControllerDelegate {
var coreDataContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#available(iOS 10.0, *)
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Movie> = {
// Initiate the query
let fetchRequest: NSFetchRequest<Movie> = Movie.fetchRequest() as! NSFetchRequest<Movie>
// Sort the data
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"title", ascending: true)]
NSLog("Request: %#, Context: %#", fetchRequest, self.coreDataContext);
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setToolbarHidden(false, animated: false)
do {
// when view loads, run the fetch (query)
if #available(iOS 10.0, *) {
try self.fetchedResultsController.performFetch()
} else {
// Fallback on earlier versions
}
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError)")
}
}
Does anyone have any ideas what's wrong? Also, not sure if it matters, but my project is a mix of both Objective-C and Swift
Also, the NSLog("Request: %#, Context: %#", fetchRequest, self.coreDataContext) from my above code prints out:
Request: (null), Context: <NSManagedObjectContext: 0x1701db4e0>
I have an entity called Movie in my xcdatamodeld with three String attributes. I also created a class for Movie like this in its own Movie.swift file:
import UIKit
import CoreData
class Movie: NSManagedObject {
}
The problem is that your Movie class has no code for generating a fetch request.
#nonobjc public class func fetchRequest() -> NSFetchRequest<Movie> {
return NSFetchRequest<Movie>(entityName: "Movie");
}
The solution: Delete your Movie class file entirely.
In the data model, configure your Movie entity to do automatic code generation:
That will solve the problem. Xcode knows how to make the class files for an entity. So just permit it to do so, and all will be well.
I'm new with swift and got a problem with core data.
My app crashes when trying to populate. I just have 1 field as for testing purposes
import UIKit
import SwiftyJSON
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
seedRestaurantList()
fetch()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fetch() {
let moc = DataController().managedObjectContext
let nameFetch = NSFetchRequest(entityName: "RestaurantList")
do {
let fetchedName = try moc.executeFetchRequest(nameFetch) as! [RestaurantList]
print(fetchedName.first!.name!)
} catch {
fatalError("merda again: \(error)")
}
}
func seedRestaurantList () {
let moc = DataController().managedObjectContext
let entity = NSEntityDescription.insertNewObjectForEntityForName("RestaurantList", inManagedObjectContext: moc) as! RestaurantList
entity.setValue("teste", forKey: "name")
do {
try moc.save()
} catch {
fatalError("deu merda: \(error)")
}
}
}
This is the error that I get
2015-11-02 17:38:04.880 DinDins[2993:1252158] CoreData: error: Illegal
attempt to save to a file that was never opened. "This
NSPersistentStoreCoordinator has no persistent stores (unknown). It
cannot perform a save operation.". No last error recorded. 2015-11-02
17:38:04.885 DinDins[2993:1252158] * Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'This
NSPersistentStoreCoordinator has no persistent stores (unknown). It
cannot perform a save operation.'
* First throw call stack: (0x236c585b 0x3503adff 0x2346da89 0x2346dc95 0x2346de2d 0x2347710f 0x1249d03 0x12534fb 0x23468ef1
0x233b31f3 0x233b3223 0x233d615b 0x9cff0 0x9c360 0x9c400 0x277f9f55
0x278b6c4f 0x278b6b45 0x278b5ef1 0x278b5b27 0x278b577d 0x278b56f7
0x277f5cc3 0x270bdb05 0x270b9201 0x270b9091 0x270b85b1 0x270b8263
0x270b1a1f 0x23688091 0x23686387 0x235d90f9 0x235d8ecd 0x27867607
0x278622dd 0x9fde0 0x35788873) libc++abi.dylib: terminating with
uncaught exception of type NSException (lldb)
Thanks in advance
---RestaurantList+CoreDataProperties.swift
import Foundation
import CoreData
extension RestaurantList {
#NSManaged var name: String?
}
---RestaurantList.swift
import Foundation
import CoreData
class RestaurantList: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Ricardo, try reseting your simulator. Open the Simulator, go to menu Simulator and select Reset content and Settings
I am trying to send a "Class" to my Watchkit extension but I get this error.
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyApp.Person)
Archiving and unarchiving works fine on the iOS App but not while communicating with the watchkit extension. What's wrong?
InterfaceController.swift
let userInfo = ["method":"getData"]
WKInterfaceController.openParentApplication(userInfo,
reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in
println(userInfo["data"]) // prints <62706c69 7374303...
if let data = userInfo["data"] as? NSData {
if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
println(person.name)
}
}
})
AppDelegate.swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!) {
var bob = Person()
bob.name = "Bob"
bob.age = 25
reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
return
}
Person.swift
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
According to Interacting with Objective-C APIs:
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
By adding the annotation #objc(name), namespacing is ignored even if we are just working with Swift. Let's demonstrate. Imagine target A defines three classes:
#objc(Adam)
class Adam:NSObject {
}
#objc class Bob:NSObject {
}
class Carol:NSObject {
}
If target B calls these classes:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
The output will be:
Adam
B.Bob
B.Carol
However if target A calls these classes the result will be:
Adam
A.Bob
A.Carol
To resolve your issue, just add the #objc(name) directive:
#objc(Person)
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
I had to add the following lines after setting up the framework to make the NSKeyedUnarchiver work properly.
Before unarchiving:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Before archiving:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
NOTE: While the information in this answer is correct, the way better answer is the one below by #agy.
This is caused by the compiler creating MyApp.Person & MyAppWatchKitExtension.Person from the same class. It's usually caused by sharing the same class across two targets instead of creating a framework to share it.
Two fixes:
The proper fix is to extract Person into a framework. Both the main app & watchkit extension should use the framework and will be using the same *.Person class.
The workaround is to serialize your class into a Foundation object (like NSDictionary) before you save & pass it. The NSDictionary will be code & decodable across both the app and extension. A good way to do this is to implement the RawRepresentable protocol on Person instead.
I had a similar situation where my app used my Core framework in which I kept all model classes. E.g. I stored and retrieved UserProfile object using NSKeyedArchiver and NSKeyedUnarchiver, when I decided to move all my classes to MyApp NSKeyedUnarchiver started throwing errors because the stored objects were like Core.UserProfile and not MyApp.UserProfile as expected by the unarchiver. How I solved it was to create a subclass of NSKeyedUnarchiver and override classforClassName function:
class SKKeyedUnarchiver: NSKeyedUnarchiver {
override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
let lagacyModuleString = "Core."
if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0 {
return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
}
return NSClassFromString(codedName)
}
}
Then added #objc(name) to classes which needed to be archived, as suggested in one of the answers here.
And call it like this:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
It worked very well.
The reason why the solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") was not for me because it doesn't work for nested objects such as when UserProfile has a var address: Address. Unarchiver will succeed with the UserProfile but will fail when it goes a level deeper to Address.
And the reason why the #objc(name) solution alone didn't do it for me was because I didn't move from OBJ-C to Swift, so the issue was not UserProfile -> MyApp.UserProfile but instead Core.UserProfile -> MyApp.UserProfile.
I started facing this after the App Name change,
The error I got was - ".....cannot decode object of class (MyOldModuleName.MyClassWhichISerialized) for key....."
This is because code by default saves Archived object with ModuleName prefix, which will not be locatable after ModuleName changes. You can identify the old Module Name from the error message class prefix, which here is "MyOldModuleName".
I simply used the old names to locate the old Archived objects.
So before Unarchieving add line,
NSKeyedUnarchiver.setClass(MyClassWhichISerialized.self, forClassName: "MyOldModuleName.MyClassWhichISerialized")
And before Archieving add line
NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)
Xcode 6 has had a ton of bugs. But I'm not quite sure if this is a bug or not. It might not be since this is something I'm just now learning.
My issue is, any time I try to instantiate my subclass of NSManagedObject, I do not have the option to pass the entity: NSEntityDescription and NSManagedContext: insertIntoManagedContext argument to the constructor, Xcode says "Extra Argument 'entity' in call"
I created a new Xcode project from scratch, just to see if I could re-create the problem in a smaller, minimal project.
ToDoList.Item is set as the Item entity class in the Data Model Inspector.
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("Item", inManagedObjectContext: context)!
//compiler complains here
var item = Item(entity: ent, insertIntoManagedObjectContext: context)!
}
Here's the subclass:
import UIKit
import CoreData
class Item: NSManagedObject {
#NSManaged var title: String
#NSManaged var completed: Bool
}
All help is appreciated.
Just came across the same problem: Init method for core data entity not available
Obviously we have to implement the
init(entity: NSEntityDescription, insertIntoManagedObjectContext context, NSManagedObjectContext?)
method in our custom NSManagedObject class. So just add
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
to your entity class and it will work.
Try the final line without exclamation mark, like this:
var item = Item(entity: ent, insertIntoManagedObjectContext: context)
And maybe You haven't added your app name to class name:
Swift classes are namespaced—they’re scoped to the module (typically, the project) they are compiled in. To use a Swift subclass of the NSManagedObject class with your Core Data model, prefix the class name in the Class field in the model entity inspector with the name of your module.
https://developer.apple.com/library/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html
Are constructors inherited in Swift?
I'd try using NSEntityDescription.insertNewObjectForEntityForName:inManagedObjectContext