I'm creating a signup form with multiple view controllers as "pages" and I need the information from one view controller such as 'First name' and 'last name' or 'email address' to another view controller. I decided I'm going to use core data to save the strings and retrieve them from another view controller rather than create a global struct for every view controller (because it crashes every time I try and implement it). Heres my code:
#IBAction func nextBtnWasPressed(_ sender: Any) {
if firstNameText.text != "" && lastNameText.text != "" {
//Core Data User Information
var userInfo = UserInfo() // The Name of the Core Data Entity
var firstName: String = firstNameText.text!
userInfo.firstName = firstName
do {
try NSManagedObjectContext.save(firstName)//Need Fixing
} catch {
fatalError("Failure to save context: \(error)")
}
performSegue(withIdentifier: "toBirthdate", sender: self)
} else {
nextBtn.alpha = 0.5
}
}
Here is the error log : 'Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UserInfo setFirstName:]: unrecognized selector sent to instance 0x600001530e80'
Full error log: View Here
Well, at first I was going to give the obvious answer which is that, in your Core Data data model, your UserInfo entity does not have a firstName attribute.
That's still possible, but then I looked at the full transcript you posted and saw that, a couple hundred microseconds before this error is another error:
Failed to call designated initializer on NSManagedObject class 'UserInfo'
I suspect this error indicates that you have initialized your managed object with init(). This is a common mistake of first-time Core Data users. Ensure that you are initializing your UserInfo object like this:
let userInfo = UserInfo.init(entity: entityDescription, insertInto: context);
or if your deployment target is iOS 10 or later, you can use the newer cleaner method:
let userInfo = UserInfo.init(context: context)
Related
I have a ConfigureTableViewController enabled tableview that has two static cells Users and Locations.
Users tableview cell is connected to ListUserTableViewController via usersSegue.
Locations tableview cell is connected to LocationTableViewController via segue locationsSegue
ListUserTableViewController has a protocol:
protocol UserSelectionDelegate{
func userDidSelectUser(userIdentifier:NSString)
}
I select user from ListUserTableViewController and pass it to ConfigureTableViewController, by implementing the delegate UserSelectionDelegate
class ConfigureTableViewController: UITableViewController, UserSelectionDelegate {
…
…
// function pulls user’s fullName from Core Data table based on userIdentifier
func userDidSelectUser(userIdentifier : NSString) {
var fetchedResults: [User] = []
let context = (UIApplication.shared.delegate as AppDelegate).persistentContainer.viewContext
userIdentifierString = userIdentifier
do {
fetchedResults = try context.fetch(User.fetchRequest())
}
catch {
print ("fetch task failed")
}
if fetchedResults.count > 0 {
for user in fetchedResults{
let aUser:User = user
if (aUser.uniqueId! == userIdentifierString as String) {
let fullName = aUser.firstName! + " " + aUser.lastName!
print("selected user full name: " + (fullName as String))
}
else {
continue
}
}
}
}
}
so far so good..
But Now, I want to take the userName pulled from DB and send to to LocationTableViewController via segue locationsSegue
As I transition from ListUserTableViewController to ConfigureTableViewController I have the userName, but when I leave the ConfigureTableViewController and go to LocationTableViewController, I lose the scope of fullName.
I have to somehow store this value of computed fullName in some permanent storage, so Should I put it in a table in CoreData. I want to avoid this, which seems lot of work.
Can I use some kind of delegate chaining so LocationTableViewController can implement a delegate defined in ConfigureTableViewController and pull the data from the other UserSelectionDelegate which has already been implemented and has the fullName in its scope.
Please advise, how to solve this problem?
Confusing problem statement:
I lose the scope of fullName
You need to save any changes to the User object and retrieve these changes in the next view controller. If the next view controller uses the same NSManagedObjectContext, you can also just pass the same User object.
Then you have all the data for the full name.
This is commonly implemented as a custom method of the User class that inspects its own first and last name and returns a properly formatted string.
App runs fine first time the simulator, but once new data is saved to Core Data the app will not launch again - crashing before the first view loads with an uncaught exception 'NSInternalInconsistencyException', reason: 'statement is still active'. There are 75 lines of pre-crash actions in the console, but nothing stands out (to my unskilled eyes).
If deleted, the app can be launched repeatedly until new data is saved to Core Data. After saving new data, it does not help to simply stop running the app or quit the simulator, it still crashes during launch.
Stackoverflow and the apple doc.s consistently suggest that it has something to do with threading, but my code is a bit simple for that - everything is on the main thread. I'd love to find things to try in swift or a swift process to identify the cause / solution.
I'm using the default xcode 7 Core Data stack in the AppDelegate class. Sample data is initially loaded into Core Data in one method, and then successfully loaded from core data. After saving a new record, (not re-launching) newly added data is successfully loaded from Core Data. The problem only occurs on re-launch.
In code, when a new record view controller is instantiated, I instantiate two managed objects with separate entities in a prepareForSegue method:
if segue.identifier == "newRecord"
{
let controller = (segue.destinationViewController as! NewRecordVC)
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate // instantiate the delegate methods in AppDelegate
controller.managedContext = appDelegate!.managedObjectContext // create context from delegate methods
let recordEntity = NSEntityDescription.entityForName("RecordData", inManagedObjectContext: controller.managedContext)
let locationEntity = NSEntityDescription.entityForName("Location", inManagedObjectContext: controller.managedContext)
controller.location = Location(entity: locationEntity!, insertIntoManagedObjectContext: controller.managedContext)
controller.record = Record(entity: recordEntity!, insertIntoManagedObjectContext: controller.managedContext)
print("segueing")
}
In the new record view controller, managed object property values are defined, and the managed context is saved while unwinding.
if (segue.identifier == "UnwindSegue")
{
updateRecord() // managed object properties updated
do
{
try managedContext.save() // commit changes / save context
}
catch
{
print("There is some error.") // if error
}
}
When the app returns to the master view, the new record is fetched from Core Data and displayed on a table.
BUT when I relaunch the app - sadness.
I've learnt that when something is "discouraged" by apple, it's best to stay away from it.
Changing the override init in the subclass to awakeFromInsert fixed it, as per doc.s. Thanks Markus S. Zarra.
I have a two core data models set-up that have a many-to-many relationship.
class BuddyCD: NSManagedObject {
#NSManaged var memberOfBunches: NSSet
}
class BunchCD: NSManagedObject {
#NSManaged var bunchMembers: NSSet
}
Sidenote: CD means Core Data here
I when creating a BunchCD, I would like to add many buddies to it as members.
I have this method in the Bunch class:
class func createInManagedObjectContext (moc: NSManagedObjectContext, members: [BuddyCD]?) -> BunchCD {
let newBunch = NSEntityDescription.insertNewObjectForEntityForName(CoreDataConst.bunchModel, inManagedObjectContext: moc) as! BunchCD
if let membersNonOptional = members {
// Add members to this bunch
for member in membersNonOptional {
member.addToBunch(newBunch)
}
}
return newBunch
}
I have this method in the Buddy class:
func addToBunch(bunch: BunchCD) {
var bunches = self.mutableSetValueForKey("memberOfBunches")
bunches.addObject(bunch)
}
I am getting this error:
2015-07-19 01:00:38.587 MyApp[34979:1885727] -[__NSSetIobjectAtIndex:]: unrecognized selector sent to instance 0x7fb7d3ecde30 2015-07-19 01:00:38.603 LunchBunch[34979:1885727] ***Terminating app due to uncaught exception'NSInvalidArgumentException', reason: '-[__NSSetI objectAtIndex:]: unrecognized selector sent to instance 0x7fb7d3ecde30'
What is the best practice for adding members to a many to many relationship in core data?
Your code seems OK. The error must originate elsewhere. Perhaps you can find out where by stepping through the code.
So the error originated elsewhere, I'm guessing from multi-threaded components of my application (that were not intentional). The errors most likely stemmed from using Alamofire asynchronous requests and using the Managed Object Context in the request callbacks. So, I added code around the Managed Object Context accesses to ensure it (the MOC) was being used on the main thread:
dispatch_async(dispatch_get_main_queue()) {
// Managed Object Context access here
}
I am trying to retrieve a query from the Parse database; however, when I run the app and click the button to go to the view controller that is going to retrieve the data from the database, my app crashes. When the app crashes, I get sent the AppDelegate.swift file.
This is the error: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Method not allowed when Pinning is enabled.'
I made sure to implemented Parse correctly in my project. Also, when I take out the if self.objects.count == 0 {} code block, the app runs fine when I go to the view controller that is retrieving the data from the database. But only this time, there are no objects in my tableview list when there are objects in my parse database. Thanks in advance.
override func queryForTable() -> PFQuery {
let query = PFUser.query()
if searchInProgress {
// We are looking for the string contents in the search bar to match the names in the parse username category.
query?.whereKey("username", containsString: searchString)
}
// From the objects aleady loaded...
if self.objects?.count == 0{
// If we have not already loaded the elements from our database, then it will use the elements that have already been downloaded when we have already run the app
query?.cachePolicy = PFCachePolicy.CacheThenNetwork
}
query?.orderByAscending("username")
return query!
}
You can't use pinning and cache policy at the same time, hence the inconsistency exception.
http://parse.com/docs/ios/api/Classes/PFQuery.html#//api/name/cachePolicy
This question seems to have been asked a lot already. But I havn't been able to find a swift solution.
I have an entity named "User" which there should only ever be one of. So after it has been created for the first time. I need to edit/update it's values rather than creating more.
I did find a video on it on youtube. Which is the attempt provided below. However it doesn't work.
This is my code in the viewController Class before viewDidLoad()
// Core data setup
let context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var newUser : User? = nil
var results = NSArray()
var hasAccount = Bool()
Here's my code in viewDidLoad()
println(newUser)
// Core data fetching
var request = NSFetchRequest(entityName: "User")
request.returnsObjectsAsFaults = false
results = context.executeFetchRequest(request, error: nil)!
if results.count > 0 {
hasAccount = true
var res = results[0] as NSManagedObject
println(res.valueForKeyPath("email")!)
}
else {
println("No Account")
}
I use the above code, to figure out if there is already something in the User entity, (then setting hasAccount to "true"). However, if you happen to know a better way to do this. it would be very appreciated!
I printed out "newUser" in hopes that I could make it print the User object. So I could just check if newUser != nil { do this } However all my attempts of getting "User" outside of the above workaround has failed.
Now the code that I havn't been able to get to work in any way is this:
let ent = NSEntityDescription.entityForName("User", inManagedObjectContext: context)
if hasAccount == false {
println("Creating a new User Object")
let newUser = User(entity: ent!, insertIntoManagedObjectContext: context)
newUser.firstName = firstNameTextField.text
newUser.lastName = lastNameTextField.text
newUser.email = emailTextField.text
newUser.password = passwordTextField.text
newUser.accountType = 0
context.save(nil)
} else {
println("Updating existing User Object")
newUser?.firstName = firstNameTextField.text
newUser?.lastName = lastNameTextField.text
newUser?.email = emailTextField.text
newUser?.password = passwordTextField.text
newUser?.accountType = 0
context.save(nil)
}
println(newUser)
The part that's creating a new User object. Should work. (However untested since I moved it into an if statement)
But the part that's supposed to update the entity, doesn't work. I know it runs due to the println.
But it doesn't change the User Entity.
Any help would be greatly appreciated!
Let me start by saying that Core Data is an incredibly powerful framework for persisting and maintaining object graphs. That being said it does, unfortunately, require a large amount of effort just to get started. For this particular reason I typically recommend to beginners looking at MagicalRecord. It's a delightful library that removes almost all of the boiler plate code required setting up the stack and maintaining and creating contexts.
Mattt Thompson (I do miss his writing) wrote an insightful post regarding when you should consider CoreData versus NSKeyedArchiver etc. Always handy to keep for reference.
I broke my solution up into two parts, in the first part I reviewed your original question just to give you some basic pointers. The last part I deal with MagicalRecord as I really feel that it might be better suited for you given its lighter learning curve. We are here to ship apps after all, learn Core Data as you get deeper into iOS.
Question Review
Before I dive into things I want to explore some of the mistakes we can rectify with your solution so you can understand why its not working.
// Core data setup
let context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
I know Apple throw the context into the AppDelegate but its a really messy hack and I typically find its best to have a CoreData singleton somewhere that wraps your contexts. Its nice to stay true to the singular responsibility principle, the App Delegate is responsible for handling App Events and instantiating the ViewController hierarchy. NOT for managing the core data stack.
var newUser : User? = nil
var results = NSArray()
This is swift, why not var results:[AnyObject] = []
var hasAccount = Bool()
you mean, var hasAccount = false....
But lets fast forward, you don't need to define all these variables upfront, anyone reading your code has to scan so many lines before they actually get to the root of what you are trying to achieve. Lets clean it up:
// Core data fetching
let userRequest = NSFetchRequest(entityName: "User")
userRequest.fetchLimit = 1
if let user = context.executeFetchRequest(request, error: nil)?.first as? User {
//now we have a user
} else {
//no user
}
One of the big gotchas that newcomers don't realize is that an NSManagedObjectContext save operation only saves one level up. If that context descends directly from the persistent store then your changes will be saved to disk but if it isn't, that save needs to recursively call save on that contexts parent etc. Here's the documentation:
If a context’s parent store is a persistent store coordinator, then
changes are committed to the external store. If a context’s parent
store is another managed object context, then save: only updates
managed objects in that parent store. To commit changes to the
external store, you must save changes in the chain of contexts up to
and including the context whose parent is the persistent store
coordinator.
So if your context has a parentContext then your save operation will never be saving the user to disk:)
Enter MagicalRecord
Getting started with Magical Record is incredibly simple:
MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed(MyCoreDataStoreName)
This typically goes in your App Delegate. They also have tons of amazing categories on most Core Data classes for common Core Data operations as well as some well thought out infrastructure for handling multi-threading issues.
So now you want to establish a record in your database that there should only be one. Before you get started with that you must make sure that there is also only one object responsible for accessing this user attribute, or in the very least you make sure that only one object can create this user attribute.
For simplicities sake lets call this class the UserManager. The user manager will be responsible for managing all the operations we want to perform with our user. For now it just needs to make sure that when we access it, that there is always one in the database.
class UserManager {
class var sharedManager : UserManager {
struct Static {
static let instance : UserManager = UserManager()
}
return Static.instance
}
func currentUser() -> CurrentUser {
if let user = CurrentUser.MR_findFirst() as? CurrentUser {
return user
} else {
let user = CurrentUser.MR_createEntity() as CurrentUser
user.managedObjectContext!.MR_saveToPersistentStoreAndWait()
return user
}
}
}
Now we have a singleton that will always guarantee that we have one user in the database. You also mentioned that you want to be able to update your user, a naive implementation could be added to our UserManager:
func updateUser(userUpdateHandler: ((user: CurrentUser) -> Void)) {
MagicalRecord.saveWithBlock { (context) -> Void in
let user = self.currentUser()
userUpdateHandler(user: user)
//as the app grows you could probably post a notification
//here so any interested parties could update their info
//if the user changes....
}
}
Calling this from you View Controller is trivial:
//the updateUser function isn't called
//on the main thread so we need to capture
//the values from the UI so we can safely use
//them in the background
let firstName = firstNameTextField.text
let lastName = lastNameTextField.text
let email = emailTextField.text
let password = passwordTextField.text
UserManager.sharedManager.updateUser { (user) -> Void in
user.firstName = firstName
user.lastName = lastName
user.email = email
user.password = password
user.accountType = 0
}
And voila, you have now implemented a pretty standard set of functions to deal with a user in your app.