I'm using a NavigationController to "push" viewControllers from the rootView of an app.
I want to use delegates to comunicate the currently loaded view and the rootViewController. I was able to do this using NSNotificationCenter, but want give a try to delegates for this particular situation, since the communication is always going to be one-to-one.
In the view that is pushed, I declared the following delegate protocole in the header file:
#import <UIKit/UIKit.h>
#protocol AnotherViewControllerDelegate;
#interface AnotherViewController : UIViewController {
id <AnotherViewControllerDelegate> delegate;
}
- (IBAction) doAction;
#property (nonatomic, assign) id delegate;
#end
#protocol AnotherViewControllerDelegate <NSObject>
- (void) doDelegatedAction:(AnotherViewController *)controller;
#end
The doAction IBAction is connected to a UIButton in the view. In my implementation file, I added:
#import "AnotherViewController.h"
#implementation AnotherViewController
#synthesize delegate;
- (IBAction) doAction {
NSLog(#"doAction");
[self.delegate doDelegatedAction:self];
}
In my RootViewController.h I added AnotherViewControllerDelegate to the interface declaration:
#interface RootViewController : UIViewController <AnotherViewControllerDelegate> {...
and this to my implementation file
- (void) doDelegatedAction:(AnotherViewController *)controller {
NSLog(#"rootviewcontroller->doDelegatedAction");
}
Unfortunately it's not working. doDelegatedAction in the rootViewController is not been called. I suspect it's because of the way I push the AnotherViewController:
AnotherViewController *detailViewController = [[AnotherViewController alloc] initWithNibName:#"AnotherViewController" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
Should I tell, in any way, to AnotherViewController that its delegate is going to be RootViewController just in the moment it's been pushed? or am I missing something else?
You need to set the delegate of AnotherViewController to the rootViewController in order for everything to be connected up properly.
If you are initializing AnotherViewController in your rootViewController it would be:
AnotherViewController *detailViewController = [[AnotherViewController alloc] initWithNibName:#"AnotherViewController" bundle:nil];
detailViewController.delegate = self;
[self.navigationController pushViewController:detailViewController animated:YES];
Related
I'm trying to wire up the delegate in KGModal to MainView, but it doesnt work. Basically I show SecondViewController in KGModal (Github), and when dismissing the KGModal view i want the MainView to know that through the delegate. Any ideas? (sorry for bad explanation). Can't get it to work.
KGModal.h
#class KGModal;
#protocol KGModalDelegate <NSObject>
- (void)modalControllerDidFinish:(KGModal *)controller;
#end
#interface KGModal : NSObject {
UIButton *dismissButton;
}
#property (weak, nonatomic) id <KGModalDelegate> delegate;
KGModal.m
-(void)dismissButtonPressed:(id)sender {
[self.delegate modalControllerDidFinish:self];
[self hideAnimated:self.animateWhenDismissed];
}
MainView.h
#interface MainView : UIViewController <KGModalDelegate>
#property(weak) id<KGModalDelegate> delegate;
MainView.m
- (void)modalControllerDidFinish:(KGModal *)controller{
NSLog(#"Dismissed.");
}
-(IBAction)modalShowing {
SecondViewController *view2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
KGModal *kg = [[KGModal alloc] init];
kg.delegate = self;
[[KGModal sharedInstance] showWithContentView:view2.view andAnimated:YES];
}
Problem solved. I set the delegate to different instances of KGModal.
KGModal *kgm = [KGModal sharedInstance];
kgm.delegates = self;
[kgm showWithContentView:view2.view andAnimated:YES];
Thanks to Hot Licks!
I could use some help with custom delegates. I'm trying to make a protocol that sends a message to its delegate to dismiss the popover view. Here is what I'm trying.
In the popoverViewController.h
#import <UIKit/UIKit.h>
#protocol MyPopoverDelegate <NSObject>
-(void) didSelectLanguage;
#end
#interface Popover : UITableViewController{
id <MyPopoverDelegate> delegate;
NSMutableArray *languageData;
}
#property (nonatomic, assign) id <MyPopoverDelegate> delegate;
#end
.m
#synthesize delegate;
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"You selected %#", [languageData objectAtIndex:[indexPath row]]);
[self.delegate didSelectLanguage];
}
...
And in the ViewController that presents the popover
#import <UIKit/UIKit.h>
#import "popoverViewController.h"
#interface ChoicesChoices : UIViewController <UIPopoverControllerDelegate, MyPopoverDelegate>{
UIPopoverController *popover;
}
- (IBAction)facebook:(id)sender;
- (IBAction)twitter:(id)sender;
- (IBAction)sms:(id)sender;
- (IBAction)copy:(id)sender;
- (IBAction)email:(id)sender;
- (IBAction)home:(id)sender;
- (IBAction)mute:(id)sender;
- (IBAction)note:(id)sender;
#property (nonatomic, retain) UIPopoverController* popover;
#end
and .m
#synthesize popover;
...
- (void)didSelectLanguage{
[popover dismissPopoverAnimated:YES];
NSLog(#"didSelectLanguage fired");
}
When I select a row in the table of the popover, didSelectLanguage does not get called. Any ideas on what I might be doing wrong? Thanks for your help.
Make sure you are setting your delegate to the be the view controller that is presenting your popover. Something like this in ChoicesChoices.m:
- (void)presentPopover
{
// assuming ARC for all allocations
Popover *myController = [Popover new];
myController.delegate = self;
self.popover = [[UIPopoverController alloc] initWithContentViewController:myController];
[self.popover presentPopover...]; // two flavors here, FromRect: and FromBarButtonItem:, that's left up to you to choose which one is correct.
}
Make sure you set the delegate in the presenting view controller when you create the instance of your custom class.
popover.delegate = self
Also, it looks like your property is a standard popover controller instead of an instance of your custom view controller.
I am fairly new to Obj-C and learning about using protocols and delegates.
I have no trouble following examples to implement a protocol/passing data when there are only two views, however, I am getting an "unrecognized selector" error when I try to call a method when I have several subviews.
For example, in a scenario where I have
FirstViewController
SecondViewController
ThirdViewController
I would like ThirdViewController to call back to the FirstViewController.
Generic code would be something like:
in FirstViewController.h
#interface FirstViewController : UIViewController <MyProtocol>
in firstViewController.m
//present a second controller which will control settings for the app
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"secondViewController" bundle:nil];
secondViewController.delegate= self;
secondViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController: secondViewController animated: YES];
and later
-(void) aMethod{
//carry out some action here
}
in secondViewController.m
//present a third controller...maybe a table view for selecting music
ThirdViewController *thirdViewController = [[ThirdViewController alloc] initWithNibName:#"thirdViewController" bundle:nil];
thirdViewController.delegate= self;
thirdViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController: thirdViewController animated: YES];
in ThirdViewContoller.h
//Create a protocol to implement options back on the firstViewController
#protocol MyProtocol;
#interface thirdViewController
{
IBOutlet UIButton *aButton;
}
#property (nonatomic, weak) id<MyProtocol> delegate;
-(IBAction) callMethod:(id)sender;
#end
#protocol MyProtocol <NSObject>
- (void) aMethod;
#end
in ThirdViewController.m
#synthesize delegate;
-(IBAction) callMethod:(id)sender{
[self.delegate aMethod];
}
When running it appears that the message is only sent back to secondViewController and not to the firstViewController because the error is:
-[SecondViewController aMethod:]: unrecognized selector sent to instance 0x19d620
I think there is something fundamental concept with setting delegate outlets that haven't learned yet, or the structure of the program is wrong.
There are numerous examples of code using only two view that work well here, but i haven't found much info on a multiple views. I apologize in advance if my program design is really incorrect.
You need SecondViewController to conform to your protocol:
#interface SecondViewController : UIViewController <MyProtocol>
You're trying to call a method on the second view controller that only exists in the first. If you want to communicate back to the first view controller, then you'll have to define a second protocol to do that.
This is a really good academic exercise to learn about the capabilities and limitations of protocols, but you should also notice the conflict in naming. Try to be as descriptive as possible when naming your protocols. Ideally, you'd have a set of header files that looked like this:
#interface FirstViewController : UIViewController <SecondViewControllerDelegate>
#end
And the second view controller:
#protocol SecondViewControllerDelegate <NSObject>
-(void)someSecondViewControllerDelegateMethod;
#end
#interface SecondViewController : UIViewController <ThirdViewControllerDelegate>
#protocol (nonatomic, weak) id <SecondViewControllerDelegate> delegate;
#end
And finally, the third view controller:
#protocol ThirdViewControllerDelegate <NSObject>
-(void)someThirdViewControllerDelegateMethod;
#end
#interface ThirdViewController : UIViewController
#protocol (nonatomic, weak) id <ThirdViewControllerDelegate> delegate;
#end
So the second view controller could implement -(void)someThirdViewControllerDelegateMethod and it could look like:
-(void)someThirdViewControllerDelegateMethod
{
[self.delegate someFirstViewControllerDelegateMethod];
}
And that's how the third view controller could call back to the first; it sort of cascades and passes on the message.
I have an existing TableViewController as follows
// TableViewController.h
#interface TableViewController : UITableViewController { NSArray *dataArray; }
#property (nonatomic, retain) NSArray *dataArray;
And a navAppDelegate - to be specific:
// navAppDelegate.h
#import <UIKit/UIKit.h>
#interface navwAppDelegate : NSObject
<UIApplicationDelegate, UINavigationControllerDelegate> {
UIWindow *window;
IBOutlet UINavigationController *navigationController;}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) UINavigationController *navigationController;
// navAppDelegate.m
#import "navAppDelegate.h"
#implementation navigationtableviewAppDelegate
#synthesize window, navigationController;
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[window makeKeyAndVisible];
[window addSubview:[navigationController view]];
}
Now, I simply added the files to an existing project, except I put the content of (void)applicationDidFinishLaunching{} in a (void)viewDidLoad{} since it's a view from now on and not a window (right?). It doesn't work and my guess is that I have to change all the window calls from above to a view? What am I making here fundamentally wrong?
I'm making a call from
// StartOffHere.m
- (void)LetsGoButtonTouched {
navAppDelegate *newview = [[navAppDelegate alloc] initWithNibName:nil bundle:nil]; // I get a SIGABRT here
[[self navigationController] pushViewController: newview animated: YES];
}
- (void)LetsGoButtonTouched {
TableViewController *tableViewController = [[TableViewController alloc] init];
[[self navigationController] pushViewController:tableViewController animated: YES];
}
Try that. Is that what you wanted to happen?
If so, what I have done is created a new instance of your table view controller and pushed that. In your original code, you were trying to push on the app delegate which cannot be done; the app delegate is not a view controller. TableViewController, your subclass of UITableViewController, is though, so you can use this for the pushViewController: method - and the table view will appear on screen.
Thanks Benjamin. Excellent. But it didn't work quite that way though - after two hard days I managed it to run. For those who are interesed, here's what I did:
If you want to add an existing TableViewController with a NavigationController you'll only need the TableViewController and the DetailViewController files. Forget the AppDelegate.
Do twice new file -> Subclass and copy your existing code into identical TableViewController .h/.m/.xib - and DetailViewController .h/.m/.xib respectively.
When you make the method call you have to integrate both the TableViewController and the NavigationController - like this:
- (void)LetsGoButtonTouched {
TableViewController *tvc = [[[TableViewController alloc] init] autorelease];
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:tvc]autorelease];
[self presentModalViewController:navigationController animated:YES];
By the way, this question gave me the hint:
Add a UINavigationBar to a UITableViewController without a UINavigationController
I'm trying to assign SecondViewController as a delegate object of FirstViewController (if I understand correctly). However FirstViewController doesn't send any messages to SecondViewController.
Am I allowed to pretend as though SecondViewController did get a message from FirstViewController and respond to the FirstViewController? (Note: My SecondViewController is in charge of a view that has a button. When I press the button on my SecondViewController's view I want it to tell the FirstViewController to update its view)
FirstViewController.h
#import <UIKit/UIKit.h>
#protocol FirstViewControllerDelegate <NSObject>
#optional
- (void) setAnotherLabel;
#end
#interface FirstViewController : UIViewController {
IBOutlet UILabel *label;
id <FirstViewControllerDelegate> delegate;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#property (nonatomic, assign) id <FirstViewControllerDelegate> delegate;
- (void) pretendLabel;
- (void) realLabel;
#end
FirstViewController.m
#import "FirstViewController.h"
#implementation FirstViewController
#synthesize label;
#synthesize delegate;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void) setAnotherLabel;
{
label.text =#"Real";
[self.view setNeedsDisplay];
}
- (void) pretendLabel;
{
label.text =#"Pretend";
[self.view setNeedsDisplay];
}
- (void) realLabel;
{
[self setAnotherLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
label.text=#"Load";
[self pretendLabel];
}
...
#end
SecondViewController.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "FirstViewController.h"
#interface SecondViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, FirstViewControllerDelegate>
{
UIImage *image;
IBOutlet UIImageView *imageView;
}
- (IBAction) sendPressed:(UIButton *)sender;
- (IBAction) cancelPressed:(UIButton *)sender;
#end
SecondViewController.m
#import "SecondViewController.h"
#implementation SecondViewController
- (IBAction) sendPressed:(UIButton *)sender
{
FirstViewController *fvc = [[FirstViewController alloc] init];
[fvc setDelegate:self];
//how do I find out if I'm actually the delegate for FirstViewController at this point?
[fvc realLabel];
self.tabBarController.selectedIndex = 0;//switch over to the first view to see if it worked
}
There are a few issues with this and what appears to be a bit of confusion.
I assume that FirstViewController and SecondViewController are in separate tabs in the tab bar controller.
In the sendPressed: method, you're creating a new instance of FirstViewController - this is not the same FirstViewController that is in your tab bar controller and why calling realLabel has no effect.
The second point is that you appear to misunderstand how delegation works - there is no reason for a delegate in the code you posted.
Good read for getting to grips with delegates: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/CommunicateWithObjects.html
As for a solution to your problem there are a few options:
Post a notification from SecondViewController that FirstViewController is observing (lots of information available on the net regarding notifications).
Get the specific instance of FirstViewController within the self.tabBarController.viewControllers array and call the method from there. Something like...
- (IBAction) sendPressed:(UIButton *)sender
{
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[FirstViewController class]])
{
FirstViewController *firstViewController = (FirstViewController *)controller;
[firstViewController realLabel];
}
}
self.tabBarController.selectedIndex = 0;//switch over to the first view to see if it worked
}
There are more options available than this, but the above will give you a good start into researching the best approach for your need.
Hope this helps.