On iOS, why the init in ViewController didn't work? - ios

In the ViewController's interface, I have
#property int count;
and in the implementation, I have
#synthesize count;
-(id) init {
self = [super init];
if (self) {
self.count = 100;
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%i", self.count++);
}
but for some reason, the first time self.count got printed, it is 0 but not 100?

One of various -init methods will be called on your UIViewController, depending on whether it came out of a .xib, storyboard, or is alloc'd manually somewhere else in your code.
A better place to put this kind of initialization is in -viewDidLoad, something like this
- (void)viewDidLoad {
[super viewDidLoad];
self.count = 100;
}

Put a NSLog or debugging breakpoint in your init method and I suspect you'll find it isn't called. If you look at UIViewController, you'll see other initialization methods (e.g. if you're using a NIB, it would invoke initWithNibName:bundle:). If it's via a storyboard, it can differ. See the discussion of initialization in Apple's View Controller Programming Guide for iOS.
A better place for general view controller configuration is viewDidLoad.

Change it to:
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.count = 100;
}
return self;
}
The view is actually getting created by the XIB, which is 'decoding' it or unboxing it. When this happens, the XIB calls initWithCoder:

Related

UIView - custom view - Where does the view's subviews is initialized?

I'm create a custom UIView with some sub views.
In the IB - i'v set my subview to be a CreateAlbumView which is a UIView, and created some subviews and outlet those.
When i'm inflating this view in code, and adding it to a super view the follow code is being called:
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self)
[self setViews];
return self;
}
but the subviews are all nil there - making my setViews() function to become useless.
I've changed my code to set the code in the following function, but it doesn't seems right:
-(void)layoutSubviews {
[super layoutSubviews];
[self setViews];
}
Where does the subviews are actually initialized and where can i start using them ?
You should use
- (void)awakeFromNib
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've changed my code to set the code in layoutSubviews, but it
doesn't seems right.
— well, have a method create the views (setViews is a bad name due to cocoa conventions) and layout them and add them in layoutSubviews, than layoutSubviews does exactly what it's name suggests.
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
[self createSubviews];
return self;
}
-(void)layoutSubviews
{
[super layoutSubviews];
for (UIView *v in mySubviews) {
v.frame = CGRectMake(...);
[self addSubview:v];
}
}

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.

Custom UIView with custom initialization doesn't work

I'm developing an iOS app with latest SDK.
I have created a class that inherits from UIView and I have to do some initialization every time the class is instantiated.
I have to call a method called setUpVars: but I don't know where to send a message to that method:
- (id)initWithFrame:(CGRect)frame;
- (id)initWithCoder:(NSCoder*)aDecoder;
This class can be used with a custom xib, or added to a Storyboard, so I need to be sure that that method will be called on every case.
- (void)setUpVars
{
_preferenceKey = #"";
_preferenceStatus = NO;
_isDown = NO;
}
Where do I have to add [self setUpVars];?
Essentially you will be wanting to cover both cases
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self setUpVars];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setUpVars];
}
return self;
}
I think that you need to send this message from each method, also do not forget about awakeFromNib method.
You can create BOOL variable, something like isAlreadySetup and set it to YES in setUpVars method.
Docs Says
awakeFromNib
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
- (void)awakeFromNib
{
[self setUpVars];
}
If you use Interface Builder to design your interface, initWithFrame: is not called when your view objects are subsequently loaded from the nib file. Instead initWithCoder gets called. So you can initialize your variables in both methods if you prefer a generic way. Works in both case
I tend to think you should call this method from the -(void)viewDidLoad method of the controller in charge

CALayer delegation causes zombie crash -- why?

I am new to Core Animation and having trouble implementing a CALayer object with the drawLayer method in a delegate.
I have narrowed the problem down to a very simple test. I have a main viewController named LBViewController that pushes a secondary viewController called Level2ViewController. In the level 2 controller, in viewWillAppear:, I create a CALayer object with it's delegate=self (i.e. the level 2 controller). Whether or not I actually implement the drawLayer:inContext: method I have the same problem -- when I return to the main viewController I get a zombie crash. In the profiler it appears that the object in trouble is the level 2 viewController object -- which is being dealloc'ed after it's popped.
I've tried using a subclassed CALayer object instead of the delegate and it works fine. If I comment out the delegate assignment it also runs fine. I would like to understand why delegation is causing this problem. Any advice is greatly appreciated.
Here's my code ---
Level2ViewController
#implementation Level2ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CALayer *box1 = [[CALayer alloc] init];
box1.delegate = self; // problem disappears if I comment out this assignment
box1.backgroundColor = [UIColor redColor].CGColor;
box1.frame = CGRectMake(10,10,200,300);
[self.view.layer addSublayer:box1];
[box1 setNeedsDisplay];
}
// makes no difference whether or not this method is defined as long
// as box1.delegate == self
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(theContext, 3);
CGContextAddRect(theContext, CGRectMake(5, 5, 40, 40));
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The method in LBViewController (the main controller) that pushes the level 2 view controller
- (IBAction)testAction:(id)sender {
Level2ViewController *controller = [[Level2ViewController alloc]
initWithNibName:#"Level2ViewController" bundle:nil];
controller.title = #"Level2";
// this push statement is where the profiler tells me the messaged zombie has been malloc'ed
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
You may want to set the layer's delegate to nil before the delegate object is released. So in your Leve2ViewController do this:
-(void)viewWillDisappear:(BOOL)animated
{
if (box1) {
box1.delegate = nil;
}
box1 = nil;
}
Obviously this requires, that box1 is turned into a field (so it is accessible in viewWillDisappear:)
Since you create box1in viewWillAppear: the code above uses viewWillDisappear:. Recently, when I ran into a similar problem, I had a separate delegate object in which I used init and dealloc.
Note: You call [super viewDidAppear:animated]; in viewWillAppear. Looks like a typo or copy/paste glitch :-)

Resources