iOS: UIView subclass init or initWithFrame:? - ios

I made a subclass of UIView that has a fixed frame. So, can I just override init instead of initWithFrame:? E.g.:
- (id)init {
if ((self = [super initWithFrame:[[UIScreen mainScreen] bounds]])) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
The Xcode documentation for -initWithFrame: says: "If you create a view object programmatically, this method is the designated initializer for the UIView class. Subclasses can override this method to perform any custom initialization but must call super at the beginning of their implementation."
What does "designated initializer" mean?

The designated initializer is the one that all the other initializers must call. UIView and subclasses are a little unusual in that they've actually got two such initializers: -initWithFrame: and -initWithCoder:, depending on how the view is created. You should override -initWithFrame: if you're instantiating the view in code, and -initWithCoder: if you're loading it from a nib. Or, you could put your code in third method and override both those initializers such that they call your third method. In fact, that's often the recommended strategy.
So, for example, you might create a UIView subclass, ClueCharacter, that has its own initialization method: -initWithPerson:place:thing:. You then create your view like this:
Obj-C:
ClueCharacter *mustard = [[ClueCharacter alloc] initWithPerson:#"Col. Mustard"
place:kInTheStudy
thing:kTheRope];
Swift:
var mustard = ClueCharacter("Col. Mustard", place: kInTheStudy, thing: kTheRope)
That's fine, but in order to initialize the UIView part of the object, your method must call the designated initializer:
Obj-C:
-(id)initWithPerson:(NSString*)name place:(CluePlace)place thing:(ClueWeapon)thing
{
if ((self = [super initWithFrame:CGRectMake(0, 0, 150, 200)])) {
// your init stuff here
}
}
Swift:
func init(name: String, place : CluePlace, thing : ClueWeapon)
{
if (self = super.init(CGRectMake(0, 0, 150, 200))) {
// your init stuff here
}
}
If you want to call your subclass's initializer -init, that's okay as long as you call -initWithFrame: in the implementation.

in UIView calling [super init] is exactly equal to [super initWithFrame:CGRectZero]

In an Objective-C class with multiple initialisers, the designated initialiser is the one that does the meaningful work. So, often you have a class with a few initialisers, say:
- (id)initWithRect:(CGRect)someRect;
- (id)initWithRect:(CGRect)someRect setDefaultColour:(BOOL)setDefaultColour;
- (id)initWithRect:(CGRect)someRect setDefaultColour:(BOOL)setDefaultColour
linkTo:(id)someOtherObject;
In that case you'd normally (but not always) say that the third was the designated initialiser, and implement the other two as e.g.
- (id)initWithRect:(CGRect)someRect
{
return [self initWithRect:someRect setDefaultColour:NO];
}
- (id)initWithRect:(CGRect)someRect setDefaultColour:(BOOL)setDefaultColour
{
return [self initWithRect:someRect setDefaultColour:setDefaultColour
linkTo:nil];
}
If a class has only one initialiser then that's the designated initialiser.
In your case to follow best practice you should implement initWithFrame: and also a vanilla init: that calls initWithFrame: with your normal dimensions. The normal convention is that you can add new variations on init in subclasses, but shouldn't take any away, and that you always do the actual initialising work in the designated initialiser. That allows any initialising methods from the parent class that you don't provide new implementations of still to work appropriately with your subclass.

Related

How do I override init - message without creating unwanted recursion in Objective C?

I want to subclass UIBarButtonItem with an initializer that automatically sets the image, target and selector.
I tried to override the - (id) init message like this:
- (id) init {
self = [super initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:#selector(myMessage)];
if (self) {
//Custom initialization
}
return self;
}
This results in an unwanted recursion, because initWithBarButtonSystemItem:target:action calls init.
I read the answer to the following question, but it didn't help me as it still gives me the same recursion:
Custom init method in Objective-C, how to avoid recursion?
The code I used when testing this was the following:
- (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(id)target action:(SEL)action {
return [super initWithBarButtonSystemItem:systemItem target:target action:action];
}
- (id) init {
self = [self initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:#selector(myMessage)];
if (self) {
//Custom initialization
}
return self;
}
I have the same requirements as the author of the mentioned question:
I don't want to create a custom init like - (id) initCustom.
I don't want to use a flag to determine if the init has already been run once.
I also want to mention that I tried to create the UIBarButtonItem without using the initWithBarButtonSystemItem:target:action initializer - but I couldn't find a #property corresponding to the first parameter.
Thank you!
As mentioned in the link you have provided (see the accepted answer), if you initialise with super call, it will not result in recursive calls.
Probably you should have something like this:
- (id) init {
self = [super initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:#selector(myMessage)];
if (self) {
//Custom initialization
}
return self;
}
You may find this helpful: Concepts in Objective-C: Object Initialization
In particular, pay attention to the concept of the designated initializer:
The designated initializer plays an important role for a class. It ensures that inherited instance variables are initialized by invoking the designated initializer of the superclass. It is typically the init... method that has the most parameters and that does most of the initialization work, and it is the initializer that secondary initializers of the class invoke with messages to self.
When you define a subclass, you must be able to identify the designated initializer of the superclass and invoke it in your subclass’s designated initializer through a message to super. You must also make sure that inherited initializers are covered in some way. And you may provide as many convenience initializers as you deem necessary. When designing the initializers of your class, keep in mind that designated initializers are chained to each other through messages to super; whereas other initializers are chained to the designated initializer of their class through messages to self.
This is why you are seeing this behavior. initWithBarButtonSystemItem:target:action is the designated initializer for that class, and it in turn calls init. Overriding init to call a designated initializer won't for the very reasons you are describing.
Instead, you can create a convenience constructor that calls the designated initializer:
+ (instancetype) bookmarksBarButtonItemWithAction:(SEL)action {
return [[[self class] alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:action];
}
Which would achieve pretty much what you are looking for, and does not even require that you subclass UIBarButtonItem. It can be done as a category.

Calling init Method in Objective C

I'm just getting to the part of my Obj-C book where init methods are covered. I understand them, but I'm not sure where to call the method. I have a VC class that needs to have one of its #propertys set to another value, besides the default 0 before the rest of the code is executed. VC is a subclass of UIViewController.
Here is my code for the init method in VC.m:
- (id)initmethod
{
if((self = [super init])){
self.value = -1 ;
}
return self ;
}
Where do I call this? Do I need to have a sub class that calls it? Or do I need to manually create the VC object that is linked to the view and call this method there?
Yes you need to call this when you create the controller,
VC *vcInstance = [[VC alloc] initMethod];
There's really no need to create your own init method though, you can just override init.
If you're making your controller in a storyboard however, you would want to override initWithCoder, and set your value property there.
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
_value = -1; // you should use the ivar rather than self.propertyName inside an init method
}
return self;
}
Likewise, if the controller is made in a xib file you should override initWithNibName:bundle:

self init.. vs. super init... in subclasses

I have a UIViewContoller subclass BaseViewControler. It has a method named -initWithStyle:. If I subclass that class as SubBaseViewContoller,
what should -[SubBaseViewController init] look like?
My code:
- (id)init
{
self = [self initWithStyle:kGreenStyle];
if (self) {
// stuff
}
return self;
}
In SubBaseViewController I don't have initWithStyle:
and my app crashed randomly with the -init above, I checked other view controllers which are subclasses of BaseViewController, and they use self = [super initWithStyle:kGreenStyle], and work. What's the explanation?
A UIViewController has two designated initializers:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle;
and
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
The second form will be invoked when Interface Builder is used to define a View Controller with a resource, that is nib files ore segues. The first form can be used when you manually create a view controller.
You can override these initializers as follows:
// Designated initializer
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
; // your initialization
}
return self;
}
// Designated initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
; // your initialization
}
return self;
}
Note: The method init will not be called when created from a resource. When you invoke init yourself, it will eventually call initWithNibName:bundle: with nil arguments.
See also: Reference: Multiple Initializers and the Designated Initializer
Another possible solution for your problem:
One approach to provide a method to initialize a custom View Controller is to use a "convenience class method". In this case, you don't need to override the designated initializers, instead you provide properties in your subclass.
The convenience class method may then look like as shown below:
// MyViewController
+ (instancetype) myViewControllerWithStyle:(Style)style
{
// Possibly get the bundle and the nib name (or leave it nil)
...
// Invoke a designated initializer with suitable parameters:
MyViewController* viewController = [[MyViewController alloc] initWithName:nibName
bundle:bundle];
viewController.style = style;
return viewController;
}
You should use super instead of self:
- (id)init
{
self = [super initWithStyle:kGreenStyle];
if (self) {
}
return self;
}
If you do it that way you force just parent class to do all the initialisation which it supposed to do. And if you want to do something in init method just in your subclass you add it here:
- (id)init
{
self = [super initWithStyle:kGreenStyle];
if (self) {
// do some stuff you need to do in subclass initialisation
// for example init variable specific just for that class (not parent class)
}
return self;
}
This is kind of a synthesis of Greg's and CouchDeveloper's answers.
But really, it's a defense of Greg's answer.
First, yes, understand designated initializers. They are important, and CouchDeveloper
is right to emphasize this.
Second, nothing prevents a subclass of UIViewController from having
completely different designated initializers (preferably one) of your own choosing.
If you are creating UIViewControllers outside of Interface Builder,
as it appears the OP may be doing, the primacy of -initWithNibName:Bundle:
and -initWithCoder: becomes silly.
Apple say this:
When you define a subclass, you must be able to identify the designated initializer of the superclass and invoke it in your subclass’s designated initializer through a message to super. You must also make sure that inherited initializers are covered in some way.
Given that, here's what the OP's BaseViewController might look like:
const NSUInteger kDefaultStyle = 0; // just to have something to use
#implementation BaseViewContoller
// No longer valid for *this* class.
// Should have one for -initWithCoder: too, but elided for this example.
- (instancetype)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
// New designated initializer for *this* class.
// This should call the superclass's designated initializer.
- (instancetype)initWithStyle:(NSUInteger)style
{
// use the designated initializer of the superclass to init
if ((self = [super initWithNibName:nil bundle:nil])) {
// stuff
}
return self;
}
// in *other* initializers, you *should* call *your* class's
// designated initializer.
- (instancetype)init
{
return [self initWithStyle:kDefaultStyle];
}
#end
Given this, I think CouchDeveloper's "this is wrong" calling out in the comments
of Greg's answer is itself wrong. This is because:
BaseViewController is unseen, so we can't possibly know what its designated initializer is, regardless of its superclass.
OP has given every indication that -[BaseViewController initWithStyle:] is the designated initializer
OP is perfectly free to declare [SubBaseViewController init] as the designated initializer, so long as he follows the rules.
Finally, unrequested advice for the OP:
Assuming those aren't typos in your original post, you should always capitalize the
first letter of class names. BaseViewController, not baseViewController.
You can swim against the idiom is when you
become a total badass; by that point, I'll wager you'll not want to.

Is there a init method in iOS that is always called?

Is there a method that is always called in Cocoa? Many classes have init or initWith, but even worse they can be loaded from a nib or something. I don't want to have to scrape around and find how it does this in this case. I just want to set some initial variables and other things, and I want a method to subclass that I can depend on no matter if it's a UIView, UIViewController or UITableViewCell etc.
No there is not such a method. init comes from NSObject so every object can use it, and as well subclasses define their own initialization methods. UIView, for example, defines initWithFrame: and furthermore there are init methods from protocols, such as NSCoding which defines initWithCoder:. This is the dynamic nature of objective-C, anything can be extended at any time. That being said, there are some patterns. UIViewController almost always takes initWithNibName:bundle: and UIView almost always takes initWithFrame: or initWithCoder:. What I do is make an internal initialize method, and just have the other inits call it.
- (void)initialize
{
//Do stuff
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initialize];
}
}
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if(self)
{
[self initialize];
}
}
Not 100% sure that it is always called, but I am pretty sure that this is a viable option. To be perfectly honest, I can't recall that I have ever seen this method used in practice and I usually shy away from using this method (I have absolutely no idea why, probably because it's just not the cleanest and most comprehensive method to achieve this...):
-didMoveToSuperview()
From documentation:
Tells the view that its superview changed.
The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.
There's many ways you can write a custom initializer.
- (id)initWithString:(NSString *)string {
if((self == [super init])) {
self.string = string;
}
return self;
}
That's just how I write my initializers in general. For example, the one above takes a string. (you don't have to pass strings if you don't want).
Btw, init is a method. According to the header for NSObject, init has a method implementation.

Infinite loop when using loadView

Very interesting problem when using loadView in UIViewController.
Usually we used like this way
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
NSLog(#"loadview");
[super loadView];
}
If remove
[super loadView];
We will get dead loop with this
- (void)loadView {
NSLog(#"loadview");
}
Why ?
Only one way to make infinite loop in this case - is getting view property until its not set. If you write next (for example):
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:self.view.bounds];
}
You'll got infinite loop, but
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectZero];
}
works OK.
So you can't to access view property until you didn't set it.
Since you are just INHERITING what's being implemented in super class(UIViewController), if you don't call super methods, then implementation that needs to be done is NOT done.
Almost all super methods do something critical and the local class inheriting super class implementations must either override them all together (unless you know everything about what super does by referring to the documentation, it's never a good idea), or just ADD local class implementation to the inherited super class implementations.
In conclusion, whenever you inherit a class, which is in most cases of software development, you should let the super class do its implementations unless it's safe to override them.
If I am correct, it seems like super loadView implements something very critical to avoid the loop.
ADDITIONAL NOTE:
However, based on the documentation, you should not call super method: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Probably, the reason for infinite loop is caused by not implementing view property appropriately.
If you override loadView, you are expected to provide a root view for the controller's view hierarchy. If you don't provide it loadView would get called every time the view is referenced, possibly leading to an infinite loop. From the docs:
If you specify the views manually, you must implement the loadView method and use it to assign a root view object to the view property.
Implementations that would cause an infinite loop:
- (void)loadView {
NSLog(#"loadview");
}
...self.view is nil after loadView
- (void)loadView {
self.view; // Or anything that references self.view
}
...referencing self.view causes loadView to be invoked, hence an infinite loop.
Correct:
- (void)loadView {
self.view = [[UIView alloc] init];
if (self.view == nil) {
[super loadView]; // Try to load from NIB and fail properly, also avoiding inf. loop.
}
}

Resources