Implementing delegation - iphone

I think I'm following how delegation works, here's the tutorial I followed, but I'm messing up somewhere. I'm expecting my delegate to NSLog but it's not. Can anyone find out what am I missing or doing wrong?
My MainViewController.h:
#interface MainViewController : UITableViewController < AddClassDelegate >
MainViewController.m:
- (void)cancelAddingClass {
NSLog(#"Canceled Yo");
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
/*
When a row is selected, the segue creates the detail view controller as the destination.
Set the detail view controller's detail item to the item associated with the selected row.
*/
if ([[segue identifier] isEqualToString:#"addClassSegue"]) {
UINavigationController *nc = (UINavigationController *)segue.destinationViewController;
AddClassViewController *addClassVC = (AddClassViewController *)[nc.viewControllers objectAtIndex:0];
addClassVC.delegate = self;
}
My modal view controller AddClassViewController.h:
#protocol AddClassDelegate <NSObject>
- (void)cancelAddingClass;
#end
#interface AddClassViewController : UITableViewController
#property (weak, nonatomic) id< AddClassDelegate > delegate;
- (IBAction)cancelButtonPressed:(id)sender;
AddClassViewController.m:
#synthesize delegate;
- (IBAction)cancelButtonPressed:(id)sender {
[self.delegate cancelAddingClass];
}
cancelButtonPressed:
is hooked up to the modal view's Cancel button in Storyboard.

Your code looks fine, which suggests the problem is somewhere we can't see. My guess is here:
AddClassViewController *addClassVC = [segue destinationViewController];
addClassVC.delegate = self;
NSLog(#"segued");
Have you embedded your modal view controller in a navigation controller? If so, destinationViewController gives you the navigation controller, not the AddClassViewController. Check what class addClassVC actually is in the debugger.
If it is a navigation controller, no problem, you just need to get to your actual VC using the .viewControllers property. On several lines to make it simpler to understand:
UINavigationController *nc = (UINavigationController *)segue.destinationViewController;
AddClassViewController *addClassVC = (AddClassViewController *)[nc.viewControllers objectAtIndex:0];
addClassVC.delegate = self;
You can do it in fewer lines but it's a mess of casting and nested brackets, which is harder to debug.

Is it possible your main view controller is being released? That would set the weak reference to nil and the message you send to your delegate would simply be ignored because you'd be messaging nil.

Everything is perfect. I haven't seen any issue. Keep break points everywhere and debug it.

Related

How to trigger an action from one view controller to another in iOS

How would I go about changing the a UILabel property in another view controller?
I have #import "SecondViewController.h" imported in the FirstViewController.m file and then
I have the following in a method in FirstViewController
-(IBAction) someAction {
SecondViewController *objV1 = [[SecondViewController alloc]init];
objV1.secondViewControllerLabel.alpha = 0.2;
NSLog(#"someAction");
}
when someAction is called nothing happens to the UILabel in the SecondViewController.
also, in this example both first and second view controllers are in another view controller called MainViewController. So, they are both onscreen at the same time.
thanks for any help.
From what you tell us, it would seem that you need to set the "embeded view controllers" as childs of the parent View Controller.
[mainViewController addChildViewController:childViewController];
[childViewController.view setFrame:self.view.bounds];
[self.childContainerView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
This is very powerful, because you can forward IBActions from the mainViewController to their child...
[mainViewController childViewControllers]
Returns an array of them, and also take a look at
– shouldAutomaticallyForwardRotationMethods
– shouldAutomaticallyForwardAppearanceMethods
So your child get automatically informed about the rotations of their parent.
To answer your question, you could do something like:
// In Parent View Controller
- (IBAction) anAction:(id) sender
{
for (CustomChildController *child in self.viewControllers) {
[child handleSomeAction];
}
}
Check out what the docs say for more details.
#Goles answer will work, but if you specifically want to trigger the change from FirstViewController.m you need to pass in a reference to SecondViewController somehow.
So you could do it with a custom init that takes a reference to your second viewcontroller as a parameter, or create a property on your FirstViewController that you can set from outside, which would be something like this:
FirstController.h:
#interface
..
#property (strong, nonatomic) UIViewController *second;
...
#end
FirstController.m:
#implementation
#synthesize second
In your parent ViewController you would create both the child view controllers, then:
ViewController1.second = ViewController2;
Then your action method would become:
-(IBAction) someAction {
self second.secondViewControllerLabel.alpha = 0.2;
NSLog(#"someAction");
}
Since in the secondViewController, secondViewControllerLabel has not been created yet, 'objV1.secondViewControllerLabel.alpha' will have no effect. Ideally, you should create a NSNumber property called labelAlpha in the secondViewController, set that property in the firstViewController, and then in the viewDidLoad of the second controller, add this line ::
self.secondViewControllerLabel.alpha = self.labelAlpha;
This will work for you.

How to pass data back from one view to other View in IOS?

I am able to pass data from one view to other view without any problem. But here is my situation i am facing problem.
Let's say
-> Passing data to other view
ViewA->ViewB This working perfectly fine
ViewB->ViewC This working perfectly fine
ViewC->ViewB Here is the problem.
I tried using push segue ,it goes to ViewB but when i press back button it goes to ViewC->back button->ViewB->back button->View A. From ViewB when i press back button it must goes to ViewA.
Tried using modal segue, it goes to ViewB but then i can't go any where.
As i am new to iOs I am really haven't got any idea about how to achieve this?
How to pass data back to ViewB from ViewC?
I think you guys can help me.
Edit
In View A i am calling ViewB like this
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"RemixScreen"])
{
if([self.recipeFrom isEqualToString:#"ViewB"])
{
ViewB *reciepe = [segue destinationViewController];
//somedata i will pass here
}
}
}
In View B i am calling like this
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"ViewA"])
{
ViewA *data = [segue destinationViewController];
//some data
}
}
Thanks for your help guys.
The correct way to do this is to use delegates. You use properties in prepareForSegue to pass data forward and delegates to pass data back.
The way it works is to have a delegate property in ViewC that is set by ViewB in prepareForSegue. That way ViewC can communicate with ViewB via a protocol you set up.
EDIT: Adding code to demonstrate:
ViewControllerBInterface:
#protocol ViewBProtocol
- (void)setData:(NSData *)data;
#end
#interface ViewBController: UIViewController <ViewBProtocol>
...
#end
Here we make ViewBController follow our protocol that ViewCController will communicate to.
Next up is ViewCController interface:
#interface ViewCController: UIViewController
#property (nonatomic, weak) id<ViewBProtocol> delegate;
...
#end
Now we look at ViewBController's prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController* controller = [segue destinationViewController];
if ([controller isKindOfClass:[ViewCController class]])
{
ViewCController* viewCController = (ViewCController *)controller;
viewCController.delegate = self;
}
}
As you can see we link the ViewC controller to the ViewB one via the delegate property. Now we do something in ViewC:
- (void)sendData:(NSData *)data
{
[self.delegate setData:data];
}
And you can use that in your viewWillDisappear method of ViewCController if you wanted to.
The way I have gotten around this problem in a recent project is the following;
View A is the parentViewController, so can be accessed from View B and View C at any time like this;
ViewAClass *parentView = (ViewAClass *)self.parentViewController;
You can then read and write to a property of the View A, like;
NSString *string = parentView.someStringProperty;
or
parentView.someStringProperty = #"Hello World";
EDIT - "To go back from view B to ViewA with out back button"
[parentView popViewControllerAnimated:YES];
Are you using navigation controller? If yes, you can easily pass data with Singleton, which is my favorite way to do that and also its easy to use. Otherwise, if you're trying to navigate through views with buttons or something, try to set a identifier to your segue, then call the method "performSegueWithIdentifier"

Error setting NSNumber property of destination view controller in prepareForSegue

I have a modal view controller that attempts to set an flag (an NSNumber property) of the source view controller that called it in its prepareForSegue method. It fails to build with the error "No known instance method for selector 'setGoToEditNewNote:'". Here is the code:
Source View Controller .h:
#property (strong, nonatomic) NSNumber *goToEditNewNote;
Source View Controller .m:
#synthesize goToEditNewNote;
...
- (void)viewDidLoad
{
[super viewDidLoad];
// clear the flag
goToEditNewNote = [[NSNumber alloc] initWithBool:FALSE];
...
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if ([goToEditNewNote boolValue] == TRUE) {
goToEditNewNote = FALSE;
[self performSegueWithIdentifier: #"editNote" sender: self];
...
Modal View Controller .h:
Modal View Controller .m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"done"])
{
[self done];
[[segue destinationViewController] setGoToEditNewNote:TRUE]; <<< get error here
}
}
I suspect the problem may have to do with goToEditNewNote not being retained when the modal view is loaded, but i don't understand why not. I have set other properties such as the managedObjectContext in a similar way with success. Please be as specific as possible in your answer as I am a novice with ARC. Thanks - Tom
destinationViewController is of the id type which doesn't contain a goToEditNewNote property. You probably want to cast destinationViewController to the SourceViewController type. This is usually a warning but it sounds like you're treating all warnings as errors (I do this too).
Your -prepareForSegue:sender: should look something like this.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"done"])
{
[self done];
SourceViewController *sourceViewController = (SourceViewController *)[segue destinationViewController];
[sourceViewController setGoToEditNewNote:[NSNumber numberWithBool:YES]];
}
}
I had the same problem. It took me 2 days and could't find any right answer on the web. Strangely, the same code in Stanford CS193p is working. Luckily I solved it now! The "set property method can not be found" error's cause is forgetting to #import "xxxx.h", where xxxx is the name of your segue's destination view controller. If you dodn't import, it never knows what xxxx's properties are!
Easy, now the segue works.
At first if you are using ARC you NEVER have to use retain (or release, too). You only have to instantiate an Object, but you dont have to care about to delete it. But take care if Objects references in a circle at each other. If A references B, B references C, C references A and you access these Variables via a Variable D (wich points at A) and you no more use D the circle of A-B-C stays in Memory. Then you have to define one of the A-B-C References as 'weak'.
But to your real question: Is it possible that you missed the #synthesize Statement in the Implementation file?
The error is telling you that you don't have any instance method that corresponds to the "setGoToEditNewNote:" selector; did you #synthesize your property (or write the accessors yourself)?

Parent View Controller and Model View Controller dismissal detection not working

I have a UIViewController called ShowListViewController that uses a Modal View Controller to push another view onto the stack:
AddShowViewController *addShowViewController = [[AddShowViewController alloc] init];
[addShowViewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentModalViewController:addShowViewController animated:YES];
I would then like to call my method populateTableData of the ShowListViewController class when the addShowViewController disappears.
I would think that the answer found here would work, but it doesn't. My method populateTableData is not detected as an optional method to use.
Essentially my questions is: How do I detect when a Modal View Controller disappears so as to call a method within the class that pushed it on the stack?
This may not be a best solution, but can do what you want at this time.
In your showlistcontroller add an instance variable like
BOOL pushedView;
#implementation ShowListViewController
and before you do the modal presentation set its values as YES like
pushedView = YES;
[self.navigationController presentModalViewController:popView animated:YES];
in the viewWillAppear of ShowListViewController you can detect whether it is appearing because pop getting dismissed or not like
if (pushedView) {
NSLog(#"Do things you would like to on pop dismissal");
pushedView = NO;
}
I think you would like something like this.
You make a delegate inside ur modalVC like this:
#protocol ModalViewDelegate <NSObject>
- (void)didDismissModalView;
#end
and implement it in your MainVC like this:
#interface MainViewController : UIViewController <ModalViewDelegate>
{
Then u will make a delegate property in your modalVC like this:
#interface ModalShizzle : UIViewController
{
id<ModalViewDelegate> dismissDelegate;
}
You set the dismissDelegate of your ModalVC to your MainVC and then you make the delegate method. Before you dismiss it however you will call the ModalVC to do one last thing. (which is populate your table). You will call for the data inside your MainVC and then do whatever you feel like it, just before you dismissed your modalVC.
-(void)didDismissModalView
{
//call ModalVC data here...
//then do something with that data. set it to a property inside this MainVC or call a method with it.
//method/data from modalVC is called here and now u can safely dismiss modalVC
[self dismissModalViewControllerAnimated:YES];
}
Hope it helps ;)
OK so it appears that in Apple's template for Utility App's they ignore what the docs for [UIViewController][1] say and actually go out of their way to call dismissModalViewControllerAnimated: from the UIViewController that pushed the modal view onto screen.
The basic idea in your case will be
Define a protocol for AddShowViewControllerDelegate
Make ShowListViewController implement this protocol
Call a method on the delegate to ask it to dimiss the modal view controller
For a full example just create a new project with Utility template and look at the source for FlipsideViewController and MainViewController
Here is an example adapted for your needs:
AddShowViewController.h
#class AddShowViewController;
#protocol AddShowViewControllerDelegate
- (void)addShowViewControllerDidFinish:(AddShowViewController *)controller;
#end
#interface AddShowViewController : UIViewController
#property (nonatomic, assign) id <AddShowViewControllerDelegate> delegate;
- (IBAction)done:(id)sender;
#end
AddShowViewController.m
- (IBAction)done:(id)sender
{
[self.delegate addShowViewControllerDidFinish:self];
}
ShowListViewController.h
#interface ShowListViewController : UIViewController <AddShowViewControllerDelegate>
{
...
}
ShowListViewController.m
- (void)addShowViewControllerDidFinish:(AddShowViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
[self populateTableData];
}

UIAlertView Question

I have a small doubt. I have a NSObject class where I am trying to display an alert view. So after the alert view is displayed when I tap on OK button I want to push a navigation controller onto the stack. Can I push a navigation controller from general NSObject class? Please let me know guys..thanks for your time..
This is the code..
- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
SettingsViewController *homeView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self.navigationController pushViewController:homeView animated:NO];
[homeView release];
}
I am creating a property called navigationController of type UINavigationController and when I catch the error I am displaying an alert view and I am using above method to push the view controller but it doesn't work..
Yes and no... depending on how you have your application set up. To push views onto the navigation stack you need to have a navigation controller.
Does your NSObject have access to this navigation controller - you might have to set up a delegate method that gets called from your delegate view when the alert view delegate gets called in your NSObject.
I'm just wondering why you're displaying a UIAlertView in an NSObject, why aren't you displaying it in a UIView or a UIViewController?
CustomObject.h
#protocol CustomObjectDelegate<NSObject>
#optional
- (void)customObjectAlertViewDidClickOk;
#end
#interface CustomObject : NSObject <UIAlertViewDelegate>{
id<CustomObjectDelegate> delegate;
}
#property (nonatomic, assign) id<CustomObjectDelegate> delegate;
#end;
CustomObject.m
#synthesize delegate;
// then put this:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
[delegate customObjectAlertViewDidClickOk];
}
Then your ViewController .h file needs to include the custom object and assign the delegate methods:
#include "CustomObject.h"
#interface MyViewController : UIViewController <CustomObjectDelegate> {
}
#end
and the .m viewDidLoad (or similar):
- (void)viewDidLoad{
CustomObject *obj = [[CustomObject alloc] init];
[obj setDelegate:self];
}
- (void)customObjectAlertViewDidClickOk{
AnotherViewController *page = [[AnotherViewController alloc] initWithNibName:nil bundles:nil];
[self.navigationController pushViewController:page];
}
Thats how I would do it - given I'm not too sure i understand quite what you're asking. :) thats all off the top of my head as well - so don't take it letter for letter, but you have the basis there to start off with. You can build on it. Look up #protocols and delegate methods, its all in there. :)