Managing subviews in Cocoa Touch - iphone

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

Related

Showing the same UIView instance in different UIViewControllers

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.

iPhone App Dev - Loading View From View Controller

I have a questionnaire viewcontroller class. This instantiates several questionviewcontrollers (each questionviewcontroller has an associated view).
How do I get the questionnaire to load these question views associated with their questionviewcontrollers....
EDIT:
-(void) setQuestions{
for (NSDictionary *q in self.questions) {
/* Create our Question object and populate it */
QuestionViewController *question = [[QuestionViewController alloc]init];
[question setQuestionId:[q objectForKey:#"questionId"] withTitle:[q objectForKey:#"question"] number:[q objectForKey:#"questionNumber"] section:[q objectForKey:#"sectionId"]];
/* Add it to our question (mutable) array */
[questionArray addObject:question];
[question release];
}
}
The above method is called in the viewDidLoad method of the QuestionnaireViewController and is where the QuestionViewControllers are created. Each one has an associated view with a next button.
It's not clear from your question what you mean when you say, "How do I get the questionnaire to load these question views".
Are you just asking how to display a QuestionViewController when a question is selected? If so, this sounds like a navigation based application. You would typically use a UINavigationController as your top level view controller in your app delegate, setting your QuestionaireViewController as the rootViewController of your UINavigationController. Then, when the user selects a question in your QuestionaireViewController, you can display its controller using:
[self.navigationController pushViewController:questionViewController animated:YES];
If instead you're asking how you can display the views for these QuestionViewControllers as subviews of your QuestionaireViewController, the short answer is don't do it (at least not under iOS 4.x). Apple's view controller framework is not designed to support using nested view controllers to manage multiple subviews simultaneously. The documentation states that each view controller should correspond to one full-screen view on iPhone. iPad changes these rules slightly for things like split views and popovers, but it's still not designed to let you nest view controllers within your own custom view controllers.
(In truth it is technically possible to use multiple view controllers to manage different subviews on a single screen, but doing so properly requires expert knowledge of how the view controller framework is designed so that you can properly delegate all the various UIViewController methods and properties like viewWillAppear:, navigationController, tabBarController, etc. You're generally better off following Apple's advice and using one view controller per screen.)

What's the best practice to pass value/parameter between NavigationControllers?

Prior to asking this question here, I have googled aroung. In general, people suggest two ways to pass values/parameters between NavigationControllers:
Via properties in the root ViewController
Via Data Models
I know already that the first choice may not be the best practice. A lot of people seem to adopt this practice. However, I still don't understand how the second practice may be implemented. Does anyone happen to know any tutorial?
Moreover, is it possible to pass the value/parameter downward through constructors? I guess the only problem with that is to get back value/parameter from sub-viewcontrollers.
This file defines the delegate protocol:
#protocol VCDelegate
- (void)notifyParent:(NSString*)someString;
#end
You can include it in the .h of any view controller you define. In that view controller you declare an ivar:
id<VCDelegate> delegate;
In the view controller in which you create the child view controller you include your child view controller's .h as usual. However you add
<VCDelegate>
to indicate that it implements the protocol you have defined, just as you would if you were indicating that it implemented UITableViewDelegate - you're defining a delegate that works just the same way.
When you create your child view controller:
MyChildViewController* myCVC = [[MyChildViewController alloc] initWithString:(NSString*)someString];
myCVC.delegate = self;
So now the child view controller has a delegate which is the parent view controller, the one you are creating the child in and the one that will push it on the nav stack. You have to implement the delegate function in the parent view controller of course:
Incidentally here's where you can pass information down the stack - just set ivars after creation, same as you do the delegate ivar. You'll notice there's an initWithString that is passing a string to a custom init method, that's another way to pass information. You still do all the normal init things, just pass data additionally.
- (void)notifyParent:(NSString*)someString
{
NSLog(#"Child view controller says %#", someString);
}
And then in the child view controller you can do
[self.delegate notifyParent:#"Hello"];
presto - parent VC gets data from child VC.
This seems like the job for NSNotificationCenter. Have a look at this.
sending data to previous view in iphone

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 to use generic (NSObject) controller with subviews of a UIViewController?

I have a UIViewController that is loading several subviews at different times based on user interaction. I originally built all of these subviews in code, with no nib files. Now I am moving to nib files with custom UIView subclasses.
Some of these subviews display static data, and I am using loadNibNamed:owner:options: to load them into the view controller. Others contain controls that I need to access.
I (sort of) understand the reasons Apple says to use one view controller per screen of content, using generic controller objects (NSObjects) to manage subsections of a screen.
So I need a view controller, a generic controller, a view class and a nib. How do I put this all together?
My working assumptions and subsequent questions:
I will associate the view class with
the nib in the 'class identity' drop
down in IB.
The view controller will coordinate
overall screen interactions. When
necessary, it will create an instance
of the generic controller.
Does the generic controller load the
nib? How?
Do I define the outlets and actions
in that view class, or should they be
in the generic controller?
How do I pass messages between the
view controller and the generic
controller?
If anyone can point me to some sample code using a controller in this way, it will go a long way to helping me understand. None of the books or stackoverflow posts I've read have quite hit the spot yet.
Okay, I think I figured it out:
Extend NSObject to make your CustomController
Define your outlets & actons in CustomController.h, including a reference to the UIView in your nib
Set the File's Owner of your nib to CustomController
Hook up all your outlets & actions as usual, including the UIView outlet
In your CustomController.m init, load the nib
- (id)init {
self = [super init];
if (self != nil)
[self loadNib];
return self;
}
- (BOOL)loadNib {
NSArray *topLevelObjs = nil;
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
if (topLevelObjs == nil) {
NSLog(#"Error! Could not load nib file.\n");
return NO;
}
return YES;
}
The new NSObject based controller will work very much like a view controller.
It sounds like what you want is what I've coined "reusable UIView widgets" -- reusable widgets that do something / present a display that you can incorporate in your app screens wherever and how many times as you want -- you can create them purely in code or instantiate them by placing their frame in another xib file (but you don't get to change the widgets' internal parameters in the xib file, that would require an IB plugin).
This is an organization that I've never seen discussed anywhere. Part of my frustration early on with iOS programming is wanting something like this but not seeing any way to express it in any example.
See my answer in this question to see how it can be structured:
UIView and initWithFrame and a NIB file. How can i get the NIB file loaded?
I recommend placing all widget-internal handling of direct/low-level events in the uiview widget subclass, and implememnt a delegate protocol for a highler level interaction with the widget client (i.e., "loginRequested:userName:passWord" instead of manually accessing button and textfields internal to the widget).
The (optional but recommended) xib file for the widget has an owner of the widget, and the init code in the widget owns the duty of loading the xib file. The customer of the widget simply instantiates the widget and implements whicever widget delegate functions makes sense for it.