I am following the tutorial for core data at here. We have RootViewcontroller and addRecipeViewController.
I list some classes and some functions and a screen for the flow below so that you wont get lost
Recipe.h
#import <CoreData/CoreData.h>
#interface Recipes : NSManagedObject
{
}
#property (nonatomic, retain) NSString * recipeName;
#property (nonatomic, retain) NSString * cookingTime;
#end
addRecipeViewController.h
#class Recipes;
#interface AddRecipeViewController : UIViewController <UITextFieldDelegate> {
Recipes *recipes;
UITextField *textFieldOne;
UITextField *textFieldTwo;
}
addRecipeViewController.m
- (void)save {
1.recipes.recipeName = textFieldOne.text;
2.recipes.cookingTime = textFieldTwo.text;
3.NSError *error = nil;
4.if (![recipes.managedObjectContext save:&error]) {
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self dismissModalViewControllerAnimated:YES];
}
RootViewController.m
- (void)insertNewObject {
AddRecipeViewController *addRecipeView = [[AddRecipeViewController alloc] initWithNibName:#"AddRecipeViewController" bundle:[NSBundle mainBundle]];
Recipes *recipes = (Recipes *)[NSEntityDescription insertNewObjectForEntityForName:#"Recipes" inManagedObjectContext:self.managedObjectContext];
addRecipeView.recipes = recipes;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController: addRecipeView];
[self.navigationController presentModalViewController:navController animated:YES];
[addRecipeView release];
}
picture for flow :
When event Save of addRecipeViewController is clicked, it will save recipes into managedObjectContext. Sooner or later, the rootViewConroller will retrieve data from managedObjectContext by using NSFetchedResultsController
QUESTION: I dont understand how manageObjectContext is the same for all view controller so that you will get the most updated manageObjectContext at rootViewController after adding or deleting Recipe from manageObjectContext in addRecipeViewController
Please help me to understand this problem.
All comments are welcomed here.
The managedObjectContext is basically your persistence layer and it includes a cache and a way to retrieve objects that are not yet in the cache. You want to avoid having multiple managed object contexts in your app so you don't need to deal with nasty cache synchronization issues.
So I'm not sure what problem you are running into exactly that is causing you to pause, but please don't over complicate the problem. Core Data is nice enough to provide you with a single entry point to the persistence store and keeps everything synchronized for you so you should run with it :)
Also, be sure not to confused NSManagedObjectContext and NSManagedObject. Managed objects live within the context. They are not the same thing.
You probably want to get notification when something changes in your context. If so, read this: Are there Core Data call back methods?
Related
im new to IOS and Objective-C and the whole MVC paradigm and i'm stuck with the following.
I am working on (replica) Contact app, also available in iphone as build in app. i want to pass data through another view controller and the data is pass (null) :(.
My Question is, How do I transfer the data from one view to another?
As most the answers you got, passing data between one controller and another just means to assign a variable from one controller to the other one.
If you have one controller to list your contacts and another one to show a contact details and the flow is starting from the list and going to detail after selecting a contact, you may assign the contact variable (may be an object from the array that is displayed in your list) and assign it to the detail view controller just before showing this one.
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
On the other hand, if you want to insert a new contact from the detail controller to the list controller, I guess the best approach would be to assign the list controller as a delegate to the detail one, so when a contact is added the delegate is notified and act as expected (insert the contact to the array and reload the table view?).
#protocol ContactDelegate <NSObject>
- (void)contactWasCreated:(Contact *)c;
// - (void)contactWasDeleted:(Contact *)c; //may be useful too...
#end
#interface ContactListViewController : UIViewController <ContactDelegate>
#property (nonatomic, retain) NSArray *contacts;
...
#end
#implementation ContactListViewController
#synthesize contacts;
...
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
detailVC.delegate = self;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
- (void)contactWasCreated:(Contact *)c
{
self.contacts = [self.contacts arrayByAddingObject:c]; //I'm not sure this is the correct method signature...
[self reloadContacts]; //may be [self.tableView reloadData];
}
...
#end
#interface ContactDetailViewController : UIViewController
#property (nonatomic, assign) id<ContactDelegate> delegate;
...
#end
#implementation ContactDetailViewController
#synthesize delegate; //remember to don't release it on dealloc as it is an assigned property
...
- (void)createContactAction
{
Contact *c = [[[Contact alloc] init] autorelease];
[c configure];
[self.delegate contactWasCreated:c];
}
...
#end
Technically, you shouldn't!
The whole idea is not for "views" to control what happens to the data.
What you want to do is to pass data between controllers (which I imagine is exactly what you are planning to do anyway).
You can have shared model (an instance of an object that both view controllers would access) keeping the data you want to share,
You can use notifications to pass data (it is best suited for certain cases).
You can write something to disk and read it again later.
You can use NSUserDefaults.
You can use KeyChain.
...
The best way is:
declare the appropriate #property in the second view controller
when you create it, simply set the property with
viewController.property = valueYouWantToPass;
I'm a big fan of delegates and protocols.
And in some occasions use a Singleton pattern.
two ways to pass/share data between view controller
create an object and sent the data like this
QGraduteYr *tableverify=[[QGraduteYr alloc]initWithStyle:UITableViewStyleGrouped];
tableverify.mystring=myString
[self.navigationController pushViewController:tableverify animated:YES];
another method is stor it in the delegates and use it via shared delegates
MedicalAppDelegate *appdelegate=(MedicalAppDelegate *)[[UIApplication sharedApplication]delegate];
appdelegate.collnameStr=collStr;
and ust this appdelegates value whereever you need
I know how to do for share data between 2 views. But if I want to share data using a tabBarController I'm lost.
This is my IBAction to move to my tabBar.
-(IBAction)goToPage2:(id)sender
{
tabController.modalTransitionStyle=UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:tabController animated:YES];
}
i need to share my NSString *dataStr in my IBAction in the first view of my tabBar.
firstView *first = [[firstView alloc] initWithNibName:#"firstView" bundle:nil];
first.dataStr = name.text;
[tabController presentModalViewController:first animated:YES];
this code doesn't work.
thx
Declare a #property in your app delegate. And you can access your app delegate from any point of your app.
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication ]delegate]
I agree with what Terente suggested. But I recommend you should use a Data Class in this case. Using a property in Appdelegate is not a good practice. You should always use Data Class for this.
You create a Data class as follows:
You need to create a Data class where you can set the properties of variables or in your case arrays (for displaying data in UITableView). Implement a class method in data class which checks that object has been instantiated or not. If not, it does that. It is something like this :
//DataClass.h
#interface DataClass : NSObject {
NSMutableArray *nameArray;
NSMutableArray *placeArray;
}
#property(nonatomic,retain)NSMutableArray *nameArray;
#property(nonatomic,retain)NSMutableArray *placeArray;
+(DataClass*)getInstance;
#end
//DataClass.m
#implementation DataClass
#synthesize nameArray;
#synthesize placeArray;
static DataClass *instance =nil;
+(DataClass *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [DataClass new];
}
}
return instance;
}
Now in your view controller you need to call this method as :
DataClass *obj=[DataClass getInstance];
And use the arrays.
This way you can assign data without disturbing AppDelegate, which is a good practice.
I wrote a lengthy tutorial about such issues on my blog: http://www.hollance.com/2011/04/making-your-classes-talk-to-each-other-part-1/
You need to figure out a clean way to let your controllers communicate with each other. My tutorial explains several ways to do this and what the advantages and downsides are of each approach. It's worth learning how to do this, because this issue comes up in almost any app you will write.
Im new to using core data and having really basic problems. Im trying to have the user enter a string and then be able to save that string and allow it to be returned to them at some point. But i cannot seem to get it to save. In fact the program quits when I attempt to run the following method. I can post the rest of my project, but i thought maybe that would be annoying so let me know if seeing it in greater detail would help. Thanks so much.
James
.h: file
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *eventArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
.m file:
-(void)saveAndReturn:(id)sender{
People *userEnteredName = (People *)[NSEntityDescription insertNewObjectForEntityForName:#"People" inManagedObjectContext:managedObjectContext];
[userEnteredName setName:firstName.text];
//NSError *error;
//if (![managedObjectContext save:&error]) {
// This is a serious error saying the record could not be saved.
// Advise the user to restart the application
//}
[peopleArray insertObject:userEnteredName atIndex:0];
}
From the error you gave you must have named the People object differently - in the model are you using "People" for both class and entity name (those can be the same)?
Edit:
After reviewing your code, you had multiple problems:
1) In the app delegate you did "[data alloc]" but no init. That was where you set the managed object context, but it was never used... not just because of the lack of an init but because...
2) The place where the data controller was really built and used from was the rootViewController. That's the one that is actually doing all the work, the one in the app delegate is just discarded.
3) So where to get the context then? Honestly the best spot is in the data controller, one fix I know worked was putting this line before every time the context was accessed:
#import "UserProfileAppDelegate.h"
// Then in the method before the use of context........
self.managedObjectContext = [((UserProfileAppDelegate *)[[UIApplication sharedApplication] delegate]) managedObjectContext];
When that was in place, the project ran. I think though you should put that into something like a viewDidLoad on the data controller (if it has a view that is ever used).
I really need some more help!
I am trying to pass an array from one view controller to another. I think the latter being a 'child' view controller?
My code is as follows:
MainViewController.h:
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#interface HelloWorldIOS4ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, AVAudioPlayerDelegate> {
NSMutableArray *countProductCode;
UIPopoverController *detailViewPopover;
}
#property (nonatomic, retain) NSMutableArray *countProductCode;
#property (nonatomic, retain) UIPopoverController *detailViewPopover;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
...
#end
MainViewController.m
#import "HelloWorldIOS4ViewController.h"
#import "JSON.h"
#import "PopoverContentViewController.h"
#implementation HelloWorldIOS4ViewController
#synthesize detailViewPopover;
#synthesize countProductCode;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *results = [jsonString JSONValue];
NSLog(#"RETURN: %#", results);
[countProductCode removeAllObjects];
NSArray *products = [results objectForKey:#"items"];
for (NSDictionary *row in products)
{
NSString *code = [row objectForKey:#"ic"];
[countProductCode addObject:code];
}
PopoverContentViewController.countProductCodes = countProductCode;
}
PopoverViewController.h:
#interface PopoverContentViewController : UITableViewController {
NSMutableArray *countProductCodes;
}
#property (nonatomic, retain) NSMutableArray *countProductCodes;
#end
PopoverViewController.m:
#import "PopoverContentViewController.h"
#import "HelloWorldIOS4ViewController.h"
#implementation PopoverContentViewController
#synthesize countProductCodes;
...
I have cut a lot out, but I know from a load of NSLog's dotted around that I get the data back etc, but I cannot pass the array countProductCode to the PopoverViewController's countProductCodes array.
I keep getting
"Accessing unknown 'setCountProductCodes:' class method"
errors.
This may be something really silly that I'm doing but it's driving me crazy!
Can anyone help please?
Thanks
James
Dear James,
I think you would like to have a closer look at the Model-View-Controller paradigm. In your app you are trying to implement some kind of a "super class". Let me explain what that means:
In your MainViewController class which is clearly a controller there is also some part of the model implemented. This is a bad idea, but a very common one to make in the beginning. Maybe I misunderstood your design, but here is how I would implement it:
The Model I would implement a proper model object, which could be in your case as easy as a custom NSObject subclass with a NSMutableArray as a property. In addition this model would have the methods for retrieving data off the internet implemented. That is right: do the networking in the model. You would have to have methods like - (void) refreshProductCode that you would call from your controller. If you want to get really fancy, use an NSOperation to encapsulate the download (then you would use the a synchronous variant of NSURLConnection, because the operation itself is already executed asynchronously) The nice thing would be then if your parsing of the JSON string takes longer, also this is performed in the background and your UI will stay responsive.
So now the model is downloading your stuff - great, but how do I know when it is done? Well you would post a Notification from the model once it is done. What if the download fails? You guessed it right: post a notification that it failed.
The Controller The controller which needs to display data from the model would first to get the model object. In this case the model object is a property on your AppController. The controller then has a property for this model object and retains it, so that the model object does not go away while the controller lives. The controller then also registers for notifications of the model. So how would a typical download work then?
Get an instance of the model object
call -(void) refreshProductCode on the model object
display network activity spinner in status bar and wait for notifications
when the notification came in, on success refresh the UI and on failure restart download or display a note to the user. Also disable the network activity spinner.
How do you shuffle data between view controllers? View controllers should operate a bit like the mafia: every view controller is working on a need-to-know basis. For example if you want a view controller to display the details of your product, you would not pass the model with all your products to the controller. Instead you would have an instance variable on the detail view controller holding only one produce model object, which has all the information like description text, photo etc. The cool thing then is if you ever want to display product information again in you app, you can reuse that view controller, as all it needs is a product model object to do its job.
In your code:
PopoverContentViewController.countProductCodes = countProductCode;
should be:
popoverContentViewController.countProductCodes = countProductCode;
Your instance name should be different from the class name.
In the mainViewController class, in the following method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
you are accessing the "countProductCodes" using class name. You should access using its object.
like
PopoverContentViewController *obj = [[PopoverContentViewController alloc] init];
obj.countProductCodes = countProductCodes;
In MainViewController.h:
+(NSMutableArray)arrayRes;
In MainViewController.m
+(NSMutableArray)arrayRes{
return countProductCode;
}
perform any of the code changes on countProductCode array as usually
In PopoverViewController.m
declare #class MainViewController; and in viewDidLoad
NSMutableArray *newArray;
newArray = [MainViewController arrayRes];
Occasional reader and first time question asker, so please be gentle :)
I am creating a Managed Object (Account), that is being passed into a child view controller where its being set in a property that is retained.
Account * account = [[Account alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
AddAccountViewController *childController = [[AddAccountViewController alloc] init];
childController.title = #"Account Details";
childController.anAccount = account;
childController.delegate = self;
[self.navigationController pushViewController:childController animated:YES];
[childController release];
[account release];
The view controller interface:
#interface AddAccountViewController : UIViewController {
}
#property (nonatomic, retain) IBOutlet UITextField * usernameTextField;
#property (nonatomic, retain) IBOutlet UITextField * passwordTextField;
#property (nonatomic, retain) Account * anAccount;
#property (nonatomic, assign) id <AddAccountDelegate> delegate;
- (IBAction)cancel:(id)sender;
- (IBAction)add:(id)sender;
- (IBAction)textFieldDone:(id)sender;
#end
So in code sample 1 I've released the account object because I am no longer interested in it in that method. As it is retained by the AddAccountViewController I have an entry in AddAccountViewController's dealloc that releases it.
However when I go to delete the object from the ManagedObjectContext the app crashes with the following (rather unclear) error:
Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData.
After much debugging & hair pulling I discovered that if I don't release account in AddAccountViewController's dealloc method the app works properly continually and doesn't appear to leak according to Instruments.
Can anyone shed any light as to whats going on? I understand from the docs on properties that those retained need to be released. What have I missed?
Update to answer Kevin's question
The code to delete the object from the ManagedObjectContext is in the RootViewController (that holding the child controller)
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
Firstly: It sounds like a bug on Apple's part. Core Data is calling _Unwind_Resume, which is (probably) some sort of exception unwind. Exception-unwinding exists on the phone, but (I think) uses the ARM ABI, which uses function names beginning with __cxa_. Are you running on the simulator? Which version of the SDK?
There might be an extra release floating around somewhere which is "balanced" when you remove the call to [account release];.
"Instruments doesn't show any leaks" doesn't mean there aren't any; last I checked it got confused by cycles (i.e. it wouldn't show a leak if you forgot to un-set IBOutlets in dealloc). I tested with NSMutableData * d = [NSMutableData dataWithLength:1<<20]; memcpy(d.mutableBytes, &d, 4);, but an easier test is just [[UIView alloc] initWithFrame:CGRectZero].
If you think it's a retain/release issue, I once debugged these by overriding retain/release/autorelease to call NSLog. I then added breakpoints on all of them, set them to run the command "bt", and clicked the autocontinue. Then run the thing that breaks (in my case I think it was just an extra retain), print out the log output,stick it on a whiteboard, and spend half an hour matching retains and releases.
I had a similar issue ending in a
"Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData."
error message.
My problem was a wrong "cascading" deletion-rule on a relation in the model. With this rule, my top managed object got deleted but still referenced in the code.
After setting the "delete rule" on this relation to "nulify", everything worked as designed.
--> no core data issue...design issue!
Johnny
When ever you delete any managedobject, system will automatically release all reference related to that object. So there is no need to realese object programatically. Once you delete object there you can not access that object in parent class.