iPhone: Switching Views From Outside Root Controller - iphone

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.

Related

Calling views from subclasses

I have an iPhone app primarily made of two views, let's call them fullScreen and cardViews (the cardViews are presented as subViews of the full screen views). I've handled all of the animations in presenting the card Views by having a masterCardViewClass and a masterFullScreenViewClass. All specific cardViews are subclasses of the masterCardView, all specific fullScreenViews are subclasses of the masterFullScreenView.
I present a cardView with a method from the masterFullScreenViewClass. I dismiss the cardView with a delegate method from the masterCardView. However, I'm having a problem calling a method to present a cardView from another cardView. All cardView presenting methods are contained in the masterFullScreenViewController class.
How do I access these methods without copying a pasting them locally where I need them.
One way of doing it is by using protocols.
In a nutshell, your masterCardView class would implement a protocol method that presents a cardView (for the sake of simplicity, let's say that you invoke cardViews with a certain index):
masterCardView.h:
#protocol CardPresenterDelegate <NSObject>
- (void)presentCardViewWithIndex:(int)index;
#end
#interface MasterCardView:UIViewController <CardPresenterDelegate>
...
masterCardView.m:
- (void)presentCardViewWithIndex:(int)index
{
// Code for presenting a cardView
}
You would also need to create a delegate (weak) property in your cardView:
cardView.h
#property (weak) id<CardPresenterDelegate> cardPresenterDelegate;
And then by accessing that property in your cardView, you can tell the masterCardView to do something for you:
cardView.m
[self.cardPresenterDelegate presentCardViewWithIndex:5];
Oh, and, don't forget to set the delegate property on your cardViews when creating them in your masterCardView:
back in masterCardView.m:
cardView.cardPresenterDelegate = self;

Same UIView subclass in several UIViewController subclasses?

I have several UIViewControllers. I would like to use the same UIView subclass (which goes on top of the existing UIViewController's view) in all of them. Is this possible using Interface Builder?
I mean, I would like to be able to drag a UIView onto each UIViewController's view, and rename the class of this dragged UIView to CustomView, and all the elements within the CustomView would show up... is this possible?
Based on your question and your response to highlycaffeinated — whose answer is correct but I think may be slightly askew from what you're asking — I think you want to be able to design a view graphically within Interface Builder (so, you haven't written a custom UIView subclass, you've just arranged some UIViews in a certain way and so that they're all children of another view), then embed it into several view controllers via some sort of indirect reference, so that you're not copying and pasting the same user interface elements and if you make a change in one place, that change then takes effect everywhere?
As far as I'm aware, there's no built-in facility in Interface Builder or Xcode 4 for achieving that. XIBs are pure data and UIViews don't have the smarts to handle an out-of-file reference.
What you can do is design the view you want to use in one XIB, called say ReusableView.xib, then write a custom UIView subclass that looks something like:
#implementation ReusableViewTemplate
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView" owner:self options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#end
Then, in your NIBs put in a UIView where you want the reusable view to go, and set the 'class' to 'ReusableViewTemplate' or whatever you called it.
If you open the ReusableView XIB and set the type of the parent view to ReusableViewTemplate, then you can wire up any UIControls (such as buttons or switches) to connect to there. You'll probably want to define some custom protocol for your reusable view template and catch viewDidLoad in any view controllers that use the reusable view in order to set an appropriate delegate.
EDIT: further thoughts on this. I've created an example project (currently at a generic file sharing site, so may not survive forever) with a class ReusableView that, for the purpose of example contains a segment view and a button, and looks like this:
#implementation ReusableView
/*
initWithCoder loads the relevant XIB and adds its
only object, which is a UIView, as a subview of this
one. If you don't like the double hierachy, you
could just have a list of views in the XIB and
addSubviews:, but then it'd much more difficult to
edit the thing graphically. You could strip the top
view programmatically, but this is just a simple
example, so...
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView"
owner:self
options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#synthesize delegate;
#synthesize segmentedControl;
#synthesize button;
/*
NSObject contains machinery to deal with the possibility
that a class may be sent a selector to which it doesn't
respond.
As of iOS 4, forwardingTargetForSelector: can be used to
nominate an alternative target for the selector quickly.
In previous versions of iOS, or in iOS 4 if you don't
respond to forwardingTargetForSelector:, you may take
delivery of the problematic invocation and deal with it
yourself.
Dealing with the invocation costs more than providing
a forwarding target for the selector, so its worth having
both.
If you're only targeting iOS 4 or above, you needn't
keep the implementation of forwardInvocation: below.
What we're doing is declaring a bunch of IBActions so
that we can wire changes up to them in Interface Builder.
By failing to implement them and providing the delegate
as the thing to talk to for any selectors we don't know,
we're allowing those wirings to be as though assigned
to the delegate.
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return delegate;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
}
#end
With interface:
#interface ReusableView : UIView
{
IBOutlet id delegate;
IBOutlet UISegmentedControl *segmentedControl;
IBOutlet UIButton *button;
}
#property (nonatomic, assign) id delegate;
#property (nonatomic, assign) UISegmentedControl *segmentedControl;
#property (nonatomic, assign) UIButton *button;
/*
NB: we're not actually going to implement these. We're
declaring them for the benefit of Interface Builder / Xcode 4.
What we'll actually do is, any time we receive a selector
we don't implement, hand it off to the delegate. So it's a
quick way of avoiding writing any glue code to pass messages
from this little reusable view to its delegate.
A better alternative could define a formal protocol that
forwards both the changed control and self from the
reusable view to its delegate. But that's obvious and
verbose, so has been omitted for the purposes of example.
The implementation as stands will generate compiler warnings,
but such is life. To get rid of the warnings, comment out
the two following lines, but only AFTER you've wired
everything up in Interface Builder / Xcode 4. They're left
uncommented here to help draw attention to the point about
selector/invocation forwarding that you'll see in the
#implementation.
!!!!!!!!!!!!!!!
HENCE:
delegates MUST implement the following methods.
!!!!!!!!!!!!!!!
We could work around that by checking at runtime whether
the actual delegate implements them and forwarding to
a dummy object that implements them to do nothing otherwise,
but that's slightly beyond the point of the example.
*/
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
#end
Net effect is that if a view controller has a UIView of type ReusableView within a XIB, it gets the contents of ReusableVew.xib inserted at runtime. If it wires itself up as the delegate of ReusableView within Interface Builder / Xcode 4 and implements:
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
Then it gets the messages from the embedded views.
This is achieved very simply and very neatly in Objective-C by using NSObject's inherent ability to forward selectors (as of iOS 4) or invocations (in earlier versions, at a greater cost) that it doesn't implement rather than allow an exception to occur.
Yes, it's possible. Just as you could have (for instance) multiple UIViewControllers in your project, each with a UIImageView as a view, you can do the same with your own subclasses of UIView.

How to access variables of a ViewController in a subclass?

I guess this is basic, but I can't get my head around this.
I used to have only one ViewController in which all my variables were defined, e.g. an UITextView named myTextView. I also had methods in this ViewController for handling events that relate to myTextView, such as - ()hideKeyboard { // do something with myTextView or - (void)keyboardWillShow:(NSNotification *)notification { // do something with myTextView.
As my program became bigger and bigger, I thought about using subclasses, especially for other views. So I started a subclass, eg. mySubClass.h and mySubClass.m, in which I had another UITextView (for argument's sake myOtherTextView). In order to incorporate mySubClass, I #imported it into my ViewController and added a #class mySubClass; and could then produce instances of this class so as to use it in my App.
So far so good. As you can imagine, all the nice methods I defined in my ViewController for what should happen when an UITextView is edited (such as hiding keyboard etc.) didn't work for the UITextView in mySubClass.
It was then suggested to me that I should make another class in which I had all the keyboard events and subclass my ViewController and mySubView to it:
#interface ViewController : MyKeyboardEventsViewController
Now, the problem I am seeing is that I won't be able to access all the views, textviews, textfields etc. that I have created in my ViewController (e.g. myTextView which I mentioned earlier).
How can I achieve that all the variables that I have defined in my ViewController will also be available for MyKeyboardEventsViewController? Or is there another way to handle this?
Basically, I don't get how MyKeyboardEventsViewController will be able to access variables in my ViewController which it will need (e.g. the UITextView in question, or the accessoryView which will pop up etc. etc.).
Any suggestions would be very much welcome.
Example:
Class A contains a ivar UITextField textField
Class B subclasses Class A and thus it already contains ivar textField
Note: it's not the other way around. Class A does not "see" what ever is created in Class B.
When ever you subclass a class you give your new class the same ivars end methods of that subclassed class.
I hope this is what you were asking for.
EDIT
So for your example I would do the follwing:
Create a class "MyUIKeybordEventResponder"
Implement all the responder methods like - (BOOL)textFieldShouldReturn:(UITextField *)textField
Subclass your ViewController from "MyUIKeybordEventResponder"
Note method textFieldSHouldReturn has a parameter UITextField so it knows which textfield was pressed. So in a way it receives your textField from the subclass.
If I'm understanding this correctly, you have a UIViewController with MyKeyboardEventsViewController as an instance variable and you want to communicate between the two? If that is the case, one option would be to create a protocol.
#protocol MyKeyboardDelegate
- (void)closeAccessoryView;
#end
(Note - make whatever methods in the protocol that you need, this is simply an example)
In your MyKeyboardEventsViewController you then include the protocol file, and create an ivar
id <MyKeyboardDelegate> delegate;
Also make it a property and synthesize it.
Whatever class that is going to create the keyboardviewcontroller should delcare themselves as conforming to the protocol.
#interface MyViewController : UIViewController <MyKeyboardDelegate>
...
#end
When you create the MyKeyboardEventsViewController, set the delegate.
MyKeyboardEventsViewController *eventsVC = [[MyKeyboardEventsViewController alloc] init];
[eventsVC setDelegate:self];
Now just implement the delegate method and perform whatever action that is necessary.

Call rootViewController to switch views within content view (iOS)

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];

Delegates Vs. Notifications in iPhoneOS

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.