why does this code use presentModalViewController? (not pushViewController) - iphone

Anyone understand why in the CoreDataBooks example code that:
(a) method for controller swapping difference
Whilst the click an item and go to detailed view uses what seems to be the standard UINavigationController concept of "pushViewController", that when when you click on the "Add" a new record button it launches the new view to add the record via "presentModalViewController" approach? That is, couldn't the approach have been the same in both cases, just using a pushViewController approach?
Are there actually any advantages to using each approach for where it's been used? I can't quite see. I'd guess there must have been something for Apple to choose these different approaches for different scenarios. For example:
any differences to the user (i.e.
UI differences or functional
differences) that they would see?
any differences for the developer
(or advantages/disadvantages)
For example, if you were to consider using pushViewController approach instead of the presentModalViewController approach for the for the "Add" scenario...
(b) data sharing approach difference
the approach to how they share the common data object seems to be different - so again just wondering why the approaches weren't the same? (i.e. in both cases the main controller is passing off to another view temporarily and there is some shared data between them - i.e. that the child view needs to pass back to the parent)
Code Extract for Convenience
That is for "Edit":
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Create and push a detail view controller.
DetailViewController *detailViewController = [[DetailViewController alloc] initWithStyle:UITableViewStyleGrouped];
Book *selectedBook = (Book *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
// Pass the selected book to the new view controller.
detailViewController.book = selectedBook;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
But for "Add"
- (IBAction)addBook {
AddViewController *addViewController = [[AddViewController alloc] initWithStyle:UITableViewStyleGrouped];
addViewController.delegate = self;
// Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addingManagedObjectContext = addingContext;
[addingContext release];
[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
addViewController.book = (Book *)[NSEntityDescription insertNewObjectForEntityForName:#"Book" inManagedObjectContext:addingContext];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[addViewController release];
[navController release];
}
thanks

You use modal view controllers to focus the user's attention on a Task. When you push, the user is in some kind of navigation flow, but still has the total application at their fingertips. They might decide to go forward or backward, switch to a different tab in the middle, whatever. When they get a modal view controller, they can't do any of that until the task is completed or canceled out of (the modal view is dismissed)

[Warning: this answer applies more to the updated code of the CoreDataBooks, which has changed to use the new-in-iOS5 setParentContext method of NSManagedObjectContext instead of messing with the persistentStoreCoordinator[
Your 2nd question about data sharing is also answered by the modal Add vs modeless Edit approach. Run the app in the simulator and notice that:
if you click on Add your next view has both Save and Cancel buttons
if you click on Edit your next view has only a Done button.
(Now, in this particular project, you have to edit each field at a time and the field editing is done in yet another view, and that one has a Cancel button, but ignore that for now, because
a. this only applies to the field. E.g. If you edit a Title and hit Save, you're back at the Edit view with the Done button, now there's no cancel to undo that change, you can only hit Done. As far as this view is concern, you've edited the Book modeLESSly
b. What a lame UI! Come on Apple, make the CoreDataBooks into a decent, albeit simple app that follows your own conventions. At least put the editing in the cells.)
Where were we? Oh yeah, "Edit"-ing an existing Book is modeLESS, so it passes the original Book in the same MOC (NSManagedObjectContext) and you can't cancel your edits to it in the Edit view.
"Add"-ing a Book, on the other hand is MODAL: It creates a new Book for to be edited in the detail view, and wants to discard it if the user hits cancel. To achieve this it has to use a second MOC, which is a child of the first. If the user Cancels, it simply ignores the new child MOC, effectively discarding the new Book; if the user Saves, it saves the child MOC, which pushes the new Book and its properties up into the parent MOC, then saves the parent MOC.
This child-MOC approach, btw, is detailed in the WWDC 2011 presentation 303 "What's new in Core Data on iOS".
There are other approaches discussed elsewhere in SO, including
Creating a new managed object with a nil MOC, and only inserting it in the parent MOC when the user hits save
Not using a managed object but a different data structure for the temporary object (the new Book that we're not sure we want to save yet), such as an NSDictionary, or just a set of different variables
and more... ?
I kind of prefer the parent-child approach because Apple favours it and because it makes use of the data model objects instead of creating parallel data structures for temporary objects.
The nil-context approach also has that benefit, and the added benefit of (apparently) better performance and simplicity (read my lips: no new MOCs). But I'm not convinced that managed objects without managed object contexts are kosher.
By the way, CoreDataBooks doesn't exactly follow the convention laid down in the aforementioned presentation, because it doesn't save the parent context in a performBlock block.
Also I'm not sure why it sets the new managed context as a property on the AddViewController and doesnt use it.

Related

Refreshing the content of TabView

Ok I am trying to refresh the tab content of each of my tabs after a web call has been made, and I have tried soo many different methods to do this that I have lost count. Could someone please tell me how this is possible?
The web call just calls JSON from a server and uses it to update the content of the tabs. For testing purposes I have a button set up inside my settings class. Settings class is a view within the home tab which has a button called refresh. When clicked this takes JSON stored on the device which is different to the one called from the web call on application start up. This saves me having to change the JSON on the server.
I will take you through some of the techniques I have tried and would be grateful if someone could tell me what I am doing wrong.
I tried making an instance of the class and calling the refresh method like this
DashboardVC *db = [[DashboardVC alloc] init];
[db refreshMe];
The refresh method in dashboard class is this
-(void) refreshMe
{
[self loadView];
[self viewDidLoad];
}
However no luck. This method will work if I call it inside the Dashboard class, but wont work if I call it from another class. I think it is become I am instantiating a new class and calling refresh on that. So I dropped that technique and moved onto the next method
This loops through all the tabBars and changes the tabTitles without any issues, so it I know it is definitely looping through the ViewControllers properly.
I also tried every varient of the view methods like ViewDidAppear, viewWillAppear etc, no luck.
I also tried accessing the refreshMe method I made in the dashBoard class through the tabController like this
[[[self.tabBarController viewControllers] objectAtIndex:0] refreshMe];
But again no luck, this just causes my application to crash.
I read through this guide
https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerPGforiOSLegacy/TabBarControllers/TabBarControllers.html
on the apple website but it doesn't seem to cover how to refresh individual tab content.
All I want is to have each individual tab refresh its content after the web call is made, and have spent ages trying to figure this out, but nothing is working.
So would be very grateful if someone could show me what I am doing wrong?
Thanx in advance....
EDIT:
Expand on what I have tried
After discussion with Michael I realised you should never call loadView as against Apple guidelines. So I removed any references to LoadView. I have now placed a method in all the main ViewControllers called RefreshMe which sets up the views, images texts etc in the class. And this method is placed inside the ViewDidLoad. Now I want to be able to call these methods after a web call has taken place, so effectively refreshing the application.
My viewDidLoad now looks like this in all my the main classes.
- (void) viewDidLoad {
[super viewDidLoad];
[self refreshMe];
}
And then the refreshMe method contains the code which sets up the screen.
The JSON data pulled from the web call will set up the content of each of the 5 tabs, so need them all to be refreshed after web call.
I tried looping through the viewControllers and calling viewDidLoad, which should in turn call the refreshMe method which sets up the class, however nothing happens. Code I used was this
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
For the time being I have also included
NSLog(#"Method called");
in the viewDidLoad of each class to test if it is being called. However the message is only being printed out when I first load the application or if I re-enter the application. This method should be called after I click the refresh button in the settings screen but it isn't and I have no idea why.
Anyone have any idea why this is not working?
From the question and your comments, it sounds like there are at least two problems:
You're having trouble accessing the view controllers managed by your app's tab bar controller.
You seem to be working against the normal operation of your view controllers.
The first part should be straightforward to sort out. If you have a pointer to an object, you can send messages to that object. If the corresponding method doesn't execute, then either the pointer doesn't point where you think it does or the object doesn't have the method that you think it does. Let's look at your code:
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
This code is supposed to call -viewDidLoad on each of the view controllers managed by some tab bar controller. Leaving aside the wisdom of doing that for a moment, we can say that this code should work as expected if self.tabBarController points to the object that you think it does. You don't say where this code exists in your app -- is it part of your app delegate, part of one of the view controllers managed by the tab bar controller in question, or somewhere else? Use the debugger to step through the code. After the first line, does tabBarViewControllers contain an array of view controllers? Is the number of view controllers correct, and are they of the expected types? If the -viewDidLoad methods for your view controllers aren't being called, it's a good bet that the answer is "no," so figure out why self.tabBarController isn't what you think.
Now, it's definitely worth pointing out (as Michael did) that you shouldn't be calling -viewDidLoad in the first place. The view controller will send that method to itself after it has created its view (either loaded it from a .xib/storyboard file or created it programmatically). If you call -viewDidLoad yourself, it'll either run before the view has been created or it'll run a second time, and neither of those is helpful.
Also, it doesn't make much sense to try to "refresh" each view controller's view preemptively. If your app is retrieving some data from a web service (or anywhere else), it should use the resulting data to update its model, i.e. the data objects that the app manages. When a view controller is selected, the tab bar controller will present its view and the view controller's -viewWillAppear method will be called just before the view is displayed. Use that method to grab the data you need from the model and update the view. Doing it this way, you know that:
the view controller's view will have already been created
the data displayed in the view will be up to date, even if one of the other view controllers modified the data
you'll never spend time updating views that the user may never look at
Similarly, if the user can make any changes to the displayed data, you should ensure that you update the model either when the changes are made or else in your view controller's -viewWillDisappear method so that the next view controller will have correct data to work with.
Instead of refreshing your view controllers when updating your tab bar ordering, why not simply refresh your views right before they will appear by implementing your subclassed UIViewController's viewWillAppear: method?
What this means is that each time your view is about to appear, you can update the view for new & updated content.

Working with the same NSManagedObjectContext in multiple tabs

I have a tab bar controller with different view controllers all using the same managed object context, being set up as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
RootViewController *rootVC = [[RootViewController alloc] initWithStyle:UITableViewStyleGrouped];
rootViewController.managedObjectContext = self.managedObjectContext;
UINavigationController *rootNavCon = [[UINavigationController alloc] initWithRootViewController:rootVC];
[rootVC release];
SettingsTableViewController *settingsVC = [[SettingsTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
settingsVC.managedObjectContext = self.managedObjectContext;
UINavigationController *settingsNavCon = [[UINavigationController alloc] initWithRootViewController:settingsVC];
[settingsVC release];
tabBarController = [[UITabBarController alloc] init];
NSArray *controllers = [NSArray arrayWithObjects:rootNavCon, settingsNavCon, nil];
tabBarController.viewControllers = controllers;
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
return YES;
}
The idea is similar to the Recipes sample code if there were another tab called Settings which offered an option to managed the Category objects. The problem is that if you navigate to the view where the user can select the Category, but then go to the settings tab and delete, add, or edit a Category, when returning to the Recipes tab the changes will not be immediately reflected. Thus selecting a deleted Category would raise an exception.
What is the best way to deal with this? I was thinking about setting up an NSNotification to alert the views whenever an important change had occurred, but wasn't sure if there is a better way to do this, such as querying [managedObjectContext hasChanges] when a view appears. (Although that wouldn't seem to work if the context had already been saved.)
There's really no answer to your question other than "It's a design decision that you must make." Think carefully about your use cases and do the appropriate thing.
In your sample code, you are pushing a global managed object context into each controller. Each controller then does something with that managed object context, presumably. One thing that is not clear from the code is whether you are pushing additional view controllers onto the navigation stack or modally displaying other views. My assumption is that you are, and that any of the controllers and associated views can mutate a managed object.
In this situation, using a single managed object context might not be what you want when used with a tab view controller. Tabs allow the user to freely roam around the application so that he or she may see the appropriate view for whatever action he or she wants to perform. Allowing them to modify managed objects from a single context will potentially show changes in the wrong places, or cause an assumption to fail. It sounds like this is the situation you are in.
My advice to you would be to create multiple managed object contexts for each workflow that could mutate data. Then I would guess that you could share one managed object context for all read-only screens. Another alternative would be to use a managed object context dedicated for each tab. This would ensure that changes are made in isolation, and propagated out whenever necessary, but you will have to register the contexts to receive change notifications and then merge those changes as appropriate.
To illustrate, picture that the following diagram is an iPhone with multiple tabs, and each respective controller is handling some activity at given times.
Tab A: Controller M |--------------- ,------->
Controller N `-------+
| via NSNotificationCenter
v
Tab B: Controller O |-----------------------+-------->
Controllers M and O are members of the viewController array in the tab bar controller. The user starts on Tab A looks around, then navigates to Tab B. Then the user goes back to Tab A an pushes Controller N onto the navigation stack of Tab A and makes some change (like your your category deletion example). The deletion may or may not need to be propagated to Tab B. You can make this happen by having two managed object contexts where one listens for change notifications that the other has broadcasted via the notification manager.
Determining these use cases is probably the hard part, because I'm sure you understand how NSManagedObjectContext works in general (if not you should spend some time understanding it more thoroughly). As you can see from that simple diagram, for an extremely simple use case it introduces another dimension of complexity. So my point is that you must plan in advance if you don't want to have to tear down and reconstruct bits and pieces of your code base as it matures.
If you want changes to your managed object context to be propagated to your interface automatically and you're using table views (or even custom views), you could be using NSFetchedResultsController. This class watches a context for changes and triggers its delegate methods, allowing you to reload your views only when necessary.

IPad Clear all fields back to start

Im creating an internal ipad app for which will allow our employees to fill in details on a site visit they have just performed.
One of the pages in the app is a massive form where the user enters all the information. I have a reset button on this form. This button will just clear all textfields, textviews, uncheck checkboxes etc etc.
Is there a clean way to reset a view to the state as if it is brand new (not-dirty). I dont really want to go through every control on the view and set it back to nothing.
Is there a way to wipe the entire view and restart again?
Thanks in advance
I think the easy-way to do this is by,
-(IBAction) reset
{
YourView *obj=[[YourView alloc] initWithNibName:#"YourView" bundle:[NSBundle mainBundle]];
[self presentModalViewController:obj animated:NO];
[obj release];
}
I think the easiest way to do this is to discard your view (or the sections of it you want to reset) and recreate them. That might be as simple as:
//assuming you have a nib file containing some custom FormView class with your current view controller as its owner and the FormView instance in the nib bound to a 'formView' property on the controller
[self.formView removeFromSuperView];
[[NSBundle mainBundle] loadNibNamed:#"FormView" owner:self options:nil];
[self.view addSubView:self.formView];
//keep a reference to the old formView first and animate the transition as you like
More complex but possibly worthwhile could be to have your view objects use KVO to watch for changes to some model object exposed as a property through a delegate or on a superview. That's handy if you want the view to be able to automatically update itself in response to changes to the model coming from other parts of the view or some external source like network updates. A "reset" could then be as simple as replacing that value of the property the views are observing with a new instance of your model.

Adding Objects to persistent store from user input

Ok, lemme start out by saying Im new to this! LOL I have done my due diligence of studying the topics (4 books and numerous videos to date) and searching for hours on end, and still havent found an answer.
I feel like I have a solid understanding of Core Data, or at least the back end DB side of it. I have my app built and I have my model built. My app has a Tabbar controller as well as a navigation controller for each separate tab.
My app will have an item table view which populates the name of those items from Core Data. Upon selecting the item, the navController pops to a detail view which loads the rest of the data for that item.
When a user clicks + to add an item, I need to pop to another View Controller with fields to add the name and details (which it does). However, I cant seem to get these details to save. I think I need to cast the user inputs as an NSSet, then bring that NSSet into the persistent store, but the method declaration for this is eluding me! Currently, my code looks like so...
- (IBAction) save:(id)sender {
NSLog(#"Save pressed");
if (itemName != nil) {
[itemName removeObject:itemName];
self.item = nil; //This will release our reference also
}
//Create a new item set for the new values
NSSet* newItem = [[NSSet alloc] initWithSet:newItem];
[self didChangeValueForKey:#"itemName"];
[self didChangeValueForKey:#"detailItem1"];
[self didChangeValueForKey:#"detailItem2"];
//Add it to the master item array and release our reference
[itemArray addObject:newItem];
[newItem release];
//Sort the array since the name might have changed with an existing item or a new one
NSSortDescriptor *nameSorter = [[NSSortDescriptor alloc] initWithKey:#"itemName" ascending:YES selector:nil];
[itemArray sortUsingDescriptors:[NSArray arrayWithObject:nameSorter]];
NSLog(#"Array sorter");
[nameSorter release];
//then pop the detailed view controller
[self dismissModalViewControllerAnimated:YES];
}
All of the documentation I have found on Core Data points more in the direction of populating an already existing database, not accepting user inputs. So if I am WAY off in my approach and the answer is more than just a simple one, please point me in the right direction!!
Also, Ive added items to my Core Data store that successfully persist. However, an sqlite DB hasn't been created in my app, which I thought happened automatically. So I may have more problems than I thought!
So far I have found this site to be a tremendous help, even though my reputation doesnt allow me to rate answers!
Anyway, thanks in advance for the help.
In most Core Data implementations, you don't deal with sets directly unless you are simultaneously adding multiple managed objects to a relationship. I'm not really sure what you are trying to do here.
In the code shown, you don't do anything related to Core Data. You do not have a context and you do not insert into the context a new managed object that you can populate with your new data. In fact, you don't seem to have any managed objects at all.
I suggest you look at the Navigation based project template in Xcode. It shows how to set up the Core Data stack and how to add and remove objects displayed in a tableview.

Best way to call other class view in iphone?

Generally i call my other class view by creating a pointer of delegate and then call the other class by using its link as below:-
First Way :-
Mydelegate *ptr = (Mydelegate *)[[UIApplication sharedApplication]delegate];
[self.navigationController pushViewController:ptr.NextClasspointer animated:YES];
Second Way :-
Create a pointer of that class which u want to call :--
NextClass *nextptr = [[NextClass alloc]initWithnibName:#"NextClass" bundle:nil];
[self.navigationController pushViewController:nextptr animated:YES];
[nextptr release];
nextptr = nil;
These above two methods i generally used but my problem is that which one is best for big project so that my stack problem will be removed I mean memory issue will be solved.And
is it necessary to release pointer in first and second case is the way i release is correct or wrong
Please help me
Thanks in Advance
In terms of memory management, there isn't a difference between these two - both enable you to either have memory leaks or not depending on the rest of your code!
However, bear in mind for either of these, each time your user wants to "drill down" another level in the navigation it will have to load the view from the nib file; conversely, each time they go "back up" the navigation stack these will be unloaded. Generally if my users are likely to be going up and down the navigation stack all the time I retain a single instance of the "child" view controller and push that back onto the stack whenever the user wants to go to it.
If you are having memory problems, I think it is likely caused by objects used by your NextClass not being released properly on unload/release. Double check that all objects that you're synthesizing/alloc'ing in the the NextClass object are being released in the ViewDidUnload method.