Showing the same UIView instance in different UIViewControllers - iphone

Is it possible to somehow store a UIView instance in such a way that it can be accessed from other view controllers? I know this is probably bordering on "globals" which they say are bad, but I digress. All I know is I have a couple UITabBar tabs that need to reference the same instance of a view that was instantiated in one tab and needs to be displayed again in another tab. What's the best approach for doing something like that?

Sure. You just need to store a retained reference to the UIView object in a persistent object. For example, you can add a retained property to your UIApplicationDelegate subclass. You can have that delegate instantiate the view, and all the controllers would just ask the app delegate for the view. If you have a root view controller that is always available, you could retain it there.
Maybe thinking through the overall structure of your app can help find the "right" place to store the UIView. Below I present an app structure I frequently use, not necessarily as advice on how you should structure your app, but as an example to expand the options you can consider to help you with thinking about the best structure for you app.
I write a lot of brochure like apps for our clients. In these apps I need to present a number of views, each somewhat analogous to pages in a brochure. Some of these views will have user interaction, and need to retain their state when off screen, or share the state data with other views.
To manage these apps I create a presentation manager object. This object will retain the overall state of the app, including views that must persist when not displayed. I use one master UIViewController that owns the presentation manager and is responsible for all common view control operations. The specific logic for individual views will go in UIView subclasses for each view. These individual views are created by the presentation manager, and can ask that manager for what it knows, including any persistent views.

You can just use dependency injection, to inject the same view instance to the view controllers like this:
UIView *myView = [UIView new];
UIViewController *controller1 = [UIViewController new];
UIViewController *controller2 = [UIViewController new];
controller1.view = myView;
controller2.view = myView;
[myView release];

B/c you use UITabBar I would suggest to add your custom view to the window in the app delegate. Then you don't have to store it, just hide it. You can use either NSNotificationCenter to send notifications to show the view or you can call your appDelegate and show the view manually.

Related

How to set the delegate to another ViewController?

I've recently started developing for the iPhone and so far I'm doing pretty good but there's this basic pattern I really don't seem to get.
Say, I have a TabBar with two views and a custom delegate protocol, thus my structure is the following:
AppDelegate.h/.m
myDelegateProtocol.h
FirstViewController.h/.m
SecondViewController.h/.m
MainView.xib
FirstView.xib
SecondView.xib
Now I want to achieve the following: I placed a button in the FirstView.xib and I'd like the IBAction which it invokes (inside FirstViewController ofc.) to send a message to the SecondViewController ([self.delegate tellSecondViewContrToSayHi]) and invoke another method which simply prints a log into the console saying "hi I'm here."
So far I know what I need to do in theory:
Specify the protocol.
Implement the protocol in the SecondViewController.
Create an id< myDelegateProtocol > delegate inside my FirstViewController,...AND last but not least:
Set the self.delegate = secondViewControllerObject.
Now, nr.4 is where the problem's at. How on earth do I link the delegate to the other viewController? I mean I'm not the one instantiating the views as the tabBar kinda does that for me,... any advise? Or am I just way too tired to notice a really stupid thing I did somewhere?
Theoretically the same question also applies to the target:action: thing,... I mean, how do I define the target?
Thanks a lot,
wasabi
You have the right idea, assuming that you want relatively tight coupling between these controllers via that delegate protocol.
Since neither controller knows about the other until that delegate property is set you need to have some object which has a reference to both of them wire up that relationship. In your case that's probably the application delegate which can create both controllers, set one as the delegate of the other, and pass both along to your tab bar controller.
What you might actually want is to have the app delegate give both controllers a reference to some shared model object. Your FirstViewController can update that model when you tap a button and your SecondViewController can observe changes to the model to update it's display (or just update its view when it appears based on the current model state). That way your controllers don't need to know anything about each other.

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 to reference object values from subview?

I have a UIViewController with a XIB and want to add programmatically another subview.
During the initialization of the subview (initWithFrame) i want to set some attributes to values according to attributes that belong to another Object which holds data (actually a ViewControllers Child-Object, but not a view).
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// The following is kind of what i want
self.myAttribute = [self.viewController.otherObject otherValue];
}
return self;
}
I want to conform to the Model-View-Controller paradigm and try to seperate my data from the subview and don't know how to access the data from within the subview.
Thanks in advance for any answers and comments how to improve the question.
EDIT: All three answers are useful for me to understand that my design is somehow the wrong way of doing the thing. As far as i understand the subview properties should be modified by the controller instead of trying the subview making to get the information. I will accept Jasons answer for his effort explaining this to me.
If you put this view in place using something like a UINavigationViewController you can use the parentViewController property. If not--and really, just in general--you can create properties that need to be set on your new view controller, and just set them in the parent or whoever else might create it.
Getting data passed around a view hierarchy can be tricky. You have a few options for global-like data:
Actual global variables (hosted in one .m file and declared in a shared .h file). Not recommended, except in rare cases where you have e.g. static data that the following approaches seem silly to use with.
A shared (singleton) controller object that owns the shared data. Then you could do, say, [[AppController sharedController] otherValue] and access it from anywhere in your application. This is good for what you might call overall properties or settings across your application. You wouldn't use this to pass around view-specific information, generally.
If the data is view-specific, you might have it "ride along" with your view controller hierarchy, by passing it from one view controller into the next as you create and push the controllers. Then when you create the views themselves, as above, don't look for the property in the initWithFrame method, but set up a property on the view that you can set to push in the data immediately after creating the view.
A simple solution for the general problem of initializing a subview with attributes is to write a custom initializer in your subclass.
-(id) initWithFrame:(NSRect) aFrame andAttribute:(SomeClass *) anAttribute{
if (self=[super initWithFrame:aFrame]) {
self.attribute=anAttribute;
}
return self;
}
You would initialize the object like so:
MySubviewClass *msc=[[MySubviewClass alloc] initWithFrame:frame andAttribute:[self.viewController.otherObject otherValue]];
This will work fine if your talking about a subview controlled by the the same controller i.e. it is a subview of the controllers.view. If you loading another view, then you need to go the data-model/navigation-controller route.

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.

Managing subviews in Cocoa Touch

I'm developing an iPhone app. I need to create a Quiz application that has different Question views embedded in it (see my similar question).
Different types of Question will have different behavior, so I plan to create a controller class for each type of Question. The MultipleChoiceQuestionController would set up a question and 3-4 buttons for the user to select an answer. Similarly, the IdentifyPictureQuestionController would load an image and present a text box to the user.
However, the docs say that a UIViewController should only be used for views that take up the entire application window. How else can I create a class to manage events in my subviews?
Thanks,
Subclassing UIViewController will provide this functionality. For example, MultipleChoiceQuestionController would be a subclass of UIViewController. MultipleChoiceQuestionController would contain the question text (UILabel or UITextView) and several buttons (UIButton). You could create a custom constructor in MultipleChoiceQuestionController that would fill the view with the relevant question string and other relevant info.
When you want to add MultipleChoiceQuestionController's view to your main view's subview, simply do the following:
[myMainView addSubview:instanceOfMultipleChoiceQuestionController.view];
I have the same problem, and according to Apple's doc, here's what you should do:
Note: If you want to divide a single
screen into multiple areas and manage
each one separately, use generic
controller objects (custom objects
descending from NSObject) instead of
view controller objects to manage each
subsection of the screen. Then use a
single view controller object to
manage the generic controller objects.
The view controller coordinates the
overall screen interactions but
forwards messages as needed to the
generic controller objects it manages.
http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/AboutViewControllers/AboutViewControllers.html#//apple_ref/doc/uid/TP40007457-CH112-SW12
You can handle the events on the view itself, or your view controller could have a delegate class that changes for different types of question. That delegate would process the different input, and react in a different way to user touches.
Here's some code with the idea.
// In QuestionViewControllerDelegateProtocol.h
#protocol QuestionViewControllerDelegateProtocol
// Define the methods you want here
- (void)touchesBegan;
- (void)touchesEnded;
- (void)questionLoaded;
#end
// In QuestionViewController.h
#interface QuestionViewController {
id<QuestionViewControllerDelegateProtocol> delegate;
}
#end
// In QuestionViewController.m
#implementation QuestionViewController
- (void)viewDidLoad:(BOOL)animated {
[delegate questionLoaded];
}
- (void)touchesBegan {
// Some processing logic.
[delegate touchesBegan];
}
#end
This is a very nice little solution, gives you all of the advantages of a view controller without breaking apples rules.
From the page:
This is a generic controller class
that can be used to handle a subarea.
It is modelled after UIViewController,
but conforms to Apple's
recommendation.
Your view controller creates the
instances and is responsible for
managing the subview controllers.
Alternatively you can further
subdivided your view hierachy and
create subview controllers inside
other subview controllers. In both
cases the controller instantiating the
object is responsible for managing the
subview controller. The responsible
controller is referred to as 'parent
controller.' Subclasses can use the
view controller when they for example
need to show a modal dialog.
https://github.com/Koolistov/Subview-Controller