i'm doing some testing of Core Data, let's say i have a mainViewController with a navigationBar and addButton.
Clicking on the addButton will open a detailViewController. When i press save to insert a new Object the detailVieController will close and show the table with the new data inserted.
I can think two different way to do that.
FIRST METHOD - Passing the ManagedObjectContext
In the action of the add button i create an instance of the new detailViewController and i pass the managedObjectContext to it. So will be the save button of the detailViewController that will take care of saving the context and then pop the controller.
This is the method called by the addButton in the MainViewController
-(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]initWhit:self.managedObjectCOntext];
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
SECOND METHOD - Using a delegate
In the action of addButton i create an instance of DetailViewController, i set it as delegate, so when i press the save button in the DetailViewCOntroller will call the delegate that will pass data to the main controller.
This is the method called by the addButton in the MainViewController
(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]init];
detVC.delegate = self;
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
[self.delegate detailVCdidSaveObject];
}
This is the delegate implemented in the mainViewController
detailVCdidSaveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
------------------------------ Passing the object
Is it best to pass raw data to the DetailViewController and create there the object or it's best to pass the instance of the object to DetailViewController that will take care of settin its data?
For Example
This way i link the object instance of the mainVC to the one DetailVC so i can easilly set its value
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]init];
detailVC.delegate = self;
self.object = [NSEntityDescription insertNewObjectForEntityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
detailVC.object = self.object;
[self.navigationController pushViewController:detailVC animated:YES];
}
this way i pass raw data and let the detailVC create the instance
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]initWithName:#"objname"];
[self.navigationController pushViewController:detailVC animated:YES];
}
those code are just pseudocode for educational purpose. all ways works, i just want to know which do you think it's the most correct and why. thanks
I have used the first two methods and in my opinion they are both equally valid (though I personally prefer delegation). However, the third method caused problems if you give the user the option to cancel or go back in a navigation controller. If that happens, you will have an object that you never needed to create.
This sounds like a perfect use case for a NSFetchedResultsController. A NSFetchedResultsController is an object makes displaying data from core data in a UITableView a lot easier. It even tells you when the objects in core data matching a predicate change (insert, delete, update, move).
So the way I would do it is that MainViewController would have a NSFetchedResultsController that provides the data to the UITableView. When you press the add button, it would do what you have in the first method. The DetailViewController will create the new instance, set the values on it then save the managedObjectContext.
Since the MainViewController has the NSFetchedResultsController, it will automatically know that a new object have been created and it can update the UITableView to show it.
The NSFetchedResutsController documentation and the NSFetchedResutsControllerDelegate documentation show you exactly how to use it with a UITableView including code you can copy into your view controller that do the majority of the work.
The actual answer depends on your preference. In my project, I have implemented the first two methods. A definite No for the third method from my side because of same reasons as Kevin mentioned. If the user cancels the operation or some error occurs, then you will have to take care of removing the change (Perhaps write the following code in your didMoveToParentViewController method and cancel method):-
[self.managedObjectContext rollback]
Assuming of course that you do not have any other process modifying that managedObjectContext at the same time.
Now, I prefer the first two methods because :-
The first method allows me to write additional code in saveObject method. Lets say that you want to validate some properties before saving the object. These properties are only present in detailViewController. So, you cannot use a delegate in that situation without explicitly passing each and every property back to delegate function (which can get messy).
Now, assume that you are creating a object in your mainViewController and the detailViewController is only used to populate a field of the object that was created in mainViewController. In such a situation, I would use the delegate method and pass the field back to the mainViewController so that when the user saves the object in mainViewController, then the field values are saved along with it. If the user cancels mainViewController, then the field values are also not saved.
Related
SettingsStore.h
#interface SettingsStore : IASKAbstractSettingsStore
{
#public
NSDictionary *dict;
NSDictionary *changedDict;
}
- (void)removeAccount;
#end
menuView.m
-(IBAction)onSignOutClick:(id)sender
{
SettingsStore *foo = [[SettingsStore alloc]init];
[foo removeAccount];
[self.navigationController pushViewController:foo animated:YES];
exit(0);
}
I want to call this removeAccount function from menuView.m. But I am getting error.
How to fix it and call this removeAccount.
There are few mistakes in your Code please find them below.
[foo removeAccount]; Calling this method is correct
[self.navigationController pushViewController:foo animated:YES];
Not correct because SettingsStore is not subclass of
UIViewController only subclass of UIViewController can be pushed to
Navigation controller
exit(0); Calling this method is not
recommended by Apple
You are calling removeAccount correctly from your menuView.m file, but there are several issues with your code:
You are treating foo as though it were a UIViewController, and it's actually a member of the SettingStore class. Does the SettingStore class refer to an actual screen, or is it more a data object (for storing settings?). If it's the latter, you don't want to push it on. You can create it, and use it, but the user doesn't need to see it.
You are calling exit(0); you can remove that line. If you want to remove the menuView.m file from your memory, remove references to it (e.g. from its parent view controller).
The menuView.m file is confusing, as in, is it a view or a viewController. An IBAction I would normally stick in a ViewController file, rather than a view file. Your basic design pattern is MVC (Model / View / Controller). In this case, it seems your SettingStore file is a Model (data), the menuView.m is a View and your code is for the Controller bit.
I am trying to reload data in a tableview based on a users account permissions whenever they log in.
The two classes involved in this are:
mainViewController and menuViewController
Currently I am able to use
[self.tableView reloadData];
To reload the data when called within the viewWillAppear method. Which is no good for me since the user hasn't logged in when the view loads so there is no data to populate the table at this point.
I have created a method called populateTable in menuViewController.h which I am calling in the mainViewController.m file on button press using the following;
(IBAction)Reload:(id)sender {
menuViewController *mvc = [[menuViewController alloc]init];
[mvc populateTable];
}
This seems to work correctly as I have an NSLog within the populateTable method which executes. However the reloadData does not work.
Here is my populateTable method;
-(void)populateTable {
self.section1 = [NSMutableArray arrayWithObjects:#"test settings", #"test", #"test",#"Users and access",#"No-track IPs", nil];
self.section2 = [NSMutableArray arrayWithObjects:#"Rules", #"Channels",#"Goals",#"Pages", nil];
self.menu = [NSMutableArray arrayWithObjects:self.section1, self.section2, nil];
[self.tableView reloadData];
NSLog(#"Reloading data");
}
Can you guys help me out here, I have been staring at this all day and getting nowhere, thanks!
From my experience this is likely a problem with timing - the IBOutlet of self.tableView is not ready when you call reloadData on it (add an NSLog and see for yourself - it is nil when called).
To solve this, the populateTable method must be called within the UIViewController's viewDidLoad method. This guarantees that the outlets are not nil and that everything is ready for your data population.
Also, you should not instantiate your MenuViewController with [[MenuViewController alloc] init] but using the storyboard's instantiateViewControllerWithIdentifier.
Your problem is this line,
menuViewController *mvc = [[menuViewController alloc]init];
This creates a new instance of menuViewController, not the one you see on screen. You need to get a reference to the one you have, not create a new one. How you get that reference depends on how, when, and where your controllers are created.
how can I add an object in my array each time I enter to my TableView
I put this code in viewDidLoad and viewDidAppear methods but it seems to doesn't work
:
- (void)viewDidLoad
{
[super viewDidLoad];
if (!myArray) {
myArray = [[NSMutableArray alloc] init];
}
[peopleListe insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
NSLog(#"%#",myArray);
}
when I put this code in a button it works
Thank you for your help
viewDidLoad is called once, when the view loads.
viewWillAppear is called every time you go into that view.
If you want to do something each time a view appears, put the code in viewWillAppear.
EDIT: It's possible that your array is getting dealloc'd. Try setting a breakpoint in dealloc as a simple way to see if that's the case:
- (void)dealloc {
NSLog(#"BYE!); // <-- put your breakpoint here
}
If it is, you'll have to (a) store your data somewhere else, or (b) keep this view/controller from being dealloced.
Also, who is your tableViewDelegate? That will have to implement methods returning the number of items in the table view and so on. I recommend having a read through the docs to get all those relationships sorted out.
You don't want to have that array as a property/ivar of your view controller. The view controller may, and will, get deallocated when it's not used (e.g. if it's inside the navigation controller, and you tap the "back" button to go to the previous screen.) When the view controller gets deallocated, your array obviously ceases to exist.
I suggest creating keeping that array in a separate place, e.g. in a singleton data object, or even (as a quick short-term solution) your app delegate.
About the code you posted: keep in mind that [UIViewController viewDidLoad] is only called once during the view controller's lifecycle. It may get called more than once, but that would mean that the original instance has been dealloc'd (and your original array is gone).
I have a very complex situation (well for me) I am trying to resolve but thus far am having trouble with it.
I will outline the structure of the application now and then explain the problem I am having.
The names I am using are made up due to sensitivity of the data I am using.
secondToLastViewController // is a UITableView on the navigation stack
lastViewController // is just a normal UIView that i want to push onto the navigation stack
RequestClass // this class dose requests to my database and passed the data back to correct classes
getInfoClass // class is used for this specific request stores the information correctly and passes it back to secondToLastViewController
So as the user initiates didSelectRowAtIndexPath inside secondToLastViewController I make a request for the data using the RequestClass
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
[RequestClass Getinfo:storedInfoPram];
}
now the thread shoots off to my RequestClass, which in turn queries the DB for some data which is then received and this data is passed off to my getInfoClass the reason I have done this is because there are dozens and dozens of different calls in RequestClass all doing different things, this particular request brings back alot of data I have to sort into correct object types so have created this class to do that for me.
anyway inside getInfoClass I sort everything into their correct types etc and pass this data back to secondToLastViewController in a method called recivedData, this is also where I think things are going wrong... as I create a new instance of secondToLastViewController the thing is I dont know how to pass the data back to the same secondToLastViewController that is already on the stack and was where the original request came from.
- (void) recivedData {
// do some stuff then pass data back to secondToLastViewController
SecondToLastViewController *sec = [[SecondToLastViewController alloc] init];
[sec sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5];
}
Now going back into SecondToLastViewController the thread lands in this method
- (void)sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5{
// call detailed view onto the stack
lastViewController *last = [[lastViewController alloc] initWithNibName:#"lastViewController" bundle:nil];
[self.navigationController pushViewController:last animated:YES];
}
after the thread reaches this point nothing happens... all the data is there and ready to be sent but the new view is never pushed to the controller stack.. and I think it is due to me declaring another version of secondToLastViewController when I am inside getInfoClass
what I would like to know firstly is how do I pass the recived data in sendGetSeriesArrays to the final view and secondly how do i even load the lastview onto the navigation stack?
Your observation is correct you are creating the secondToLastViewController instance again inside the getInfoClass. Dont do like that you have to use delegate/protocol approach for passing the data back to the secondToLastViewController.
Do like this
Define a protocol in getInfo class
getInfoClass.h
#protocol GetInfoClassProtocol <NSObject>
//delegate method calling after getting data
// I dont know the argument types give it properly
- (void)sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5;
#end
// declare the delegate property
#property (assign, nonatomic)id<GetInfoClassProtocol>delegate;
getInfoClass.m
- (void) recivedData {
// do some stuff then pass data back to secondToLastViewController
if ([self.delegate respondsToSelector:#selector(sendGetSeriesArrays: param2:)])
{
[self.delegate sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5];
}
}
secondToLastViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
RequestClass.delegate = self;
[RequestClass Getinfo:storedInfoPram];
}
Your secondToLastViewController should conform to the GetInfoClassProtocol
There are lots of ways you can accomplish this. In your revivedData function, instead of creating a new instance, you could:
1) Maintain a pointer to the navigation controller in getInfoClass, then you can get the last view controller from the view controllers on the navigation stack and use that. This will be the active instance of the view controller. There are ways to recover this from the window object, but those seem fragile and I would not recommend that approach.
2) You can pass a pointer to self from secondToLastViewController to your RequestClass getInfo call, then hold on to that and pass it back. This is probably a pain depending on the amount of code you have already.
3) You can maintain a static instance of the class if you will never have more than one secondToLastViewController. See How do I declare class-level properties in Objective-C?
In my project I have 3 controllers;
NavigationController
ServiceTableViewController
DateTableViewController
The ServiceTableViewController is the initial view controller. It has several rows which prompt the user to enter in data, which will be emailed to a particular email address. One of the rows, when tapped, sends the user to the DateTableViewController which prompts the user to select a date from the UIDatePicker.
The issue I am facing is getting data back from DateTableViewController in order to display a label on the ServiceTableViewController to show the date the user selects in the DateTableViewController. I know how to get information from one view controller to another, but to go in reverse, so to speak, is not something I know how to do. Any help is appreciated.
Take a look at this:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/CommunicateWithObjects.html
There are couple of ways to pass data back and forth between view controllers.
Delegates
Target-Aciton
Notification
KVO
but honestly delegates are really all you need really and it sounds like in your current case.
see this -> (Passing Data between View Controllers)
Having said that, if you use delegates, here is how ---
setup a protocol in DateTableViewController.h at the top like so:
#protocol DateTableViewControllerDelegate <NSObject>
- (void)userSelectedThisDate:(NSDate *)d;
end
put this with the other properties
#property (nonatomic, weak) id <DateTableViewControllerDelegate> delegate;
and in DateTableViewController.m with the date to send back
[self.delegate userSelectedThisDate:withTheDateToSendBack];
in and ServiceTableViewController.h add
#import "DateTableViewController.h"
#interface ServiceTableViewController : UIViewController <DateTableViewControllerDelegate>
and since you are UINavigationController, somewhere in ServiceTableViewController.m add this when you are about to push to the DateTableViewController
DateTableViewController *vc = [[DateTableViewController alloc] init];
self.delegate = self;
[self.navigationController pushViewController:vc animated:YES];
and finally put the delegate method in ServiceTableViewController.m
- (void)userSelectedThisDate:(NSDate *)d {
NSLog(#"%#", d); // this should show the returned date
}
Research delegate pattern (here) (a heavily used pattern within Apple frameworks). You want to define a delegate protocol which allows to a date to be passed to the delegate.
You could implement the pattern as an #protocol with a single method and a property on the DateTableViewController. The ServiceTableViewController sets itself as the delegate before pushing the DateTableViewController.
Or, you could implement using a block. Again, the ServiceTableViewController sets the block before pushing the DateTableViewController.