Understanding custom Delegate - objective-c

So I have an app, and in the app there is a tableView, I have a uinavigationbarbutton that presents a modal viewController. When the user hits a go button in the modal interface, I want it dismiss the modal view and get some of the information in the modal view. I will than put that info in the tableView. To do this, I wrote a custom delegate, but it doesn’t work. I included my code below. Thanks for any help.
TrackerMainViewController.h //the tableView
#import "NewItemViewController.h"
#interface TrackerMainViewController : UITableViewController <UITableViewDelegate, DetailDelegate>
TrackerMainViewController.m
-(void)finishedAddingFoodItemFromDetail:(NSDate *)date whatWasEaten:(NSString *)whatFood whichMeal:(NSString *)meal {
NSLog(#"in delegate method here");
[self.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
NewItemViewController.h // the modal view
#protocol DetailDelegate <NSObject>
-(void)finishedAddingFoodItemFromDetail:(NSDate *)date whatWasEaten:(NSString *)whatFood whichMeal:(NSString *)meal;
#end
#interface NewItemViewController : UIViewController {
id <DetailDelegate> _delegate;
}
#property (nonatomic, retain) id <DetailDelegate> delegate;
#end
NewItemViewController.h
#implementation NewItemViewController
#synthesize delegate = _delegate;
//the go button in the modal view
- (IBAction)Go:(id)sender {
[self.delegate finishedAddingFoodItemFromDetail:[NSDate date] whatWasEaten:#"chicken" whichMeal:#"breakfast"];
}
I put a log in both the go button and in the implementation of the delegate in the tableview, but only the go log is being called.
Thanks

In the code you posted, you dont set the delegate. You need to set it similar to this detailView.delegate = self, otherwise it is nil. You can send messages to a nil-object without any warning and error, nothing will happen.

Related

Call method in different controller in objective c

I have a view controller from where I will be showing a window controller using [runmodal]. I have some textfields and button in the modal window. When i click the button i need to call a method in view controller with the collective data from window controller. How can i achieve this? Is there anything to do with custom delegate method? As I am new to Mac dev and objective c some one help me to do.
You can use the delegation pattern. You define a protocol like this:
#protocol DataProviderDelegate <NSObject>
- (NSDictionary *) retrieveData;
#end
implement this protocol in your view controller:
#interface MainViewController () <DataProviderDelegate>
#end
#implementation MainViewController {
...
- (NSDictionary *) retrieveData {
....
}
...
#end
In your window controller you define a delegate property
#interface ModalWindowController : NSWindowController
#property (nonatomic, weak) id <DataProviderDelegate> dataProviderDelegate;
#end
From the main view controller, set that property to self
modalWindow.dataProviderDelegate = self
At this point the modal window controller is able to call any method of the view controller that is defined in the DataProviderDelegate protocol - for instance:
if (self.dataProviderDelegate) {
[self.dataProviderDelegate retrieveData];
}
To dig more in the delegation pattern I suggest to google for it
You can use a delegate for this. In the ModalViewController you will have to implement a delegate that will have a method per action (textfields, buttons) :
ModalViewController.h
#class ModalViewController;
#protocol ModalViewControllerDelegate <NSObject>
- (void)modalViewControllerDelegateButtonPressed:(APPCameraOverlay *)overlay;
- (void)modalViewControllerDelegate:(APPCameraOverlay *)overlay
textFieldEdited:(NSString *)text;
#end
#interface ModalViewController : UIViewController
#property (nonatomic, weak) id <ModalViewControllerDelegate> delegate;
#end
Then, you will be able to call your delegate methods inside your ModalViewController.m :
ModalViewController.m
// The method linked to your button
- (IBAction)actionButtonPressed {
[self.delegate modalViewControllerDelegateButtonPressed:self];
}
// Your textfield method that is called when input has changed
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.delegate modalViewControllerDelegate:self
textFieldEdited:textField.text];
}
Now, you just have to set your ModalViewController delegate object in the ViewController when showing the modal controller :
ViewController.m
#import "ModalViewController.h"
// We create an extension to the class to implement the delegate protocol
#interface ViewController () <ModalViewControllerDelegate>
#end
#implementation ViewController
// This method gets called by apple when a view controller is showed (modally, pushed or embedded)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// if the view that is showed is the ModalViewController
if ([segue.destinationViewController isKindOfClass:[ModalViewController class]]) {
ModalViewController *controller = segue.destinationViewController;
controller.delegate = self;
}
}
// You have to implement the delegate methods now :
- (void)modalViewControllerDelegateButtonPressed:(APPCameraOverlay *)overlay {
// Do whatever you want when the button is pressed on the ModalViewController
}
- (void)modalViewControllerDelegate:(APPCameraOverlay *)overlay
textFieldEdited:(NSString *)text {
// Do whatever you want when the textfield is edited
}
#end
Add the view controller as an instance variable to your (subclassed) window controller, and then when the button is clicked you can send a method to the view controller with the collective data from the window controller.

Is it possible to force rebuild previous view of a Navigation Controller

I have a table view in my previous view which is get data from an array in my app. I have a view to update data which is push on cell select. Once data is updated in the view i call
[self.navigationController popViewControllerAnimated:YES];
to go back to previous view. But the label get stacked with old and new data I don't know why... If I go one view back and come back again to the tableview everything is fine only new data is shown..
So I guess I have to rebuild view to avoid the problem. Is this possible ?
Your question initially asked about rebuilding a controller, so here's the answer to that:
I'm assuming that your have a navigation stack like this:
An instance of FirstController
An instance of SecondController
An instance of ThirdController
A thing happens in your third controller, and you now want the stack to look like this:
An instance of FirstController
A new instance of SecondController
The first thing to do is to define a delegate protocol for ThirdController, in your header file like this:
#protocol ThirdControllerDelegate;
#class ThirdController : UIViewController
#property (nonatomic, weak) id<ThirdControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol ThirdControllerDelegate <NSObject>
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController;
#end
Instead of having the ThirdController pop itself, it should tell its delegate that the thing happened, like so:
[self.delegate thirdControllerDidDoTheThing:self];
You'll also want to define a delegate protocol for SecondController, in the same way, and you'll want to specify that SecondController can act as a delegate for a ThirdController:
#import "ThirdController.h"
#protocol SecondControllerDelegate;
#class SecondController : UIViewController <ThirdControllerDelegate>
#property (nonatomic, weak) id<SecondControllerDelegate> delegate;
... your existing stuff ...
#end
#protocol SecondControllerDelegate <NSObject>
- (void)secondControllerDidDoTheThing:(SecondController *)secondController;
#end
Notice the extra bit in there where we put <ThirdControllerDelegate> after the #class line.
Now we find the part of the SecondController that shows the ThirdController, and have it set the controller's delegate first:
- (void)showThirdControllerAnimated:(BOOL)animated
{
ThirdController *thirdController = [[ThirdController alloc] init];
thirdController.delegate = self;
[self.navigationController pushViewController:thirdController animated:animated];
}
When the SecondController gets the message from the ThirdController, it should pass it on to its delegate, like this:
- (void)thirdControllerDidDoTheThing:(ThirdController *)thirdController
{
[self.delegate secondControllerDidDoTheThing:self];
}
Finally we modify FirstController so that it can act as the delegate to the SecondController:
#import "SecondController."
#class FirstController : UIViewController <SecondControllerDelegate>
When we show the SecondController, we make the FirstController its delegate:
- (void)showSecondControllerAnimated:(BOOL)animated
{
SecondController *secondController = [[SecondController alloc] init];
secondController.delegate = self;
[self.navigationController pushViewController:secondController animated:animated];
}
Finally we implement the SecondController's delegate method to pop to the first controller, then show a new secondController.
- (void)secondControllerDidDoTheThing:(SecondController *)secondController
{
[self.navigationController popToViewController:self animated:NO];
[self showSecondControllerAnimated:NO];
}
Done.
You've since altered your question; in the case you now describe you can follow the steps above to make the SecondController the delegate of the ThirdController, but then inside thirdControllerDidDoTheThing you just reload the data of your SecondController's view; if it's a UITableView or UICollectionView you'd do that with the reloadData method.
you should refresh your table view every time it is going to be shown; otherwise, the old data would be cached.
In the controller that controls the table view:
- (void)viewWillAppear {
[tableView reloadData];
}

Objective-c proper delegation

I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.

How to call viewDidLoad after [self dismissModalViewControllerAnimated:YES];

Okay. If you have two viewControllers and you do a modal Segue from the first to the second, then you dismiss it with [self dismissModalViewControllerAnimated:YES]; it doesn't seem to recall viewDidLoad. I have a main page (viewController), then a options page of sorts and I want the main page to update when you change an option. This worked when I just did a two modal segues (one going forward, one going back), but that seemed unstructured and may lead to messy code in larger projects.
I have heard of push segues. Are they any better?
Thanks. I appreciate any help :).
That's because the UIViewController is already loaded in memory. You can however use viewDidAppear:.
Alternatively, you can make the pushing view controller a delegate of the pushed view controller, and notify it of the updates when the pushed controller is exiting the screen.
The latter method has the benefit of not needing to re-run the entire body of viewDidAppear:. If you're only updating a table row, for example, why re-render the whole thing?
EDIT: Just for you, here is a quick example of using delegates:
#import <Foundation/Foundation.h>
// this would be in your ModalView Controller's .h
#class ModalView;
#protocol ModalViewDelegate
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView;
#end
#interface ModalView : NSObject
#property (nonatomic, retain) id delegate;
#end
// this is in your ModalView Controller's .m
#implementation ModalView
#synthesize delegate;
- (void)didTapSaveButton
{
NSLog(#"Saving data, alerting delegate, maybe");
if( self.delegate && [self.delegate respondsToSelector:#selector(modalViewSaveButtonWasTapped:)])
{
NSLog(#"Indeed alerting delegate");
[self.delegate modalViewSaveButtonWasTapped:self];
}
}
#end
// this would be your pushing View Controller's .h
#interface ViewController : NSObject <ModalViewDelegate>
- (void)prepareForSegue;
#end;
// this would be your pushing View Controller's .m
#implementation ViewController
- (void)prepareForSegue
{
ModalView *v = [[ModalView alloc] init];
// note we tell the pushed view that the pushing view is the delegate
v.delegate = self;
// push it
// this would be called by the UI
[v didTapSaveButton];
}
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView
{
NSLog(#"In the delegate method");
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
ViewController *v = [[ViewController alloc] init];
[v prepareForSegue];
}
}
Outputs:
2012-08-30 10:55:42.061 Untitled[2239:707] Saving data, alerting delegate, maybe
2012-08-30 10:55:42.064 Untitled[2239:707] Indeed alerting delegate
2012-08-30 10:55:42.064 Untitled[2239:707] In the delegate method
Example was ran in CodeRunner for OS X, whom I have zero affiliation with.

How to go back two viewControllers before, from ABPeoplepicker delegate method

I'm developing an app which uses ABPeopleViewController, and i want to when the user finalized choosing a contact, go backward two viewcontroller before.
Here's how i am arriving to ABPeoplePickerNavigationController:
Tap in a button of a main view controller --> load modal (dialog) view controller --> tap in a button of the modal view controller --> load ABContacts.
I'm implementing the delegate of ABContacts in the modal view, which in turn has a delegate in the main view controller.
I want to go back from ABPeoplePicker delegate method to the main view controller.
Hope this understands and someone can help me, i didn't find anything like this.
My MainViewController.h:
#protocol ModalViewDialogDelegate
- (void)didReceiveMail:(NSString *)mail;
#end
#interface SetUpViewController : UIViewController<UITextFieldDelegate, ModalViewDialogDelegate>{
}
//...
My MainViewController.m:
//...
- (void)didReceiveMail:(NSString *)mail{
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
//...
My ModalView.h:
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#protocol ModalViewDialogDelegate;
#interface DialogViewController : UIViewController<ABNewPersonViewControllerDelegate, ABPeoplePickerNavigationControllerDelegate>{
id<ModalViewDialogDelegate> delegate;
}
#property (nonatomic, assign) id<ModalViewDialogDelegate> delegate;
#property (nonatomic, retain) NSString * mailSelected;
//...
My modalView.m:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
//...here i get the email person property and then i want to go backwards to the main view controller, not the modal.
[self dismissViewControllerAnimated:YES completion:nil];
//don't know if it's ok like this, because in the implementation also dismiss presented viewcontroller.
[_delegate didReceiveMail:self.mailSelected];
return NO;
}
return YES;
}
Try putting this
[_delegate didReceiveMail:self.mailSelected];
inside the completion block of the
[self dismissViewControllerAnimated:YES completion:nil];
that precedes it.
(If that doesnt work you can simply call the dissmiss twice on your maincontroller delegate method, each dismiss will remove one from the stack)
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:NO completion:nil];