i need to save data in multiple step like a wizard e.g name, family and ... in firstviewcontroller Next adress in secendviewcontroller next image in thirdviewcontroller next ... and in lastviecontroller if user click on finish whole entity save into core data, so i need something to hold temporary data before last step of wizard. i think i need a Separat class to hold temporary data but i don't know how can i implement that.
I am new in ios and i need a Full description.
help me please
No need for separate class. Just pass your partially populated managed object on to the next controller. You can even do a partial save in between with
[context save:nil];
(will work across restarts). To cancel, just delete the object with
[context deleteObject:object];
Related
I have an iOS app with master and detail layout.
In master, I managed my own NSManagedObjectContext, and also detail is, by this way:
NSPersistentStoreCoordinator *psc = ((AppDelegate *)UIApplication.sharedApplication.delegate).persistentStoreCoordinator;
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setPersistentStoreCoordinator:psc];
In master, I display list that can be clicked by user to show the detail in detail layout.
Upon filling the detail by user, user can save the detail by clicking on button there.
However, I am trying to understand this:
Since there is a save button in detail, the save method will save the detail with detail's context and call the load list in master
Load list will remove all the NSMutableArray of the list [_activities removeAllObjects]; and re-fetch the data from Core Data and reload the tableview
Done that but the the re-fetch function seems to use old data and not the latest.
Why does re-fetch the data doesn't work if I use same context?
You are using outdated APIs to create your managed object contexts. After creating a context, instead of assigning the persistent store, you should set the parentContext.
If you display a "list", you should be using a context that is of type NSMainQueueConcurrencyType. The best way is a NSFetchedResultsController which will also help you manage memory and performance, and greatly simplify the updates you need for your UI. (You would avoid the verbosity and complexity of merging the data "manually" via NSManagedObjectContextDidSaveNotification.)
NB: Resetting the context is a crude and inefficient method for the task you are trying to accomplish. Due to the fact that it affects the entire context, it could change the behavior in other parts of your app.
You have two contexts going on here which is why the data is not being refreshed. If you call [context reset] then you will find that the data will refresh.
Explanation:
An NSManagedObjedctContext is a "scratchpad" which manages the Objective-C representation of SQLite (or XML) data in memory. When you fetch data the context retains the managed objects that are created by the fetch. If you then fetch from another context that context reads the data from the persistent store and creates new managed objects based on what it finds.
ALSO: When you perform a fetch the context checks to see if a managed object representation is ALREADY in existence and then returns it if it is. This means that two contexts can get out of sync quite quickly.
There are several ways around this:
1) Calling reset on a context returns it to it's "baseState" which means all the managed objects are "forgotten" or released by the context. Therefore, another fetch will force the context to read the data directly from the store.
2) Implementing the NSManagedObjectContextDidSaveNotification (see here) will allow you to incorporate changes between contexts.
I have found this article very useful in the past.
I've created a couple of Apps that use Core Data and a lot of experiments, but I've never found the "perfect" way to implement a simple Add/Edit viewController.
I just want to implement a single controller able to manage both the edit and add functionalities, I don't want to create two different Controllers.
At the moment I'm working whit this approach (let's take the classic Person NSManagedObject as example)
1) In the addEditViewController I add a currentPerson property
var currentPerson:Person?
2) When I present the controller in Add-Mode this property is nil
3) When I present the controller in Edit-Mode this property is a reference to the Person to edit
4) When I need to save user operations I just check if the currentPerson is set and I understand if I need to create a new object in the context or just save the one I need to edit.
Ok, this approach works but I want to follow another approach that seems to be more secure for the edit action. Check this terrible error!
Let's say that the person has Address property that needs a different viewController to be edited.
1) Following my previous logic I can pass the currentPerson property to the addressViewController that I'm going to present:
addressVC.currentPerson = currentPerson
presentAddressVC()
2) Now when the user has completed the edit operation and he/she taps on "save"
the addressVC calls the saveContext function.
Where is the problem? well... if the user starts editing the currentPerson in the addEditViewController an then just goes back to a previous controller, the currentPerson still stores the edit of the user and as soon as the context will be saved in any other controller the not-really-wanted data get stored and becomes persistent.
Probably I can perform a rollback in case the user taps the back button on the addEditViewController, but I really don't like this behaviour it seems so poor.
I think to work with multiple contexts or inserting NSManagedObjects in a nil context and just move them to the main context only at the end of the operations but I'm not sure about this choice too.
I know it's a kind of a complex and long (an tedious) question, but I hope you can give me some lights on this issue.
How to you treat this kind of situation? what do you think about my approach and about my proposed approaches?
It seems to me that your problem is maintaining a connection to a single NSManagedObjectContext when instead what you really want is to establish a tree. The construction of a context is fairly cheap so you should be creating a context per ViewController.
So when you show the addEdit controller you can simply create it with a new context:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
context.parentContext = //parentContext
context.undoManager = nil;
Think of these new contexts as scratch pads for editing your managed objects. The only thing to bear in mind is that when you call save, it saves to the parent context and not all the way to the store. For that you will need a recursive call all the way to the parent for saves. Heres a basic recursive save:
- (void)saveChangesToDiskForManagedObjectContext:(NSManagedObjectContext *)context {
if (context && [context hasChanges]) {
[context save:nil];
NSManagedObjectContext *parentContext = context.parentContext;
[parentContext performBlock:^{
[self saveChangesToDiskForManagedObjectContext:parentContext];
}];
}
}
Its not really great practice to retain managed objects in an app where they could be deleted on other screens. So what you should do is perhaps fetch these objects in your view controllers view will appear method. Either that or call refreshObject(mergeChanges:) to synchronize your managed object with the changes made by another screen.
I really don't like the idea of calling save when the user navigates back, there should be a save button, when i press back I'm trying to close the screen, i would expect to select "done" if i want my changes saved.
Don't forget you can also use an NSUndoManager to track all your changes, thats why the context has an undoManager:)
So I have a tabbed application. The first tab allows a user to enter information in ~20 fields that describe a NSManagedObject. They are then able to save this into core data, and that works just fine.
The second tab is a TableView of all of the existing submissions. Now when a user clicks on a cell in the TableView, it will open up the first tab and repopulate all of the fields that were originally saved into core data. When the user clicks save again, I want the existing submission in core data to be updated, instead of a new insertion into core data.
I have found a lot of information saying that I should make a fetch request and then update it like that. But that seems redundant to me because I already have the object that was saved passed to the first tab/ViewController.
If you could point me to some code that would help my situation or describe a way you might accomplish this scenario, I would greatly appreciate it!
Since you have a reference to the NSManagedObject in the first tab, you can update its properties to the new values when the user saves. You can then save the changes to your NSManagedObject (let's call it myObject for simplicity) by calling [[myObject managedObjectContext] save:&error] where error is an NSError *.
I'm using magical record for all my core data work.
everything works great, with the exception at times when I'm doing updates in the background I need to detach or disconnect the entity from the context.
for example
ButtonList = [Buttons MR_findAllSortedBy:#"listOrder" ascending:YES];
How would I keep the entity, but remove the reference to the context for the array ButtonList?
Thanks
This will only happen when you don't use a NSFetchedResultsController, or code that observe context changes and remove deleted objects from the UI to reflect the store state.
If you like the deleted objects to be removed from view as soon as your context finds out about the deletion, you would need to listen for "context did change notification" on your main context and look at the deleted objects set, if any of the deleted objects are part of your display array you will need to update your view accordingly (remove from array and update table. a NSFetchedResultsController also listen for context changes).
Another option:
Since you manage your tableview state by yourself (and not a fetched results controller) and
If you like the "buttons" to remain in view including their properties, you could:
Change your request to return dictionaries instead of managed objects (does not nullify on deletion):
NSFetchRequest* r = [Buttons MR_requestAllSortedBy:#"listOrder" ascending:YES];
[r setResultType:NSDictionaryResultType];
//This is your link to the data store and managed object (if you later need to fetch by or update if still exist)
NSExpressionDescription* objectIdDesc = [[NSExpressionDescription new] autorelease];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
[r setPropertiesToFetch:#[objectIdDesc,#"buttonName",#"buttonIcon"/*, and any other property you need for display*/]];
Now all is left to do is execute this request on any context you like (even in background) and get the array back to your table view controller.
The difference here is you get back dictionaries and not NSManagedObject array.
In Core Data there's no such concept of "detaching" an entity from a given context. You would have tons of problems in attempting to do something like that (e.g. move objects between contexts/persistent stores), especially when dealing with relationships.
I think you should refactor and design your application in a way that reacts proactively to any changes in the object(s) you are representing, e.g by removing the related table view cells (with NSFetchedResultsControllerDelegate callbacks), by automatically popping the detail view controller if present, etc...
I would not recommended recommend it but, if there are states for the object you're representing which are "transitory" and shouldn't be reflected in the UI, you could temporarily nil the NSFetchedResultsController delegate (I assume you're using that to display your managed objects) so that it doesn't receive updates and doesn't update the table view (for example). When the objects are ready to be displayed again, you can set the delegate back and perform a new fetch, so that the tableView gets updated (with automatic cell insertions, removals and updates).
If an object has been fetched from a managed object context, the only way to break its connection to the context is to delete the object and then save changes. There is no way to convert a fetched object into something that's not associated with its context. You could copy the data to some other object, but the one that you fetched is and will always be associated with the context that you got it from.
In your case, if you're deleting these objects in a background queue, you should not be using them any more. Or if you do need to use them, then you should not be deleting them. I can't tell from your question just what you're trying to accomplish here, but what you've described makes no sense.
First, let me explain what I'm trying to accomplish. I've got a master-detail application with a MasterViewController and a EditViewController. The MasterViewController contains an Add button and a table listing Core Data entities. When the user taps a table row or the Add button, the Edit view should pop up. I'm confused about how I should handle editing and adding differently.
Here's how I'm currently doing it: my app uses Storyboards, so I have editEntity and addEntity segues from Master to Edit. Both segues pass an entity to the EditViewController, but editEntity finds an existing entity based on the row tapped whereas addEntity creates a new one. Both segues set the isNew transient property on the entity.
The EditViewController doesn't know anything about Core Data--it simply edits the entity it's given. It in turn has done and cancel unwind actions. MasterViewController looks at the isNew property when considering cancel--if the entity is new, it deletes it, and if it already exists, it simply doesn't apply the changes.
This works, but it has a couple problems. Firstly, it seems a tad messy to add extra properties to the entity. Secondly, if the user closes the app on the Edit view while editing a new entity, that entity won't be deleted, which is certainly unexpected. Most of all, this seems like a problem that Core Data itself must have a solution to--I just don't know how. Thanks a bunch!
The simplest improvement would be to replace the isNew flag on the entity description with a flag on your edit view controller. The edit VC might not know anything about Core Data, but it's OK to let it know if the object it's editing is new or pre-existing. Set the flag there, and have the master VC check the value before deciding how to proceed. Don't put this in your entity description, it's not data you need to keep around.
What I've done in this situation is create the new instance but don't insert it in the managed object context yet. Something like
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Entity" inManagedObjectContext:[self managedObjectContext]];
NSManagedObject *myObj = [[NSManagedObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil];
Passing nil for the second argument when creating the instance gives you an instance that hasn't been inserted yet. Pass that to the edit view controller.
If the user taps on the save button, you can insert it later with something like:
if ([myObj managedObjectContext] == nil) {
[[self managedObjectContext] insertObject:myObj];
}
Since the object hasn't been inserted, it has no managed object context, so checking that property tells you whether to insert it. Don't use the isInserted property here, it won't do what you need. Save changes in either case.
If the user taps "cancel", just don't insert it. The object gets deallocated just like any other object, and never makes it to the persistent store. Since you never inserted it, you don't need to bother deleting it.