CoreData: Accessing the instance of NSManagedObject - ios

I have 3 Scenes each collecting User's input. Each Scene has 5 UITextFields. The 4th Scene shows all the 15 Text Field in a UITableView.
I'm not sure if this is the best way to do do this, but I have the following code for scene 1:
//Meetings is NSManagedObject class. Meetings.h and .m was created from the Meetings entity from Core Data
//I have this code once in the file right before I start saving the data
Meetings *meetings = (Meetings *) [NSEntityDescription insertNewObjectForEntityForName:#"Meetings" inManagedObjectContext:self.managedObjectContext];
// I have similar code below for each user's input.
NSString *date = [[NSString alloc] initWithFormat:#"%#", [dateFormatter stringFromDate:selectedDate]];
DateLabel.text = date;
[meetings setDateLabel:date];
...
[meetings setTimeLabel:time];
..
//Code below is to save. I have this once at the end of the file to save the data
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
//The log below shows the saved data fine. Thus, the data is being saved in managnedObjectContext.
NSLog (#"This is the DateLabel %#", meetings.DateLabel);
Question: How do I access the pointer *meetings from Scene 2 and 3 to save rest of the fields in managedObjectContext? I did a NSLog from Scene 2 and it shows as Null:
//In Scene 2 viewDidLoad method I did the following to check:
self.managedObjectContext = [(STAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Meetings *meetings = (Meetings *) [NSEntityDescription insertNewObjectForEntityForName:#"Meetings" inManagedObjectContext:self.managedObjectContext];
NSLog (#"This is the DateLabel from Scene 2 %#", meetings.DateLabel);
The log shows:
2013-02-11 18:04:05.447 MyApp[3505:c07] This is the DateLabel from Scene 2 (null)

You either need to pass a pointer to the Meetings object from the previous screens forward to the next by storing them in a property, or you can pass the object's id's and fetch them as needed for the final screen.
Assume the following is a reflection of your code. The class names may not be identical, but I think you will be able follow and change them as needed.
Scene 1 Header File:
//
// Scene1ViewController.h
// ... etc.
#import <UIKit/UIKit.h>
#import "Meetings.h"
#interface Scene1ViewController : UIViewController
#property (nonatomic, strong) Meetings *meetingsForScene1;
// ... etc.
#end
Scene 2 Header File:
//
// Scene2ViewController.h
// ... etc.
#import <UIKit/UIKit.h>
#import "Meetings.h"
#interface Scene2ViewController : UIViewController
#property (nonatomic, strong) Meetings *meetingsFromScene1;
#property (nonatomic, strong) Meetings *meetingsForScene2;
// ... etc.
#end
meetingsForScene2 may or may not be appropriate depending on your requirements. You might just add data from Scene2 to the meetingsFromScene1 and pass that instance on to the next scene.
Scene 1 Implementation's -prepareForSegue:sender::
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
if ([segue.identifier isEqualToString:#"YourSegueIdentifierForTransistionFromScene1ToScene2"]) {
Scene2ViewController *scene2 = [destinationViewController isKindOfClass:[Scene2ViewController class]] ? (Scene2ViewController *)destinationViewController : nil;
NSAssert(scene2, #"scene2 should not be nil");
NSAssert(self.meetingsForScene1, #"self.meetingsForSecen1 should not be nil");
scene2.meetingsFromScene1 = self.meetingsForScene1;
}
}
Note you might have two properties on Scene2. One for the current scene's data and one for the previous scene's data. The important property is the one that holds the data from the previous scene. The -prepareForSegue:sender: method in Scene1 is an appropriate time to set Scene1's data to the property on Scene2.
I've included some NSAssert calls to do some checking. Consider removing those in your production code. Also, note the check when assigning *scene2. If your destination controller is not the right "kind", you'll find out right away.
I am not necessarily advocating this approach as the "best" for your problem, but this approach will at least provide a path to a solution, if not address the issue entirely.

Well, what you do in your viewDidLoad method is creating a new object in your NSManagedObjectContext instance.
What you should do is to make a fetch request for your previously created Meetings object.
Maybe you should read a tutorial about Core Data. The following is very easy to understand: http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started

Related

Core Data - Segue doesn't work unless inserting new object

Bit of an odd question, hopefully I can explain it. I'm adding objects in a method using:
Objects *object = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManagedObjectContext:self.managedObjectContext]];
object.name = #"Whatever";
When I try to send it over to the ViewController (to load up into a table) with:
ViewController *vc = [segue destinationViewController];
vc.managedObjectContext = self.managedObjectContext;
in the prepareForSegue method, the only way it works is if I add:
Objects *object = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManangedObjectContext:vc.managedObjectContext];
at the end. So I have to add a new entry in the segue for everything to send without errors?
The error without that line is:
NSRangeException', reason: [_PFBatchFaultingArray objectAtIndex:]: index(1) beyond bounds (1)
All help is appreciated :)
Try adding a public property to your receiving view controller...
In ViewController.h
#import "Objects.h"
and
#property (nonatomic, strong) Objects *receivingObject;
Then in your prepareForSegue method include this line...
vc.receivingObject = object;
UPDATE: with thanks to Hal Mueller
Remove the line of code...
vc.managedObjectContext = self.managedObjectContext;
In the case that you need to obtain the NSManagedObjectContext in your destination view controller, you can use this line of code...
NSManagedObjectContext *context = self.receivingObject.managedObjectContext;
Hope this helps.

Trying to store values received in a ViewController and store them in a single instance of an NSMutableArray in another View Controller in iOS

I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.

Custom TableViewCell with Core Data

I have a Destination View Controller that allows you to edit information displayed in the TableViewController..I am attempting to set this up in a custom cell..I have my UITableViewCell file with the custom property class and I also have my Model Class for the Core Data with the attributes. I managed to get my root table view controller to show the custom label when I add a NEW player but once I click on the cell and edit it in the new view controller it goes back to the default on the table view. I believe it has something to do with this code but I can not figure it out.
my NsManagedObject Subclass
#property (nonatomic, retain) NSString *playerFirstName;
I have a pointer to my Player Class of currentPlayer in my viewcontroller.h file and the firstnameTextfield is my UITextField
-(IBAction)doneEditing:(id)sender {
_currentPlayer.playerFirstName = firstnameTextField.text;
AppDelegate *myApp = (AppDelegate *) [[UIApplication sharedApplication]delegate]'
[myApp saveContext];
}
Update
I believe this is my line of code that is the problem after messing with it
_currentPlayer.playerFirstName = firstnameTextField.text;
how do I get the currentPlayer pointer to go to my playerNameCell property in my customcell class
You should do something like:
- (IBAction)newPlayer {
_currentPlayer = (Player*) [NSEntityDescription insertNewObjectForEntityForName:#"Player" inManagedObjectContext:_managedObjectContext];
}
The _managedObjectContext should be passed to the view controller from the app delegate or some other view controller.

Pass core data entity variable between views

I´m having trouble understanding how to use a core data entity variable between views, and for better understanding of what my issue is, my code is below:
View A:
At some point i´m doing this when a save button is pressed:
- (void)guardarOrcamento
{
newBudget=[NSEntityDescription insertNewObjectForEntityForName:#"Budget" inManagedObjectContext:context];
newBudget.relationshipBetweenEntityBudgetAndClient = myEntityClientVariable;
UIAlertView *saved = [[UIAlertView alloc]initWithTitle:#"Budget Saved" message:#"" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[saved show];
NSError *error;
[context save:&error])
}
View B:
My problem is in this view, i need to connect another relationship and for that, my "newBudget" variable most not be empty!:
- (void) setSelectedRowInTableview:(int)line
{
rowEntity=[NSEntityDescription insertNewObjectForEntityForName:#"rowEntity" inManagedObjectContext:context];
rowEntity.relationshipBetweenEntityRowEntityAndBudget = newBudget;
....
This RowEntity can only exist if Budget entity already exists...and at this point it does!...in the other view i have inserted a new object and saved it...and i understand why the variable "newBudget"(in view B) is empty, but how can i persist it?
Thanks for you time
Basically you have to pass either the full budget entity or the ID of the relevant budget entity from view "A" to view "B". Not knowing your app's view hierarchy and logic I assume you select a budget then add entities related to that budget.
Independently from the variable passing solution you have the choice whether you store the selected/inserted budget object in an NSManagedObject variable or store the ID of the budget object in NSManagedObjectID and then retrieve the object using -(NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID error:(NSError **)error.
1) using global variable
Setup in your AppDelegate a NSManagedObject/NSManagedObjectID, and make it accesable:
NSManagedObject *selectedBudgetReference; // OR
NSManagedObjectID *selectedBudgetReferenceID;
...
#property (strong, nonatomic) NSManagedObject *selectedBudgetReference;
#property (strong, nonatomic) NSManagedObjectID *selectedBudgetReferenceID;
Then store into the inserted/selected reference at view A:
AppDelegate *app = (AppDelegate*) [[UIApplication sharedApplication] delegate];
app.selectedBudgetReference = newBudget;
app.selectedBudgetReferenceID = [newBudget objectID];
Finally access it in view B:
AppDelegate *app = (AppDelegate*) [[UIApplication sharedApplication] delegate];
NSManagedObject *localBudgetToRelate = app.selectedBudgetReference;
NSManagedObject *localBudgetToRelate2 = [context existingObjectWithID:app.selectedBudgetReferenceID];
2) passing variable when user switches from view A to B
Similarly as above but you setup the object variable in form B (formBViewController) and when on form A and creating form B to switch to that view you basically access the form B's newly created view controller and pass the budget info to formBViewController's object variable.

Proper way of creating new objects which are copies of NSDictionary and NSArray objects defined in app delegate

I am wondering what the correct way is to make a copy of an object defined in the app delegate or a singleton object. In short, I am making an app which requires a user to login. This login view is just a modal view controller on top of the 'real' app, which consists of a tabbarcontroller, plus some tableview controllers. After a successful login, there is send a data request to a remote server, and the modal view controller is dismissed, revealing the tabbar controller and table views holding the XML data. To parse the incoming data, I have created a singleton object named DataParser, which has interface
...
#interface DataParser : NSObject {
// Data objects that hold the data obtained from XML files
NSMutableDictionary *personnel;
NSMutableDictionary *schedule;
NSMutableDictionary *today;
}
#property (nonatomic, retain) NSMutableDictionary *personnel;
#property (nonatomic, retain) NSMutableDictionary *schedule;
#property (nonatomic, retain) NSMutableDictionary *today;
...
Now in these dictionaries I store (mutable) dictionaries and arrays holding NSString objects with the parsed XML data. Since I do not want to modify these original objects holding the parsed data (that is to say, I only want to modify them at the login stage, but not in any of the tableview controllers), I am creating a new dictionary object which holds a copy of the content of one of the dictionaries above in each tableview controller. So for instance, in the loadView of a view controller called ScheduleViewController I have
...
#interface ScheduleViewController : UITableViewController {
NSDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [NSDictionary dictionaryWithDictionary:sharedSingleton.schedule];
}
...
Now this seems to work fine. The only difficulty arises however, when the user 'logs out', which entails popping the login modal view controller back on the stack. When the user presses the login button again, then a new XML data request is send to the server and the dictionaries in the singleton object get refreshed with the (new) data (I check if they contain any data, if so I call removeAllObjects before filling them up again with newly parsed data). At this point the dictionaries in all view controllers should be updated too, however I am not quite sure how to go about this the right way. I have noticed that loadView is not always called again in this case and so to this end I have added the same code as above in loadView to every viewWillAppear method. After navigating back and forth between the different views or navigating back and forth between child views of a tableview a couple of times, I receive an EXC_BAD_ACCESS error however. I suspect this has to do with not properly retaining the copies of the original dictionaries, but I don't seem to be able to find a solution around this. Instead of using dictionaryWithDictionary, which I suspect is not the right way to go anyway, I also tried a different approach, where instead of using objects of type NSDictionary in ScheduleViewController I use NSMutableDictionary. So:
...
#interface ScheduleViewController : UITableViewController {
NSMutableDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSMutableDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [[NSMutableDictionary alloc] initWithDictionary:sharedSingleton.schedule];
}
- (void)viewWillAppear {
DataParser *sharedSingleton = [DataParser sharedInstance];
[self.copyOfSchedule removeAllObjects];
[self.copyOfSchedule addEntriesFromDictionary:sharedSingleton.schedule];
[self.tableView reloadData];
}
...
But this doesn't get rid of the EXC_BAD_ACCESS errors. To make a very long story short: what would be the best way to go about making independent copies of objects defined in a singleton object or app delegate and which can be dynamically updated at request? Since I am already rather into the project and lots is going on, I realize that my question may be a bit vague. Nonetheless I hope there is somebody who could enlighten me somehow.
Deep copies are often made recursively. One way to do it would be to add -deepCopy methods to NSDictionary and NSArray. The dictionary version might go like this:
- (NSDictionary*)deepCopy
{
NSMutableDictionary *temp = [self mutableCopy];
for (id key in temp) {
id item = [temp objectForKey:key];
if ([item respondsToSelector:#sel(deepCopy)] {
// handle deep-copyable items, i.e. dictionaries and arrays
[temp setObject:[item deepCopy] forKey:key]
}
else if ([item respondsToSelector:#(copy)]) {
// most data objects implement NSCopyable, so will be handled here
[temp setObject:[item copy] forKey:key];
}
else {
// handle un-copyable items here, maybe throw an exception
}
}
NSDictionary *newDict = [[temp copy] autorelease];
[temp release]
return newDict;
}
I haven't tested that, so be a little careful. You'll want to do something similar for NSArray.
Note that views are not copyable.
It is quite a typical pattern that you build an array or dictionary with some code, so clearly it must be mutable while you add bits to it, and when you're done you don't want it ever to change. To do this:
Have a property like
#property (...) NSArray* myArray;
When you calculate the contents of myArray, use a mutable array to build it, like
NSMutableArray* myMutableArray = [NSMutableArray array];
When you're done building the array, just use
self.myArray = [NSArray arrayWithArry:myMutableArray];

Resources