Can a delegate variable be assigned to the calling object? - ios

Most of the time I am doing this:
self.someVC = [myVC alloc] initWithFrame:frame];
self.someVC.delegate = self;
Is there a way to set the delegate variable automatically?
-(void)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.delegate = [the object that is calling initWithFrame];
..

No, this isn't possible. You would have to inspect the call stack or something which I would not recommend. There are no standard facilities for this.
Even if it was possible, it would be a bad idea. You should use constructor or property injection as usual so the dependencies are clear and you don't confuse people that are reading your code.

Add a custom initializer that takes in the delegate object as a parameter.
Like this:
-(void)initWithFrame:(CGRect)frame delegate:(id)delegate
{
self = [super initWithFrame:frame];
if (self) {
self.delegate = delegate;
..

You could always provide a new initWithFrame method:
- (id)initWithFrame:(CGRect) aFrame delegate:(id)aDelegate {
self = [super initWithFrame:aFrame];
if (self) {
self.delegate = aDelegate;
}
return self;
}

Related

Why we are using the method -(id)init and what are the uses?

While referring a sample code i found this snippet can any explain why it is used.
- (id)init
{
self = [super init];
if (self) {
[[self view]setBackgroundColor:[UIColor redColor]];
}
return self;
}
and what is the difference between the following snippet.
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
}
init and viewDidLoad both are completely different.
viewDidLoad called, when the view is loaded into memory, this method called once during the life of the view controller object. It's a great place to do any view initialization.
init method is an initializer method. Cocoa has various types of intializer. To learn more, please check the link,
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/Initialization/Initialization.html

A few enter points in IOS

At first, sorry for my english. I trying to resolve problem with a few enter points (a few initializators like initWithFrame: and initWithCoder:). Not to repeat my setup code. At first i had a simple solution, just create method ("setup" for example) that called by initializators. But there is a little problem with subclasses. If my subclass have own initializator like initWithFrame:backgroundColor: and property "backgroundColor" then its own overriden "setup" will be called by super initializator but "backgroundColor" will still nil. So this "setup" will cant use this property. I think its common problem and its have nice common solution, that i cant find.Thanks.
Typically, I'll create static function called _commonInit(MyClass *self) and call that from each initializer. It is a function because it won't be inherited.
base class
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
-(instancetype)initWithCoder:(NSCoder*)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
-(void)setup
{
//some setup code
}
child class
-(instancetype)initWithFrame:(CGRect)frame param:(id)param
{
self = [super initWithFrame:frame];
if(self)
{
self.param = param;
//setup will be run by parent
}
return self;
}
-(void)setup
{
[super setup];
//child setup code
//when this code will work self.param will still nil!
}
thats what i mean

About using init methods

I made a test app to understand how exactly init methods work. In my simple UIViewController I call the following:
- (id)init {
self = [super init];
self.propertyArray = [NSArray new];
NSLog(#"init called");
return self;
}
The above does not print any values in NSLog. However, when I write :
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
NSLog(#"init called");
self.propertyArray = [NSArray new];
return self;
}
It does print "init called" in console. So my question is: why is the init method called and the other is not? Which one do I have to use, when i want to do my stuff before the view loads (and any other methods called)?
Any explanation will be appreciated, thanks.
To begin with, you mention ViewController in your question. A UIViewController's designated initializer is initWithNibName:bundle:
You would never want to override just init on a UIViewController.
There is a lifecycle for each object:
When initializing in code, you have the designated initializer. Which you can find in the documentation for that class. For NSObject derived classes this would be init:
- (id)init
{
self = [super init];
if (self) {
// perform initialization code here
}
return self;
}
All objects that are deserialized using NSKeyUnrchiving, which is what happens in the case of Storyboard's or NIBs(XIBs), get decoded. This process uses the initWithCoder initializer and happens during the unarchiving process:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// perform initialization code here
}
return self;
}
It is common, because of this lifecycle, to create a shared initializer that gets called from each initializer:
- (void)sharedInit
{
// do init stuff here
}
- (id)init
{
self = [super init];
if (self) {
[self sharedInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self sharedInit];
}
return self;
}
To take it one step further. In the case of Storyboard's and XIBs, if you want to perform initialization or updates AFTER the unarchiving is completed and to guarantee all your outlets and actions are connected, you would use awakeFromNib:
- (void)awakeFromNib
{
// do init or other stuff to be done after class is loaded from Interface Builder
}
When a class is instantiated in your code, you pick which initializer to call, depending on your needs. When a class is instantiated through framework code, you need to consult the documentation to find out what initializer would be called.
The reason that you see the behavior that you describe is that your view controller is in a storyboard. According to Cocoa documentation, when a view controller is instantiated through a storyboard, its initWithCoder: initializer is called. In general, this call is performed when an object gets deserialized.
Note that it is common to check the result of self = [super initWithCoder:aDecoder]; assignment, and skip further initialization when self is set to nil.
When you load view controller from nib file (and storyboard) it uses initWithCoder: so in your example this is why it call this method.
If you create your view controller programatically this method won't work and you should override initWithFrame: initialiser instead and also you should create view controller by calling
[[UIViewController alloc] initWithFrame:...];
The different inits are different constructors. As in any other language, an instance is instantiated by the most appropriate constructor. That's initWithCoder: when restoring from an archive.
As a style point, note that use of self.propertyArray in a constructor is considered bad form. Consider what would happen if a subclass overrode setPropertyArray:. You'd be making a method call to an incompletely instantiated object. Instead you should access the instance variable directly, and perform the idiomatic if(self) check to ensure it is safe to do so.

Function that is called when UILabel initializes

I am trying to subclass UILabel. The first try involves that my custom UILabel simply sets the property adjustsFontSizeToFitWidth to YES. The problem is that I am new to iOS programming and unsure about where to put this code. I tried the code below but they are never called.
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"init custom label");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
- (id)init
{
NSLog(#"init custom label");
self = [super init];
if (self) {
// Initialization code here.
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
I got it to work by using:
lblCustom = [lblCustom init];
But is there someway I can get this call to be called automatically?
When a label is used in interface builder is then the coded use the NSCoder protocol:
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSLog(#"init custom label");
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code here.
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
What I do, is create one method to setup my custom UI object and let all the init call this method.
I would personally stay away from initWithCoder: and instead use awakeFromNib instead. Here is why (Apple UIKit Documentation):
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
I have a variety of UI subclasses in my program and here is the solution I came up in my BaseLabel class.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
// We were not loaded from a NIB
[self labelDidLoad:NO];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
// We were loaded from a NIB
[self labelDidLoad:YES];
}
- (void)labelDidLoad:(BOOL)loadedFromNib
{
// Do some initialization here...
}
Now all of my subclasses simply override {type}didLoad:(BOOL)loadedFromNib.
For example buttonDidLoad:, textFieldDidLoad:, and tableViewDidLoad: (you get the idea).
Calling only init on an UIView is the same as calling initWithFrame: with a zero-rect. So you should override initWithFrame or initWithCoder if you are using nib-files.

getInstance before initWithNibName:

I have a class named IGMapViewController
In that I have
static IGMapViewController *instance =nil;
+(IGMapViewController *)getInstance {
#synchronized(self) {
if (instance==nil) {
instance= [IGMapViewController new];
}
}
return instance;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// more code
instance = self;
}
return self;
}
If use the object in more then 1 class but only use initWithNibName in one class.
In a class named IGRouteController in the init method i use _mapViewController = [IGMapViewController getInstance]; this happens before the initWithNibName gets executed in another class.
In IGRouteController I have a method updateRouteList in that method I use:
[_mapViewController drawSuggestedRoute:suggestedRoute];
It all does run but I can't see the result.
If i use:
IGMapViewController *wtf = [IGMapViewController getInstance];
[wtf drawSuggestedRoute:suggestedRoute];
Then it does work great.
So is it possible to get a instance and init later with a nib?
I believe I see what you are trying to accomplish. You want to initialize a singleton instance of your class from a nib. Correct?
When you initialize your instance, you are using [IGMapViewController new] which is presumably not the intended behavior. How about this (untested...)?
+ (id)sharedController
{
static dispatch_once_t pred;
static IGMapViewController *cSharedInstance = nil;
dispatch_once(&pred, ^{
cSharedInstance = [[self alloc] initWithNibName:#"YourNibName" bundle:nil];
});
return cSharedInstance;
}
clankill3r,
You should avoid creating singleton UIViewControllers (see comments in this discussion UIViewController as a singleton). This has been also highlighted by #CarlVeazey.
IMHO, you should create a UIViewController each time you need it. In this case your view controller would be a reusable component. When you create a new instance of your controller, just inject (though a property or in the initializer the data you are interested in, suggestedRoute in this case).
A simple example could be the following:
// YourViewController.h
- (id)initWithSuggestedRoute:(id)theSuggestedRoute;
// YourViewController.m
- (id)initWithSuggestedRoute:(id)theSuggestedRoute
{
self = [super initWithNibName:#"YourViewController" bundle:nil];
if (self) {
// set the internal suggested route, e.g.
_suggestedRoute = theSuggestedRoute; // without ARC enabled _suggestedRoute = [theSuggestedRoute retain];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self drawSuggestedRoute:[self suggestedRoute]];
}
For further info about UIViewControllers, I really advice to read two interesting post by #Ole Begemann.
Passing Data Between View Controllers
initWithNibName:bundle: Breaks Encapsulation
Hope that helps.

Resources