Understanding delegation - objective-c

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
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

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.

Related

TextFields and text areas become unresponsive after performing validation in the textfield

I am creating a view controller in which UITextField are getting created dynamically.
self.ansText = [[UITextField alloc] init];
self.ansText.tag = 1;
self.ansText.delegate = self;
[_ansText createAnsTextwithParentFrame:QuestionView.frame withUpperFrame:questSeq2Frame];
[QuestionView addSubview:_ansText];
Here when I do a validation on the UITextField, text fields even in other view controllers become unresponsive.Here it is reaching UITextField ShouldBeginEditing method.I have gone through a number of similar question in stack overflow.And I saw a solution like setting the delegate not to self. But Could anyone please help me on this..Could you please elaborate on this. Thank you very much in advance.
#iOSManiac, delegate can be the UITextField, it's just
self.ansText.delegate = ansText;
Here's a more detailed answer, your And Text must be inherited from UITextField and it must implement UITextFieldDelegate
#interface ViewController ()
#end
#interface AnsText : UITextField <UITextFieldDelegate>
#end
#implementation AnsText
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
NSLog(#"textFieldShouldBeginEditing");
return YES;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AnsText *ansText = [[AnsText alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
ansText.tag = 1;
ansText.delegate = ansText;
[self.view addSubview:ansText];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end

UIViewController Retaining in ARC

I have a subclass of UIViewController -> MyPopUpViewController
#protocol MyPopUpViewController Delegate;
#interface MyPopUpViewController : UIViewController
{
}
#property (nonatomic, strong) id <MyPopUpViewControllerDelegate> delegate;
-(IBAction) buttonPressed:(id)sender;
#end
#protocol MyPopUpViewControllerDelegate
-(void) popupButtonPressed: (MyPopUpViewController*)controller;
#end
I cannot have this MyPopUpViewController as an instance variable because this comes externally, and there could be many and multiple of these popups can be up. So far I tried this, and it crashes on the delegate call due to not being retained:
MyMainViewController:
-(void)externalNotificationReceived: (NSString*) sentMessage
{
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[self.view addSubview:popupView.view];
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
}
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
NSLog(#"Popup Delegate Called");
[controller.view removeFromSuperview];
controller.delegate = nil;
controller = nil;
}
Once the popup comes up, and when the ok button is tapped, it crashes and never gets to that NSLog. How can I change
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
..so it would retain without making it an instance variable?
Thanks in advance.
You should be doing proper view controller containment by calling addChildViewController:.
- (void)externalNotificationReceived: (NSString*) sentMessage {
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
[self addChildViewController:popupView];
[self.view addSubview:popupView.view];
[popupView didMoveToParentViewController:self];
}
This will keep a proper reference to the view controller as well as properly pass various view controller events. Read about this in the docs for UIViewController and the "View Controller Programming Guide for iOS".
BTW - you should name your methods better. Example:
popupButtonPressed::
should be named:
popupButtonPressed:buttonNumber:
Usually delegates are weak-referenced instead of strong. I, myself, would name it something else as to not confuse other people.
Also, the following bit of code will have no effect:
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
...
controller = nil;
}
the controller would be released (set to nil) automatically at the end of the scope.

How can I redraw an image in an custom NSView for an NSStatusItem?

I am having some troubles understanding how to wire a custom NSView for an NSMenuItem to support both animation and dragging and dropping. I have the following subclass of NSView handling the bulk of the job. It draws my icon when the application launches correctly, but I have been unable to correctly setup the subview to change when I invoke the setIcon function from another caller. Is there some element of the design that I am missing?
TrayIconView.m
#import "TrayIconView.h"
#implementation TrayIconView
#synthesize statusItem;
static NSImageView *_imageView;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
statusItem = nil;
isMenuVisible = NO;
_imageView = [[NSImageView alloc] initWithFrame:[self bounds]];
[self addSubview:_imageView];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Draw status bar background, highlighted if menu is showing
[statusItem drawStatusBarBackgroundInRect:[self bounds]
withHighlight:isMenuVisible];
}
- (void)mouseDown:(NSEvent *)event {
[[self menu] setDelegate:self];
[statusItem popUpStatusItemMenu:[self menu]];
[self setNeedsDisplay:YES];
}
- (void)rightMouseDown:(NSEvent *)event {
// Treat right-click just like left-click
[self mouseDown:event];
}
- (void)menuWillOpen:(NSMenu *)menu {
isMenuVisible = YES;
[self setNeedsDisplay:YES];
}
- (void)menuDidClose:(NSMenu *)menu {
isMenuVisible = NO;
[menu setDelegate:nil];
[self setNeedsDisplay:YES];
}
- (void)setIcon:(NSImage *)icon {
[_imageView setImage:icon];
}
TrayIconView.h
#import <Cocoa/Cocoa.h>
#interface TrayIconView : NSView
{
BOOL isMenuVisible;
}
#property (retain, nonatomic) NSStatusItem *statusItem;
- (void)setIcon:(NSImage *)icon;
#end
The solution to this problem was actually outside of the view detailed here. The caller of the interface was being double instantiated on accident, thus nulling out the reference to the previously created NSView. After correcting that concern the app draws and works just fine.
With regard to dragging, I just implemented a subclass of NSView that implemented the Cocoa draggable protocol and added it as a subview to this parent class. That allows dragging onto the currently established NSRect that contains the menubar icon.

changing UILabel text on a subview from main view

Ok, so I'm a relative noob with Objective-C/iOS programming, so hopefully someone with more knowledge here can help me out.
I have an iPad application using the SplitViewController template (with Core Data). I created another UIViewController (with xib file) called PlayerViewController. This View has several UILabel components on it.
I have a list of players that show up in the RootViewController (UITableView) and when you select a player, I programmatically create a PlayerViewController (in DetailViewController), pass it the NSManagedObject that was passed to the DetailViewController, try to set the text of one of the labels on the PlayerViewController's view, and then add it as a subview to the DetailViewController.
All of this works great except for the setting the text of the label on the PlayerViewController's view. I'm not sure what I'm doing wrong. I have used NSLog to confirm that the NSManagedObject is not nil and that the NSManagedObject property I'm trying to use has the correct text.
I'm at a loss here. Any help would be greatly appreciated. (Code follows):
This method is in the DetailViewController.m file:
- (void)configureView {
// Update the user interface for the detail item.
PlayerViewController *player = [[PlayerViewController alloc] init];
player.player = detailItem;
[self.view addSubview:player.view];
}
This method is called when the user selects an item from the RootViewController (This functionality, calling of configureView, is setup by the template and I haven't changed it).
Setting the player property of the PlayerViewController to object detailItem is handled in the setPlayer method of that class.
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
I then have a configureView method as well in PlayerViewController that sets the text of the label:
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
Ok, so the first NSLog statement prints the desired value, but the text of the UILabel (called nickName) returns nil.
The following is the full PlayerViewController.h & .m files:
PlayerViewController.h:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface PlayerViewController : UIViewController {
NSManagedObject *player;
IBOutlet UILabel *nickName;
IBOutlet UILabel *goalCount;
IBOutlet UILabel *assistCount;
IBOutlet UILabel *timeInGame;
}
#property (nonatomic, retain) IBOutlet UILabel *nickName;
#property (nonatomic, retain) IBOutlet UILabel *goalCount;
#property (nonatomic, retain) IBOutlet UILabel *assistCount;
#property (nonatomic, retain) IBOutlet UILabel *timeInGame;
#property (nonatomic, retain) NSManagedObject *player;
#end
PlayerViewController.m:
#import "PlayerViewController.h"
#implementation PlayerViewController
#synthesize nickName, goalCount, assistCount, timeInGame, player;
#pragma mark -
#pragma mark Managing the detail item
/*
When setting the player item, update the view
*/
- (void)setPlayer:(NSManagedObject *)managedObject {
if (player != managedObject) {
[player release];
player = [managedObject retain];
// Update the view.
[self configureView];
}
}
- (void)configureView {
nickName.text = [[player valueForKey:#"Nickname"] description];
NSLog(#"Nickname %#", [[player valueForKey:#"Nickname"] description]);
NSLog(#"Nickname %#", nickName.text);
}
/*
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
}
return self;
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
I'm sure I'm just missing something trivial, but I can't figure it out, and haven't been able to find any answers searching the web.
Thanks for any help!
Ok, so after playing with this for a bit and searching and searching around, I have gotten the answer to my problem. It turns out all the code I had was fine except the location of one statement. My call to configureView in PlayerViewController.m needed to be in viewDidLoad() not in the setPlayer() method. It all works great now.
Change the configureView method to that :
- (void)configureView {
nickName.text = (NSString*)[player valueForKey:#"Nickname"];
}
Yes, better place to call method is
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self configureView];
}
(void)setPlayer:(NSManagedObject *)managedObject called before your nib files loaded.

Why isn't my UIButton responding to touches?

I'm sure I'm overlooking the obvious as I've got countless working buttons...but...for whatever reason this one is not cooperating...
I've added a UIButton (Rounded Rect) to a UIView subclass (DialogView) which is a subview of my view controller's view. This subview is created almost entirely in IB. I've wired up the button to (IBAction)okButtonPressed:(id)sender in IB to Touch Up Inside and created a corresponding method in DialogView. However when I "touch" this button it doesn't trigger the method. userInteractionEnabled is true for the VC's view, DialogView and the UIButton.
Thinking maybe initWithCoder had to do some frame manipulation or something I added the following which successfully logs to console.
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
In further exploration I wired up an IBOutlet to the button and then if I try to change the titleLabel from the view controller I notice that it get's severely truncated. Default text of say "Press Me!" set in IB displays fine when view is first drawn. But if I change the text...
self.DialogView.okButton.titleLabel.text = #"Not Working";
...it gets truncated to "N..."
Dunno if this is related. Probably...
Anyone see what I've screwed up here?
Edit (adding code related to showing UIButton):
From the View Controller:
self.DialogView = [[[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:self options:nil] objectAtIndex:0];;
self.DialogView.myVC = self;
self.DialogView.backgroundColor = [UIColor clearColor];
self.DialogView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
self.DialogView.nameLabel.text = loan.fullName;
self.DialogView.noteLabel.text = loan.summaryOfLoan;
self.DialogView.amountLabel.text = [currencyFormatter stringFromNumber:loan.originalAmount];
self.DialogView.alpha = 0.0;
[self.view addSubview:DialogView];
The UILabels all displaying as expected. As is the problem UIButton. I can see it I just can't interact with it!?!
DialogView's interface:
#class MyViewController;
#interface DialogView : UIView {
IBOutlet UILabel *nameLabel, *noteLabel, *amountLabel;
IBOutlet UIImageView *arrowView;
IBOutlet UIButton *okButton;
MyViewController *myVC;
}
#property (nonatomic, retain) UILabel *nameLabel, *noteLabel, *amountLabel;
#property (nonatomic, retain) UIImageView *arrowView;
#property (nonatomic, assign) MyViewController *myVC;
#property (nonatomic, retain) UIButton *okButton;
- (IBAction)okButtonPressed:(id)sender;
#end
And DialogView's implementation:
#import "DialogView.h"
#import "MyViewController.h"
#implementation DialogView
#synthesize nameLabel, noteLabel, amountLabel, arrowView, okButton;
#synthesize myVC;
- (void)dealloc {
[nameLabel release];
[noteLabel release];
[amountLabel release];
[arrowView release];
[okButton release];
[super dealloc];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
NSLog(#"DialogView initWithCoder called");
}
return self;
}
- (IBAction)okButtonPressed:(id)sender {
NSLog(#"pressed DialogView OK button");
[self.myVC.navigationController popViewControllerAnimated:YES];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
#end
I thought that we should use -setTitle:forState: in order to set button's title ?
An other thought, did you check that the button's frame is not CGRectZero ? And by the way, all the frames for the view in the hierarchy ? And check that one superview in the hierarchy is not user interaction disabled ?
And, I think imageView does not respond to touches, do you have one in your code ?
I was just having more or less the same problem and I found that my containing view did not have "User Interaction Enabled".
Hope this helps.
Do you maybe have two buttons on top of one another? Change the IB project window to the detail view and see if your view has more buttons than you are expecting. Maybe you've wired up a button that's not actually getting the press you're expecting.