Pass Data to UINavigationController Partent - objective-c

I've got a VC (root) that calls a Modal Segue to a UINavigatioController (settings). The user can change what they want. How do I let the first VC (root) know that the changes are done. the UINav's delegate is the (root) VC. any ideas to pass data back from a modal segue? cheers

Use your AppDelegate as a central communication hub that child VCs can call, and pass data to. Try the following:
First set up a shared delegate class method, and a method to pass data to a child VC, in your AppDelegate like so:
// In MyAppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface MyAppDelegate : NSObject {
// rootviewcontroller is the parent of your UINavigationController
UIViewController *rootViewController;
}
+ (MyAppDelegate *)sharedAppDelegate;
-(void)passData:(NSString*)myString;
and so..
// In MyAppDelegate.m
#import "MyAppDelegate.h"
#implementation MyAppDelegate.m
+ (MyAppDelegate.m *)sharedAppDelegate
{
return (MyAppDelegate.m *) [UIApplication sharedApplication].delegate;
}
-(void)passData:(NSString*)myString{
// pass data to parent of UINavigationController
[rootViewController hereIsSomeData:myString];
}
and then from the .m file of your UINavigationController (VC.m), you import your AppDelegate class and instantiate a shared delegate, which will essentially let you "call" the AppDelegate and send it data..
// In VC.m
#import "MyAppDelegate.h";
// ...
NSString *myString = #"alskdjfalsdjfq324r";
[[MyAppDelegate sharedAppDelegate] passData:myString];

There's also another way. You can create a mutable object, such as an NSMutableDictionary in the parent controller and pass that to the child controller. Any changes made to the NSMutableDictionary in the child controller carry over to the original parent controller, since they are both pointers to the same object in memory. Good luck!

Related

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.

Refer to a main view controller property by another class

I work on a project for iPad with Xcode 4.
I have a main view controller with many UITextField.
The TextFieldDelegate is a separate class in a separate file.
How can I refer, from TextFieldDelegate to a property (to a UITextField) of the main view controller (for example assign a value to a double)?
Thank you.
In most cases, if you want to use a separate delegate you should not need more information than what is passed to the delegate (the method's parameters). However, if you don't want to use your MainViewController as a delegate for your UITextField, you can initialize your TextFieldDelegate in your MainViewController instance and pass it the MainViewController instance.
For example you could have:
#import "MainViewController.h"
#interface TextFieldDelegate<UITextFieldDelegate> {
MainViewController* mainViewController;
}
#property(nonatomic,retain) MainViewController* mainViewController;
-(id)initWithController:(MainViewController*)controller;
#end
#implementation TextFieldDelegate
#synthesize mainViewController;
-(id)initWithController:(MainViewController*)controller {
if(self = [super init]) {
//some stuff
self.mainViewController = controller;
}
return self;
}
#end
Then in your MainViewController:
TextFieldDelegate tfd = [[TextFieldDelegate alloc] initWithController:self];
You just need to set the TextFields' delegate to tfd and you should be able to reference the MainViewController properties from the TextFieldDelegate instance. It's also possible to initiate it somewhere else, as long as you send the MainViewController instance to your TextFieldDelegate instance.
Edit: woups forgot a few '*'

How to Call a Controller Class (delegate) method from View Class Event in Objective C/Cocoa

lets say I have an NSWindow Class, that has several events for mouse and trackpad movements
for example this code
IMHO, I think this should be good programming, it is similar to pointing an action of a button to its method in controller.
in MyWindow.m Class I have (which in IB I have set the window class to it)
#implementation MyWindow
- (void)swipeWithEvent:(NSEvent *)event {
NSLog(#"Track Pad Swipe Triggered");
/* I want to add something like this
Note that, Appdelegate should be existing delegate,
not a new instance, since I have variables that should be preserved*/
[AppDelegate setLabel];
}
#end
and in My AppDelegate.h I have
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
IBOutlet NSTextField *label;
}
-(void)setLabel;
#end
and in my AppDelegate.m I have
-(void)setLabel
{
[label setStringValue:#"swipe is triggered"];
}
I have tried #import #class, [[... alloc] init], delegate referencing in IB (I made an object class of MyWindow - thanks to the answer of my previous question )
that latter seems the closest one, it works if both of the classes are delegates, so I could successfully call the "setLabel" action from a button in a second controller (delegate class)'s IBAction method,
but this View Events seem not communicating with the delegate's action although their code is executing.
You are sending a message to the AppDelegate class, not the instance of your AppDelegate class which is accessible from the NSApplication singleton ([NSApplication sharedApplication])
[AppDelegate setLabel];
This is wrong, to get the delegate do this:
AppDelegate* appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
then send the message to that instance:
[appDelegate setLabel];

Pass a delegate method multi-levels up a navigationController stack

I have a button in a toolbar that has a popover associated with it. Within the popover, I've set up a navigationcontroller. What I'm trying to achieve is for a view controller two or three levels down in the navigationcontroller stack to change the state of the button that originally called the popover. I've managed to do this, but it requires a couple of delegates and seems very clunky; my reason for posting here is to figure out if there is a more elegant and efficient solution.
So, to get started:
//ProtocolDeclaration.h
#protocol ADelegate <NSObject>
- (void)changeButtonState;
#end
#protocol BDelegate <NSObject>
- (void)passTheBuckUpTheNavChain;
#end
Then, for my MainController that holds the button:
// MainController.h
#import "A_TableController.h"
#import "ProtocolDeclaration.h"
#class A_TableController;
#interface MainController : UIViewController <ADelegate>
...
#end
// MainController.m
- (IBAction)buttonPressed:(id)sender {
A_Controller *ac = [[[A_Controller alloc] init] autorelease];
ac.ADelegate = self;
UINavigationController *nc = [[[UINavigationController alloc] initWithRootViewController:ac] autorelease];
UIPopoverController *pc = [[[UIPopoverController alloc] initWithContentViewController:nc] autorelease];
[pc presentPopoverFromBarButtonItem...]
}
// ADelegate Method in MainController.m
- (void)changeButtonState
{
self.button.style = ....
}
Now, for A_Controller, my rootViewController for my navController:
//A_Controller.h
#import "B_Controller.h"
#import "ProtocolDeclaration.h"
#class B_Controller;
#interface A_Controller : UITableViewController <BDelegate>
{
id<ADelegate> delegate;
...
}
#property (assign) id<ADelegate> delegate;
...
#end
//A_Controller.m
//In the method that pushes B_Controller onto the stack:
B_Controller *bc = [[[B_Controller alloc] init] autorelease];
bc.BDelegate = self;
[self.navigationController pushViewController:bc animated:YES];
//In the BDelegate Method in A_Controller:
- (void)passTheBuckUpTheNavChain
{
[ADelegate changeButtonState];
}
Lastly, in B_Controller:
//B_Controller.h
#import "ProtocolDeclaration.h"
#interface A_Controller : UITableViewController
{
id<BDelegate> delegate;
...
}
#property (assign) id<BDelegate> delegate;
...
#end
//B_Controller.m
//Where it's necessary to change the button state back up in MainController:
[BDelegate passTheBuckUpTheNavChain];
Now, this works, but it seems like a sort of Rube-Goldberg-ish way of doing it. I tried init'ing both A_Controller and B_Controller in MainController and setting B_Controller's delegate to MainController right there, and then using a NSArray of the two viewcontrollers to set the navcontroller stack, but it really messed up the way the viewcontrollers appeared in the navcontroller: I'd get a back button even on the rootviewcontroller of the navcontroller and you could just keep clicking Back and going round and round the navcontroller stack instead of stopping at the root. Any ideas on a better way to do this?
If you want to decouple the view controllers you could define a notification, and just post that one.
This way only the root view controller that receives the notification needs to know about the deep nested view controller.
Define the notification like this:
// In .h
extern NSString* const BlaControllerDidUpdateNotification;
// In .m
NSString* const BlaControllerDidUpdateNotification = #"BlaControllerDidUpdateNotification";
The deeply nested controller (BlaController) need to post the message like this:
[[NSNotificationCenter defaultCenter]
postNotificationName:BlaControllerDidUpdateNotification
object:self];
And the root view controller need to act on it with something like this:
// In init or the like:
[[NSNotificationCenter defaultCender]
addObserver:self
selector:#selector(blaControllerDidUpdateNotification:)
name:BlaControllerDidUpdateNotification
object:nil];
// And then define this method:
-(void)blaControllerDidUpdateNotification:(NSNotification*)notification {
// Update UI or whatever here.
}