I'm working on a pretty simple multiview app for the iOS and I've been following a great tutorial in an Apress book. I've basically got my rootViewController instantiated and displayed with the app delegate, and I've got a number of content viewControllers (6) which I'd like to swap in and out based on user input. However, in the book they perform their switches with a button on a toolbar placed in the rootView using Interface Builder. It fires a method in rootView that loads up the new content ViewController and displays it.
My problem is that I'd like to perform the content view switch (that lies in my rootViewController instance), but I'd like to trigger the switch action with a button that's in my content view (and is therefore unavailable as my File Owner is my contentViewController, whose reference is held inside my rootViewController).
Hopefully I've explained it well enough, please let me know if I should elaborate more. I appreciate any help!
You need to pass down a reference to your root view controller (RootViewController *rootViewController) when you create your content view either in a custom init method or by just assigning it after you created it: self.contentView.rootViewController = self;.
Now inside your content view you can then call the appropriate method in the root view controller to do the switch: [self.rootViewController switchView]. This call then can be triggered inside the method that is called when you press the button (IBAction method).
So this is what you need to do:
1) Create a property inside the your content view controller of type RootViewController
#class RootViewController;
#interface MyContentViewController : NSObject {
#private
RootViewController *rootViewController;
}
#property (retain) RootViewController *rootViewController;
and make sure it retains the reference.
2) Synthesis the property and add the callback to the root view controller that switches the view:
#implementation MyContentViewController
#synthesize rootViewController;
- (IBAction) switchView:(id) sender {
[rootViewController switchToNextView];
}
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
Also release your retain reference at the end.
3) Assign the root view controller to the content view inside your RootViewController:
self.contentViewController = [[[MyContentViewController alloc]
initWithNibName:#"ContentView"
bundle:nil] autorelease];
self.contentViewController.rootViewController = self;
That should be all. I hope that helps you.
Well, you could simply create an IBAction in each of your child controllers that calls:
[self.parentViewController switchToDifferentController:(int) viewNumber]
and then implement the switchToDifferentController method in your root. Other than ignore the compiler warning that parentView might not implement that method, it might work.
However, that is a bit brittle, as you'd have to assume that it was the parent calling you and that nobody will forget to implement that method.
In general, you use the "delegate" concept for a child controller to ask its parent to do something. The general idea is that you declare a group of methods as a "protocol". Think of it as a contract between objects. One object can say "I promise to implement these methods," and another can then choose to send those messages to it. The contract allows the compiler/system to check for conformance. You'll see this in UITableView, where the OS provides a standard table, but it calls back to your code to provide the individual cells as needed.
To implement a protocol, you mustdo the following: (See code segments below
Declares a protocol for the conversation
Specify that the parent will follows that protocol
Create a delegate property in your child
When the parent is about to launch the child, it assigns itself as the delegate for that child.
When the child wants to switch, it calls the parent using that protocol
#protocol myVCDelegate
- (void)switchToDifferentController:(int) viewNumber ;
#end
#interface ParentViewController : UIViewController <VCDelegate>
#property(nonatomic, assign) id <VCDelegate> delegate
childController.delegate = self;
[self.delegate switchToDifferentController:kController5];
Related
I have declared a UIView inside a UIViewController class. Can I call this UIView in another UIViewController class?
If its possible how can I call it?
Yes, you can use a single instance of a view in a number of views/viewControllers. Typically I do the same with Views that carry advertisements.
You pass them along as you would do with any other object.
If you do not create it in Interface Bulder (I suggest creating it programmatically) then you may want to define it within your application delegate rather than a view controller and pass it to the individual view controllers that make use of it.
Within the view controller just add it as sub view accordingly, as you would do it with any other view too.
There is one thing though. When you add this view to another super view for the second time or more then it will be removed from its prior super view. That means that you will have to add it as super view again, when its prior super view becomes visible again. A view can only be part of one view hierarchy at a time.
Sample code, thanks to Gordon:
/* Untested and simplified */
AppDelegate.h:
#property ( strong, nonatomic) ReuseableView reuseableView
;
AppDelegate.m
#synthesize reuseableView;
/* in didFinishLaunchingWithOptions ...*/
reuseableView = [[alloc] init]; // or init from nib, initwithframe, etc.
viewController.m
/* In each view controller that uses the view */
- (void) viewWillAppear:(BOOL)animated
{
[self.view addSubview:((AppDelegate*)[UIApplication sharedApplication].delegate).reuseableView];
}
- (void) viewWillDisappear:(BOOL)animated
{
[((AppDelegate*)[UIApplication sharedApplication].delegate).ReuseableView removeFromSuperview];
}
I am not quite sure whether this removeFromSuperview is really required. The next addSubview will remove it from its existing superview anyway and if addSubview is called on the same super view twice in a row then it does not do any harm. However, it is save using removeFromSuperview at this point.
Well, summarized that is basically it. You define, create and store your shared view (the reusableView in Gordon's example) at a common place. The application delegate is a good place for that. In each view's controller that uses the shared view you fetch it from the delegate and add it as subview to the current view.
I would subclass UIView and import it on the ViewControllers where I wanna use it
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"YourView" owner:self options:nil];
yourView = (YourView *)[nibArray objectAtIndex:0];
Then you set its frame and [self.view addSubview:yourView]
I'm adding a subview to my primary iPad UIViewController, and within that subview I need to reference said view controller in order to play a video using that controller.
Can anyone help me out with the way that should be done, and possibly a code example?
Thank you in advance.
Regards,
Ed
EDIT (A LITTLE BIT MORE INFO):
This subview is a view from a uiviewcontroller class that is designed for the iPhone. It's a table that loads a video when a row is pressed. The movieplayer loads the video within the referenced viewcontroller (which is why I want to reference the iPad view controller from within the subview). The view is basically used within an iPad app in it's same form.
This sounds like an architecture problem. It's not up to your view to tell something to play a sound. That's a controller's job. It's up to your view to tell "someone" that it was touched, or slid, or whatever the user has done. "Someone" (who is watching) will then perform the correct response to that.
To do this, your view should generally take a target, and possibly and action. Look at how UIControl (for example, UIButton) informs other objects that it has been activated. The observer (controller) then reacts accordingly.
EDIT
The view should not load a video. A view controller should load the video and install it into the correct view. The only job the view has is to tell its view controller that it has been pressed. UITableView handles this automatically with the UITableViewDelegate method tableView:didSelectRowAtIndexPath:. If you're not using a UITableView, you should still follow this pattern. The view accepts has a delegate and tells the delegate (controller) that something was selected. Then the controller updates the views with the new data.
You probably already have the main view living in a property of your application delegate (it is commonly assigned via the application's .xib file, look in the app delegate's applicationDidFinishLaunching: method, where it adds the main view as a subview to the window, something like:
[window addSubview:primaryController.view];
[window makeKeyAndVisible];
).
So anywhere in your app where you need to access the main view, you can do:
[(MyAppDelegateType*)([UIApplication sharedApplication].delegate).primaryController somePrimaryControllerMethod];
Edit: while this will work, I agree with Rob Napier that this isn't the best way to do it, architecture-wise.
You could just use [yourSubview superview] to get the superview.
Can't you just add a property to the view like this:
#property (nonatomic, assign) UIViewController *parentViewController;
And on initialization of the view (from within the UIViewController), set the property:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
customView.parentViewController = self;
And from then on you should be able to call the parentViewController from within the view.
Edit: Rob Napier has a good point, it's probably a better idea to set a target and selector from within the view instead of a view controller. This way you should be able to hook up methods directly with the view. The properties would look like this:
#property (nonatomic assign) id target;
#property (nonatmic, assign) SEL selector;
And perhaps add a designated initializer to your view:
- (id)initWithFrame:(CGRect)aFrame target:(id)aTarget selector:(SEL)aSelector
{
self = [super initWithFrame:aFrame];
if (self)
{
self.target = aTarget;
self.selector = aSelector;
}
return self;
}
I am using a UINavigationController to switch between views. What I would like is for each view to have the ability to control when it is swapped out for another view by having buttons within the view. All of the samples I've seen thus far have placed buttons on a toolbar, which is located on the root view containing the Switch View Controller rather than the views, them self. Is it possible to do what I want? I can't figure how to wire up the connection back to the UINavigationController.
I'm having a difficult time wording this, so please feel free to let me know if you need additional clarification.
Read about delegates. Delegates are a common method to signal stuff from objects to their "parents" or any other objects.
You should have a "delegate" property (can really be called anything, this is just a convention) on your child views. You can have buttons in your child views.
You declare the delegate like this:
interface ChildView : UIViewController {
id delegate;
}
#property (assign) id delegate;
implementation ChildView
#synthesize delegate;
Then, when you set up your child views inside your UINavigationController, you do:
ChildView *childView = [[ChildView alloc] init...]
childView.delegate = self;
Inside your child view, you have a button method:
- (IBAction) didPressButton:(id)sender {
[self.delegate didPressButtonToSwapView];
}
Inside your UINavigationController, you have a method:
- (void) didPressButtonToSwapView {
[self popViewController]; // use the right names, I made these up :)
[self pushAnotherViewController];
}
You should also read about protocols which would make the above code more robust and would help you make sure you only call the right methods on delegate, but I did not want to complicate this example.
EDIT: yes, the cleanest way to get rid of the warning is to use a protocol. Just put this in a separate .h file:
#protocol SwitchingDelegate
- (void) didPressButtonToSwapView;
#end
Include this .h in the UINavController header, and say the UINavController implements the protocol:
#interface MyNav: UINavController <SwitchingDelegate> { ...
Implement the method in the implementation (you don't need anything more in the interface).
In your ChildView, say that the delegate must implement the protocol: change all the declarations to:
id<SwitchingDelegate> delegate;
The compiler then helps you by checking whether the delegate objects really implement the protocol. You should not get any warnings when you have completed all of this correctly.
I am trying to call a method in my root view controller from a child view controller such that when I change my options they will automatically update the root view, which will in turn update several other view controllers. For the second part I have used notifications, but for this first I am trying to use a delegate because it (so I have been lead to believe) is a good programming practice. I am having trouble making it work and know that I can set up another notification easily to do the job. Should I continue trying to implement the delegate or just use a notification?
Delegating is a good programming practice for many situations but that doesn't mean you have to use it if you're not comfortable with it. Both delegating and notifications help decouple the view controllers from each other, which is a good thing. Notifications might be a little easier to code and offer the advantage that multiple objects can observe one notification. With delegates, such a thing cannot be done without modifying the delegating object (and is unusual).
Some advantages of delegating:
The connection between delegating object and delegate is made clearer, especially if implementing the delegate is mandatory.
If more than one type of message has to be passed from delegatee to delegate, delegating can make this clearer by specifying one delegate method per message. For notifications, you can use multiple notification names but all notifications end up in the same method on the side of the observer (possibly requiring a nasty switch statement).
Only you can decide what pattern is more appropriate for you. In any case, you should consider not having your view controller send the notification or the delegate message. In many cases, the view controller should change the model and then the model should inform its observers or its delegate that it has been changed.
Implementing a delegate pattern is simple:
In your ChildViewController.h, declare the delegate protocol that the delegate must implement later:
#protocol ChildViewControllerDelegate <NSObject>
#optional
- (void)viewControllerDidChange:(ChildViewController *)controller;
#end
At the top of the file, create an instance variable to hold the pointer to the delegate in your ChildViewController:
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController {
id <ChildViewControllerDelegate> delegate;
...
}
#property (assign) id <ChildViewControllerDelegate> delegate;
...
#end
In RootViewController.h, make your class conform to the delegate protocol:
#interface RootViewController : UIViewController <ChildViewControllerDelegate> {
...
In the RootViewController implementation, implement the delegate method. Also, when you create the ChildViewController instance, you have to assign the delegate.
#implement RootViewController
...
// in some method:
ChildViewController *controller = [[ChildViewController alloc] initWithNibName:...
controller.delegate = self;
...
- (void)viewControllerDidChange:(ChildViewController *)controller {
NSLog(#"Delegate method was called.");
}
...
In the ChildViewController implementation, call the delegate method at the appropriate time:
#implementation ChildViewController
...
// in some method:
if ([self.delegate respondsToSelector:#selector(viewControllerDidChange:)]) {
[self.delegate viewControllerDidChange:self];
}
...
That's it. (Note: I have written this from memory so there are probably some typos/bugs in it.)
I would like to add:
objects receiving notifications can
react only after the event has
occurred. This is a significant
difference from delegation. The
delegate is given a chance to reject
or modify the operation proposed by
the delegating object. Observing
objects, on the other hand, cannot
directly affect an impending
operation.
Typically, if you need to update the UI based on a change to data in a model, you would have the view controllers observe the relevant model data and update their views when notified of changes.
I see delegation as a bit more formal and like the distinction that Peter Hosey shared recently:
The difference is that delegation is
for to-one (and bidirectional)
communication, whereas notifications
are for to-many, unidirectional
communication.
Also, I have found that (completely) updating the view in viewWillAppear: works fine (but this is not the best solution where performance is a concern).
Notifications can make the runtime behavior of your program significantly more complex. Think of it like a goto with multiple destinations. The order of those destinations is not defined. If you ever crash there is little stack trace information.
There are cases when it makes sense to use notifications--the typical one being to communicate a model change or a global state change to your views. Example, the network is down, the application will resign, etc!
It is worthwhile to learn the delegate pattern in iOS. Delegates give you complete stack traces when you debug. They result in significantly simpler runtime behavior while still achieving the goal of decoupling your objects.
Delegates are a little hard to get used to, but I think it's the best practice and, like Apple, they just work.
I always use the formal protocol declaration. It's a bit more logical in my mind, and it's very clear in the code. I suggest using a UIView to change your options instead of a controller. I always use one main controller and have a lot of subclassed UIViews that the one controller can control. (However, you can modify the following code for a controller, if you really need a controller instead of a normal view.) In the header file of the child view, make it look like this:
// ChildView.h
#import <UIKit/UIKit.h>
#protocol ChildViewDelegate; // tells the compiler that there will be a protocol definition later
#interface ChildViewController : UIView {
id <ChildViewDelegate> delegate;
// more stuff
}
// properties and class/instance methods
#end
#protocol ChildViewDelegate // this is the formal definition
- (void)childView:(ChildView *)c willDismissWithButtonIndex:(NSInteger)i; // change the part after (ChildView *)c to reflect the chosen options
#end
The method between #protocol and the second #end can be called somewhere in the implementation of the ChildView, and then your root view controller can be the delegate that receives the 'notification.'
The .m file should be like this:
// ChildView.m
#import "ChildView.h"
#implementation ChildView
- (id)initWithDelegate:(id<ChildViewDelegate>)del { // make this whatever you want
if (self = [super initWithFrame:CGRect(0, 0, 50, 50)]) { // if frame is a parameter for the init method, you can make that here, your choice
delegate = del; // this defines what class listens to the 'notification'
}
return self;
}
// other methods
// example: a method that will remove the subview
- (void)dismiss {
// tell the delegate (listener) that you're about to dismiss this view
[delegate childView:self willDismissWithButtonIndex:3];
[self removeFromSuperView];
}
#end
Then the root view controller's .h file would include the following code:
// RootViewController.h
#import "ChildView.h"
#interface RootViewController : UIViewController <ChildViewDelegate> {
// stuff
}
// stuff
#end
And the implementation file will implement the method defined in the protocol in ChildView.h, because it will run when the ChildView calls for it to be run. In that method, put the stuff that happens when you'd get the notification.
In this case, you don't need to use either delegation or notification because you don't really need to communicate directly between your views. As gerry3 said, you need to change the data model itself and then let all other views respond to that change.
Your data model should be an independent object that all your view controllers have access to . (The lazy way is to park it as an attribute of the app delegate.) When the user makes a change in View A, View A's controller writes that change to the data model. Then whenever Views B through Z open up, their controllers read the data model and configure the views appropriately.
This way, the neither the views, nor their controllers need to be aware of each other and all changes occur in one central object so they are easily tracked.
Is it really necessary for your root view controller to know about the changes, or just the subviews?
If the root controller does not have to know, having the settings send out the notifications the other views are looking for seems like a better answer to me, as it simplifies code. There is no need to introduce more complexity than you have to.
Edited for brevity:
How does a subview access properties in its superview and its superview's view controller? Easy enough to go down the chain. How do we go back up?
Original (verbose) post:
The immediate problem I'm trying to solve is simplifying the "birth" of a somewhat complex view. The large picture has to do with Nibs and how subclasses (of UIView in particular) that have beyond trivial initializers are reconstituted when the view loads.
I have some custom UIViews - subviews of a subview of my viewcontroller's view. When I instantiate these views in particular they need a reference to some properties (NSNumberFormatter, & NSDictionary) of the View Controller.
Currenty I use a method to instantiate them:
- (id)initWithFrame:(CGRect)frame items:(NSDictionary *)dictionary forEditingMode:(EditingMode)mode
I'm experimenting with moving them into a Nib to let them reconstitute themselves and running into basic design issues. One being I think I'm limited to initWithFrame as the default initializer?!? If so how can these objects look into the parent view controller and get a reference to the dictionary and some other properties?
Are there methods I could call within initWithFrame (similar to the ones that retrieve the UIApplication delegate) but would instead allow the child view to send methods to it's parent views and/or controllers?
Send in the MVC police, I'm sure I'm breaking something...
It should work the other way round.
The views only contain controls (text fields, etc.). The data lives in a model, and the view/window controllers mediate, accessing and setting the view controls values, synchronizing with the model.
OK, sometimes you may need to have a dictionary shared between the controller and the view. Then create a dictionary property in the view and set it in the awakeFromNib method of the nib owner.
You can set up outlets that get connected to the view's superview or view controller and pull out the info in awakeFromNib. You need to use awakeFromNib instead of init* because the connections won't be created until then.
FYI, if you instantiate via a Nib, your designated initializer is initWithCoder:, not initWithFrame: (but don't use either for this).
You should probably be doing this through the view controller manually just after the nib is loaded. This means that you will have to set the shared dictionary AFTER the view has been initialized. You might do it like so:
#interface MyViewController : UIViewController {
IBOutlet MyView* view1;
IBOutlet MyView* view2;
IBOutlet MyView* view3;
}
#end
#implementation MyViewController
-(void) viewDidLoad;
{
NSDictionary* sharedDict = //make the shared dictionary here;
view1.sharedDict = sharedDict;
view2.sharedDict = sharedDict;
view3.sharedDict = sharedDict;
}
#end