Search code examples
objective-cdelegationchildviewcontrollerparentviewcontroller

Understanding delegation


I am trying to understand delegation. I have written a small project to try to tackle this. I have also had help from S.O. I am stuck on the very last part of it. My project is simple. We have a main view controller that has a button "start". This button triggers a container view that's hooked to a ContainerViewController. I have done a small animation to get the container to slide from the side. I have another button "back" that makes the container view disappear with the opposite animation. Note, I am copying a lot of code and making up the rest as I am learning, so there may be unnecessary lines, please feel free to comment.

ViewController.h

#import <UIKit/UIKit.h>
#import "ContainerViewController.h"

@interface ViewController : UIViewController <ContainerViewControllerDelegate>

- (IBAction)Start:(id)sender;
- (IBAction)back:(id)sender;

@end

Here is the m file:

#import "ViewController.h"

@interface ViewController ()

@property UIViewController *childView;
@property NSString *myReceivedValue;
@property ContainerViewController *controller;
@property IBOutlet UILabel *myLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.childView = [self.storyboard instantiateViewControllerWithIdentifier:@"childVC"];
   self.controller = [self.storyboard instantiateViewControllerWithIdentifier:@"childVC"];
    self.controller.delegate = self;

    self.childView = [self.childViewControllers lastObject];
    [self.childView.view removeFromSuperview];
    [self.childView removeFromParentViewController];
    self.childView.view.userInteractionEnabled = NO;

    }

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)Start:(id)sender {

    self.childView.view.frame = CGRectMake(0, 84, 320, 210);

    [self.childView didMoveToParentViewController:self];

    CATransition *transition = [CATransition animation];
    transition.duration = 1;
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    [transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [self.childView.view.layer addAnimation:transition forKey:nil];

    [self.view addSubview:self.childView.view];
    self.childView.view.userInteractionEnabled = YES;

}

- (IBAction)back:(id)sender {

    [self.childView willMoveToParentViewController:nil];

    [UIView animateWithDuration:1
                          delay:0.0
         usingSpringWithDamping:1
          initialSpringVelocity:1
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^{

                         self.childView.view.frame = CGRectMake(-320, 84, 320, 210);

                     } completion:^(BOOL complete){

                         [self.childView removeFromParentViewController];
                     }];

}

- (void) passValue:(NSString *) theValue
{
  // here is where you receive the data
}
@end

Ok, so the Container View has a pickerView of which it is the delegate and this pickerView has just an array of ten colors to chose from:

h file for the container view:

#import <UIKit/UIKit.h>


@protocol ContainerViewControllerDelegate;

@interface ContainerViewController : UIViewController <UIPickerViewDelegate>

@property NSArray *colors;

@property (weak)id <ContainerViewControllerDelegate> delegate;

@property (weak, nonatomic) IBOutlet UIPickerView *myPickerView;

- (IBAction)chosenCol:(id)sender;

@end

@protocol ContainerViewControllerDelegate <NSObject>

- (void) passValue:(NSString *) theValue;

@end

m file for the container view:

#import "ContainerViewController.h"

@interface ContainerViewController ()

@property NSString *selValue;

@end

@implementation ContainerViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colors = [[NSArray alloc] initWithObjects:@"blue", @"red", @"green", @"purple", @"black", @"white", @"orange", @"yellow", @"pink", @"violet", nil];

    }

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {

        return 10;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {

    return self.colors[row];

}

- (IBAction)chosenCol:(id)sender {

    [self.delegate passValue:[self.colors objectAtIndex:[self.myPickerView selectedRowInComponent:0]]];

}
@end

enter image description here

Here is a picture of what it looks like. Note that the "chosen" button is just a provisional one that I put there to make sure everything is hooked alright and that I can log out the chosen color using a button in the container. What I want to do is be able to pass that color to the parent view controller. So that after I dismiss the container with its picker view I have the data stored of what color was chosen. I have had help from someone in S.O. and he's done a very good job of helping me start this up. The only thing I didn't understand is what happens when I receive the data, at the end of the m file of the parent:

  - (void) passValue:(NSString *) theValue
    {
      // here is where you receive the data
    }

This is obvioulsy noob question but I really do need it spelt out. How do I actually access the data in the parent. I asked in the comments section, and the reply was (I am changing the class, it was originally uicolor):

"No, you'll receive the data inside the method - (void) passValue:(NSString *) theValue; Put a breakpoint in that method to be sure that it's working, you can access it like this: NSString *myReceivedColor = theValue;

I tried to write word for word "NSString *myReceivedColor = theValue;" but "theValue" is unrecognised.

Ultimately, what I want, is to pass the data back to the parent so that when I hit the button "back", in the parent, the label "you chose" is updated with the chosen color".

I have never touched delegation before so I am lost. Can a charitable soul take the time to explain this last bit in very obvious terms? many thanks

UPDATE-----------------------------------------------------------------------

So, what I am looking at, is to add, at the end of my method for the "back" button,

 - (IBAction)back:(id)sender {

        [self.childView willMoveToParentViewController:nil];

        [UIView animateWithDuration:1
                              delay:0.0
             usingSpringWithDamping:1
              initialSpringVelocity:1
                            options:UIViewAnimationOptionCurveEaseIn
                         animations:^{

                             self.childView.view.frame = CGRectMake(-320, 84, 320, 210);

                         } completion:^(BOOL complete){

                             [self.childView removeFromParentViewController];
                         }];

the couple of lines:

        self.myReceivedValue = theValue;

        self.myLabel.text = self.myReceivedValue;
}

To be able to update the text of myLabel to the the color I've chosen in the view container. It comes back with the error: "use of undeclared identifier "theValue". This is all new to me so I am just copying what people have said on S.O. with the hope of understanding eventually. What am I doing wrong here? tx


Solution

  • It looks like your delegate is nil.

    self.childView = [self.storyboard instantiateViewControllerWithIdentifier:@"childVC"];
    self.controller = [self.storyboard instantiateViewControllerWithIdentifier:@"childVC"];
    self.controller.delegate = self;
    

    You create two instances of "childVC" (a copy/paste typo maybe?) then set the delegate on 'controller' but you use 'childView'. Just change the childView property to be a ContainerViewController and set self.childView.delegate=self.

    (BTW its an easy mistake, so many times when you're thinking "why isn't this working??" check that the delegate property is set)

    EDIT

    The return value property you're logging is nil b/c you never set it. You have to implement the delegate method, i.e.

    -(void) passValue:(nsstring*)theValue
    {
    self.receivedValue = theValue
    }
    

    Also what i was saying about the chosenCol action is that is where you are calling your delegate - your 'back' action does not call this method.