iPhone: Using a Singleton with Tabview Controller and Navigation Controller - iphone

I have developed a small iPhone application by using singleton that I use to navigate through the views. Here is a sample method from my singleton class.
+ (void) loadMenuController:(NSMutableArray *)menuItems{
MenuViewController *menuViewControler = [[MenuViewController alloc] initWithNibName:#"MenuViewController" bundle:nil];
[menuViewControler setMenuItems:menuItems];
RootViewController *root = (
P2MAppDelegate *appDelegate = (P2MAppDelegate*) [[UIApplication sharedApplication] delegate];
UINavigationController *navController = [appDelegate navigationController];
[navController pushViewController:menuViewControler animated:YES];
[menuViewControler release];
}
Now my requirement has changed to require a tab view controller . I could change my application delegate to a tabview controller but I still need to navigate inside each tab. I am unable get a clue how to navigate from my singleton class.
Please guide me. Please let me know if my query is not clear.
Thanks in advance.
Regards,
Malleswar

You shouldn't be using a singleton to manage the interface and even if you did, you wouldn't put the UI logic in a class method. You need to rethink your design from scratch.
The normal pattern is to hold the navigation controller or the tabbar controller as an attribute of the application delegate. The app delegate itself should not be a subclass of any controller but just a NSObject subclass that implements the application delegate protocol.
Look at the Apple supplied template projects in Xcode to see the quick and dirty way to structure apps built around navigation and/or tabs.
Singletons should only be used when you have to ensure that one and only one instance of class is alive at one time. You don't need to make your own singleton to manage the UI. The application delegate is attached to the application object which is itself a singleton. This means the app delegate provides all the restriction on class for the UI you might need. You don't need another singleton in addition to that.
Overuse of singletons is dangerous and can cause your design to get trapped in a dead end resulting in a massive rewrite. Think carefully before employing them.

Related

iOS - Accessing viewControllers on storyboard from appDelegate

I've got an app using Core Data where I'm creating a managedObjectContext in the app delegate.
I want to pass that managedObjectContext to two view controllers on my storyboard so they are using the same managedObjectContext to save and fetch to and from.
I can access the first view controller with:
self.window.rootViewController
But the second view controller I want to access is then after a segue from the first and no reference is returned from it.
I tried:
instantiateViewControllerWithIdentifier:
But that creates a new instance of the view rather than allowing me to access the second view controller that appears after the segue.
So my question is, how can I access the second view controller?
Or (as I'm very new to this) is there a better way to be managing/passing the data between the view controllers?
Thanks in advance.
Or (as I'm very new to this) is there a better way to be managing/passing the data between the view controllers?
It depends on the data you're trying to pass around. In this case, you want to give your view controllers access to your Core Data managed object context. Because this is something you're going to need throughout the lifespan of your app it would be better to have your view controllers access it via your application delegate.
You can do this via [[UIApplication sharedApplication] delegate] - however, you may need to typecast it to avoid compiler warnings, or alternatively you might want to create a macro that returns the managed object context to save you time and make your code a little more readable.
If you told XCode you wanted to use Core Data when you created the project you should have the methods to retrieve your object context already in your app delegate. If not, you'll need to create them.
To create a macro to save you having to write out [[UIApplication sharedApplication] delegate] every time you need to access the managed object context, check out this answer: Short hand for [[UIApplication sharedApplication] delegate]?
You can go through this :-
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *result;
//check to see if navbar "get" worked
if (navigationController.viewControllers) {
//look for the nav controller in tab bar views
for (UINavigationController *view in navigationController.viewControllers) {
//when found, do the same thing to find the MasterViewController under the nav controller
if ([view isKindOfClass:[UINavigationController class]])
for (UIViewController *view2 in view.viewControllers)
if ([view2 isKindOfClass:[MasterViewController class]])
result = (MasterViewController *) view2;
}
}

Adding views with controllers to a view with controller - proper way?

I have to add several views (each having own controller) to a main view (with controller). I am following MVC. Should the code to add these subviews be written in view class or controller class? Also, what is proper way,
MyViewController1 *myViewController1 = [[MyViewController1 alloc] init];
[myMainViewController.view addSubview:myViewController1.view];
Or, some other way?
There is another option - container view controller (with addChildViewController method), but that is tough to manage, so I need the simple way.
If you're adding view controllers to the view of another view controller, then you need to use container containment. You can do that in IB with container views. That makes it easier, than making custom container controllers in code.
The Absolute best way is to maintain ViewControllers according to their functionality (ex. one might be dashboardView one might be settingsView). Now when moving from one view controller to another is to use navigationController.
The practice I follow is to declare one navigationController in appDelegate when your app starts and then keep reusing this. Example -
YourAppDelegate *delegate=(YourAppDelegate *)[[UIApplication sharedApplication] delegate];
MyViewController1 *myVC = [[ FLOHome alloc ]initWithNibName:#"MyViewController1" bundle:[NSBundle mainBundle]];
[delegate.navigationController pushViewController:myVC animated:NO];
This is the absolute best way when dealing with viewControllers. navigationController handles whole lot of stuff like memory management, caching views to make them snappy. You could keep pushing viewcontrollers and poping them when you exit from them...

What is the recommended way to enforce a specific rootViewController in a custom UINavigationController?

I'm building an iPhone SDK that will be used by other iPhone developers.
The main use of this SDK will be to present a UI that will enable the user to complete a certain process.
This UI should be wrapped inside a UINavigationController because it is composed from several UIViewControllers that form the process.
I want that the developers that will use my SDK will initialize my custom UINavigationController and then present it however they want.
I want to enforce that the rootViewController of my custom navigation controller will be a specific ViewController from the SDK, and developers won't be able to initialize it with a different rootViewController.
What is the recommended way to achieve this?
You should not subclass UINavigationController as written in Apple doc.
This class is not intended for subclassing. Instead, you use instances
of it as-is in situations where you want your application’s user
interface to reflect the hierarchical nature of your content.
If you iOS 5, use the new containment API for UIViewControllers.
Implementing a Container View Controller
Using this approach you could enforce your root controller and leave it hidden to the user.
If iOS 5 is not available see my previous answer on it Handle different view controllers within an application: creating Custom Content View Controllers.
Hope that helps.
You could write a class method to instantiate your view controller:
+(MyViewController*)myViewController {
MyViewController* = myViewController = [[MyViewController* alloc] init];
myViewController.rootViewController = [[MyDesiredRootViewController alloc] init];
return myViewController;
}
The developers would than use this method to initialize your view controller and all the initialization work would be hidden from them.
you could fail the initialization if the rootViewController is a different class
- (id)initWithRootViewController:(UIViewController*)rootController
{
if(![rootController isKindOfClass:[YourRootViewController class]])
return nil;
//Do default init stuff
}

Programmatically navigating in iOS

I'm in the process of porting an existing Android app to iOS, and I'm pretty inexperienced in this environment. The examples I've read so far for navigating multiple views all use some kind of visual user control for triggering the loading and unloading of views (tab bar, nav bar). This application's navigation needs to be pretty strict and not allow the user to freely move around between the three views.
The app needs to have a full screen splash view, a main view that the user interacts with, and a third view for data collection. The splash screen should appear, and the user should be navigated to the main view when tapping on the splash image. There will be custom logic in the main view controller for determining if data is required at which point it should navigate to the data collection view. Once valid data is entered and the user clicks OK, it should navigate back to the main view.
What I've read so far is that all Views should have an associated UIViewController class, and the easy way to do this is to create the XIB and UIViewController class in one shot and link them together (I have plenty examples/books/tutorials that I can reference for that part). I believe what I've read is that the app should have a root UIViewController that handles loading the others and navigating between them.
My questions are:
What class should I derive from for my main view controller that I use to load the others?
How do I wire that up to the app so that it knows to load this as the main controller?
What is the accepted standard way of having a navigation controller in the app and allowing the other views to obtain a reference to it? Should my UIViewControllers hold a reference to their parent controller, or should they ask the UIApplication for a reference to it when needed? How do I make sure I don't instantiate extra copies of the views and their controllers as the user navigates?
What class should I derive from for my
main view controller that I use to
load the others?
UIViewController
How do I wire that up to the app so
that it knows to load this as the main
controller?
Read the "Defining Your Subclass" section of View Controller Programming Guide for iOS. Scratch that -- read the whole thing. It's all important, you might as well start to learn it now. Also read App Programming Guide for iOS. Again, read the whole thing, but the application lifecycle part is the most relevant to your question.
What is the accepted standard way of
having a navigation controller in the
app and allowing the other views to
obtain a reference to it?
Again, this is explained well in View Controller Programming Guide. Views should never care about the navigation controller, but any view controllers that are part of a navigation stack have direct access to the nav controller via their respective navigationController properties.
Should my UIViewControllers hold a
reference to their parent controller,
or should they ask the UIApplication
for a reference to it when needed?
A view controller already has a reference to its parent controller in its (surprise!) parentController property. It's best for a controller to avoid assuming too much about its
parent, though. If the controller expects its parent to be a certain type or respond to certain messages, it becomes more difficult to reuse that controller or reorganize your application. Try to give the controller what it needs to do its thing when you create it. If the controller will need to ask for additional data or something like that, delegation is a good way to go.
How do I make sure I don't instantiate
extra copies of the views and their
controllers as the user navigates?
Exercise caution. There's not much danger of creating extra copies of views in a properly structured application because each view controller should take care of its own views. If you find yourself loading or otherwise creating views outside the context of the view controller that owns them, stop that.
It sounds like you can accomplish what you need with a couple of basic calls. To programmatically call a view controller:
- (void)showController {
MyViewController *myController = [[MyViewController alloc] initWithNibName:#"MyViewControllerXIB" bundle:nil];
[self.navigationController pushViewController:myController animated:YES];
[myController release];
}
To return to the previous view just call from any view controller:
- (void)goBack {
[self.navigationController popViewControllerAnimated:YES];
}
Read up on the documentation for the UINavigationController for more ways to move through views. This method is just one of many ways to do this and may not be suitable for all situations.
Not quite right — each UIViewController should know how to trigger its children. Apple's preferred navigation path through views is a branching tree, with the caveat of tab bars that collapse multiple view controllers into a single node on the tree.
You don't explicitly handle loading. Normally you have a sufficient relationship between your NIBs that the container classes are loaded automatically. Cocoa will then load the views whenever they're needed but not yet loaded (which is the purpose of loadView and viewDidLoad), and keep them unless and until a low memory warning requires them to be purged (leading to viewDidUnload). It's relatively rare that you explicitly load a NIB yourself (though table view cells are an obvious example where programmatically loading a NIB is quite common).
So you'd probably have:
a splash screen or preview of the first view controller, as the Default.png
a view controller that probably displays Default.png, and has two outlets going to the data collection controller and the main controller
when the user taps the button on the main screen, ask the model whether data collection is necessary. If so then navigate to the data collection controller, otherwise navigate to the main controller
give the data collection controller an outlet to the main controller and let it perform a navigation there at the appropriate moment
You get a MainWindow.xib for free when creating a new view based project. Probably the easiest thing to do is to put references to the three UIViewController subclasses in there, but set each of them to load from other files. Set the links between them in MainWindow.xib, set the links to things within the relevant views within the relevant XIBs.
That will prevent you from keeping multiple instances of any controllers about, and the built-in Cocoa loading mechanisms will ensure that the stuff that occupies significant amounts of memory — the views — is loaded only on demand and kept for no longer than space allows.
There's no need to link to parent view controllers. Every view controller already knows who presented it, via the parentViewController property. So if a view controller wants to dismiss itself and return to whoever presented it, you can just issue:
[self.parentViewController dismissModalViewControllerAnimated:YES];
Because the model is ideally a separate sovereign thing, all controllers really need to know is which other controllers they can present, how to populate themselves from the model and how to push data back to the model. You rarely end up with particularly complicated links between view controllers.
I think you should have the view loading/unloading in the application delegate and then each view should send notifications to the application delegate.
Here is the official introduction from Apple:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
You can set before which ViewController to load first
If the application is navigation based use the following code:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.nav = [[UINavigationController alloc] initWithRootViewController:mainViewController];
[_window addSubview:nav.view];
[_window makeKeyAndVisible];
}
If the application is View based use the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}

How do I access the managedObjectContext from a controller deep in the UI?

I'm still a little fuzzy on understanding iPhone/Cocoa in general so this is probably a simple question.
I have a CoreData Window-Based App for the iPhone. The rootController is a UITabBarController. The first tab view has a UINavigationController attached to it with a table in it's main view.
When the App starts the objectContext is set up, which makes sense to have the App do that once. But now I have the managedObjectContext in the main Controller but I want to get that passed down to the Controller of the View inside the navcontroller, inside the first item in the TabBarController's tab list. How do I do this?
Would naming the one of the fields in the UI Inspector Tool allow me to do something like:
tabcontroller.navcontroller.manageObjectContext = self.managedObjectContext;
Would this only work if the controller was instantiated and 'live'. (Do the controllers not get instantiated until they are needed?) What if this was in a view that was for the most part hidden?
Anyway this is probably a simple thing I'm just not understanding things properly yet.
What is the general right way to share the manageObjectContext that is created and setup in the rootController to the many sub-controllers in the app?
I'm guessing this is the preferred method assuming the core-data initialization is done in the AppDelegate:
[[[UIApplication sharedApplication] delegate] managedObjectContext]
I usually give controllers a - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context init method and a corresponding variable.
If a controller creates another controller in turn, it will, if needed, pass the NSManagedObjectContext to that controller in the same manner.
If you don't want to create an extra init method, just give the controllers a property for the NSManagedObjectContext and set that property directly after creating them.
I usually try to limit the number of controllers that directly deal with and "know about" Core Data though.
The answers to this question provide several means of accessing the Core Data stack deep within your application. As I indicate in one of the comments, I prefer to use a singleton DatabaseController that can be accessed from wherever, similar to how the NSUserDefaults' standardUserDefaults works.