In which method should I set the delegate of a UITextField? - objective-c

Is it good practice to be setting the delegate of a UITextField in viewDidLoad or in an init method?
I tried setting the delegate as self in an init method, but it wasn't calling the corresponding delegate methods, when I moved the code into viewDidLoad, it registered as setting self as the delegate?
It seems that I should be able to set it in either method, if someone can help clear this up for me it would be much appreciated.
-(id) init {
self = [super init];
if (self)
textField.delegate = self; //this text field is an IBOutlet
//some other code here as well
return self;
}
OR
-(void)viewDidLoad {
[super viewDidLoad];
textField.delegate = self;
}

If your text field is an IBOutlet then until viewDidLoad method is called your text field will be nil (hence you set delegate to nil object). When viewDidLoad gets called it literally means that view was loaded and all IBOutlets and IBActions were connected and are at your disposal.

Assuming that your class is a UIViewController and is loaded from a NIB (since you have an IBOutlet), the proper init method to override is initWithCoder:(NSCoder*)decoder. What's happening right now:
iOS loads your NIB file and creates your UIViewController
The UIViewController is created by calling its initWithCoder:(NSCoder*)decoder method.
The first thing that initWithCoder does is to call init and therefore your code, before it has even decoded the NIB.
Because it hasn't decoded the NIB yet, your textField IBOutlet hasn't been set yet (if you debug it you should be able to see that its value is nil inside your init). And therefore setting the delegate doesn't do anything.
The easiest way to proceed is indeed to set your delegate in the viewDidLoad method; it can't be done in init. However it can be done by overriding initWithCoder:
- (id)initWithCoder:(NSCoder*)decoder {
self = [super initWithCoder:decoder];
if (self)
textField.delegate = self;
return self;
}
This time you wait until UIViewController's implementation of initWithCoder has finished decoding the NIB and so all your outlets have been set.

Related

Setting up buttons in interface builder but want to call custom init method

I have a custom UIButton class. I have set up the buttons in the interface builder. I have this code:
#import "FriendButton.h"
#implementation FriendButton
-(id)init {
self = [super init];
NSLog(#"init called");
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(longPress:)];
[self addGestureRecognizer:longPress];
return self;
}
It isn't being called though, I have set the class type to FriendButton in the interface builder. Is there a different method I need to use? I know that for custom UITableViewCells one has to use awakeFromNib.
From Apple docs:
Objects that conform to the NSCoding protocol (including all
subclasses of UIView and UIViewController) are initialized using their
initWithCoder: method.

IBOutlet is nil after initWithCoder is called

Simple problem, I have defined a UIImageView, called bigImageView in a UIViewController using the storyboard,
It's declared in the h file of that UIViewController as follows:
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
on my appDelegate I init the UIViewController as follows:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
this calls initWithCoder on my UIViewController m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Custom initialization
}
return self;
}
This function is only called once so there's no double init.
However, later, when I check my bigImageView pointer, it's still nil.
Isn't the init supposed to allocate memory to it?
I think that's why when I try to set this UIImageview to hold a UIImage it doesn't display the image
Thanks
It's all working how it's meant to. First every object in the nib/storyboard gets alloc/init called on them, then all the connections are made, and then viewDidLoad is called.
You need to wait for - (void)viewDidLoad to be called on your controller, and then bigImageView should be set. If it's not set then you did something wrong in the storyboard.
init methods are not responsible for allocating any memory. All memory is allocated by the alloc method which is always called before init. Alloc will fill all your instance variables with nil/NULL/0 values, and then init gives the chance to assign initial values to each one (based on the contents of the NSCoder object usually, but it's up to you to decide what should be done).
For IB outlets however, those are setup by the nib loading process after init.
EDIT:
// ViewControllerA.m:
imageViewController = [storyboard instantiateViewControllerWithIdentifier:#"chosenImageController"];
imageViewController.image = imageToShow;
// ViewControllerB.h
#property (retain) NSImage *image;
#property (retain, nonatomic) IBOutlet UIImageView *bigImageView;
// ViewControllerB.m
- (void)viewDidLoad
{
self.bigImageView.image = self.image;
[super viewDidLoad];
}
You don't need to define initWithCoder, since you have no custom logic in there. I would delete that boilerplate code.
Here is what I would check:
In the storyboard, ensure that the class of the view controller is set properly.
Ensure that the outlet is hooked up properly in the storyboard by looking for a circle near your #property. It should be a filled in circle, not an outline of a circle.
Make sure you are reading the value only after viewDidLoad is called. Apple's only guarantee is that the outlet is set after this method call.
Update: It sounds like you want to access the image view before the view is loaded. There is no way to do this. One hack is to call viewController.view which will force the view to load, but there are many reasons why you should not do this.
A better approach would be to implement properties on your view controller which work for both when the view is not loaded and when the view is loaded. You can see an example of an elegant solution in this question. Notice how if the view is loaded, the photographerLabel will get set via the didSet method. On the other hand, if the view is not loaded, it will get set via the viewDidLoad method. For an Objective-C version of that code or for more details, see the linked video in that question.

How to Subclass UICollectionViewFlowLayout with Storyboard

I'm using a UICollectionView with Storyboard and trying to subclass the UICollectionViewFlowLayout but it doesn't seem to work.
I've created the subclass CollectionViewFlowLayout :
#import "CollectionViewFlowLayout.h"
#implementation CollectionViewFlowLayout
-(id)init
{
NSLog(#"Init of CollectionViewFlowLayout");
if (!(self = [super init])) return nil;
self.itemSize = CGSizeMake(250, 250);
return self;
}
#end
And in the Storyboard's Identity Inspector I changed the class for the flow layout:
But when I save/build/run, the itemSize is not set at 250 and my NSLog isn't being output.
I've seen in examples such as this that you can set the layout in the collectionView controller, but I sort of assumed that wasn't necessary if you set it in the storyboard.
Objects loaded from the storyboard use initWithCoder:, not init. Move your setup code there instead, or have a common method that is called from each initialiser.

Pass Variables to Parent View?

I have a UITableViewController which pushes a UIViewController and I need to pass two NSDates and a BOOL from that child view controller back to the table view controller.
How could I do this? Let me know if I am not being clear or you need more explanation.
I'd appreciate any help, thanks.
Using a delegate is the proper way of passing info from one controller to another.
Generally, what you want to do is to declare a protocol in your UITableViewController header, implement it, and then init the UIViewController with a delegate.
In a nutshell, your UITableViewController .h file should have something like this:
#protocol setDateDelegate <NSObject>
#required
- (void) setDateOne:(NSDate *)one dateTwo:(NSDate *)two;
#end
Then, you ought to implement the above mentioned class doing whatever you want to do with the dates in your UITableViewController .m file.
In your UIViewController, you could (alternative at the end) define a custom init method:
First, define a delegate property in your .h, and a custom init method, then implement it in your .m with something like this:
- (id)initWithDelegate:(NSObject *)myDelegate
{
self = [super init];
if (self) {
self.delegate = myDelegate;
}
return self;
}
Next, when you need to pass the dates to your UITableViewController, just use
[self.delegate setDateOne:one dateTwo:two];
There are more ways of doing this, including skipping the custom init method and just setting the delegate after you create a UIViewController:
MyUIViewController *viewController = [[[MyUIViewController alloc] init] autorelease];
viewController.delegate = self;
...
It's more of a personal preference style.
I've probably messed up the code (and forgotten a few bits) because I'm writing this off the top of my head, but it should be good enough to get you started.

Can I use segues with designated initializers of view controllers?

I am new to storyboards and I have set up a segue from a button to a view controller. This view controller is of a custom subclass SFListViewController, which has a designated initializer initWithList:.
Using the designated initializer is the only way to correctly initialize the view controller. However, when using segues the designated initializer won't be called (obviously).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Show List"]) {
SFListViewController *listViewController = segue.destinationViewController;
// ????
}
}
How can I make the segue call the designated initializer when performed?
As far as I know this is not possible since the storyboard framework simply calls alloc and init on the class you define in Interfacebuilder. Additionally the segue's destinationViewController attribute is read-only so you couldn't simply replace the existing ViewController either.
The only way to use Storyboarding would probably be to create a wrapper-class that internally instantiates the SFListViewController with the desired attributes and then functions as a proxy object and thus propagates viewDid*** and viewWill***-methods to the wrapped class and also returning the wrapped VC's view in a readonly view property... You get the idea.
Generally there are a number of alternative ways to initialize a UIViewController in such a case:
There is an option to specify "User defined runtime Attributes" which could be used for initialisation.
Override the prepareForSegue: method, like you tried, in your root ViewController and do "post-alloc-init-initialisation" there.
If worst comes worst, you could fall back to an IBAction in order to be able to initialize the ViewController yourself.
I hope this helps.
Edit: I can verify that the Proxy-Approach works since I just came across a similar problem with ABPeoplePickerNavigationController where this approach worked nicely. Since we've set up the thing in our story board please note that you have to use awakeFromNib in order to do initial configuration (instead of some init method).
This is the code for my wrapper class:
#import "PeoplePickerViewControllerWrapper.h"
#implementation PeoplePickerViewControllerWrapper
#synthesize ppvc = _ppvc; // This is the object I'm proxying (The proxyee so to speak)
#synthesize delegate = _delegate;
- (void)awakeFromNib
{
self.ppvc = [[ABPeoplePickerNavigationController alloc] init ];
self.ppvc.peoplePickerDelegate = self;
self.ppvc.addressBook = ABAddressBookCreate();
self.ppvc.displayedProperties = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonPhoneProperty]];
}
#pragma mark - View lifecycle
- (void)loadView
{
[super loadView];
[self.ppvc loadView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.ppvc viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.ppvc viewWillAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.ppvc viewDidDisappear:animated];
}
-(UIView *)view{
return self.ppvc.view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
[self.ppvc viewDidUnload];
}