iOS: UIView subclass init or initWithFrame:?
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.
How do I write a custom init for a UIView subclass in Swift?
The init(frame:)
version is the default initializer. You must call it only after initializing your instance variables. If this view is being reconstituted from a Nib then your custom initializer will not be called, and instead the init?(coder:)
version will be called. Since Swift now requires an implementation of the required init?(coder:)
, I have updated the example below and changed the let
variable declarations to var
and optional. In this case, you would initialize them in awakeFromNib()
or at some later time.
class TestView : UIView {
var s: String?
var i: Int?
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
iOS: UIView subclass init will call [super init] then call methods in super class, why it will call [subclass initWithFrame:xx]?
Since initWithFrame:
is the designated initializer, apple's implementation of init
(which you call when you call [super init]
) internally calls the initWithFrame:
function and passes in CGRectZero
. That is the reason both get called. So the end flow ends up looking like this:
[YourClass init] -> [super init] -> [self initWithFrame:CGRectZero] ->
[YourClass initWithFrame:CGRectZero] -> [super initWithFrame:CGRectZero]
This is assuming you call [super init]
when you override init in YourClass and [super initWithFrame:]
when you override initWithFrame
.
Why UIView calls both init and initWithFrame?
The reason is that inside View1 initWithFrame:
you call [super initWithFrame:]
.UIView initWithFrame
: calls [self init]
.
When you call a method inside a class, the method on the very subclass is called.
So, when you call an instance method (e.g. init) on UIView it tries to call the init method on View1 (if it is implemented).
EDIT according to answer below: https://stackoverflow.com/a/19423494/956811
Let the view1 be an instance of View1.
The call hierarchy is:
- [view1(View1) init]
- [view1(UIView) init] (called by [super init] inside View1)
- [view1(View1) initWithFrame:CGRectZero] (called inside [view(UIView) init] )
- [view1(UIView) initWithFrame:CGRectZero] (called by [super initWithFrame] inside View1)
- ...
- NSLog(@"initWithFrame"); (prints "test1[8422:60b] initWithFrame")
- NSLog(@"init"); (called inside [view1(View1) init] ; prints "test1[8422:60b] init")
Check inheritance in OOP.
http://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)
http://www.techotopia.com/index.php/Objective-C_Inheritance
How to override both initWithFrame: and initWithCoder: in subclass of UIView?
Pack your special initialization in one method. It can be private (declared in .m).
Then override both initializers and call your init-method from within them.
- (void)myInitialization
{
//do your stuff
}
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
if (self)
{
[self myInitialization];
}
return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self myInitialization];
}
return self;
}
UIView: how to set property before initWithFrame is called?
You can't set a property on the object before you call the initialiser because until that has been called the object does not exist. If the initialiser needs access to a property, you will need to supply it as a parameter (since it is a requirement for successful creation of your object).
- (id)initWithFrame:(CGRect)frame
takes a CGRect
parameter because the purpose of this method is to create an instance with a predefined frame; it adds functionality to the default NSObject
's - (instancetype) init
, so is supplied with the frame
parameter to work with.
A UIView
needs a frame so it can be layed out on screen and rendered (amongst other things). At some point in the implementation it will perform a call to the default [super init]
method and then access self
to work with the frame it has been handed. It builds on an existing class.
You are building on the UIView
class in that you want to to be able to initialise it with a UIImage
. You can either opt to provide a default frame for your subclass:
- (instancetype)initWithImage:(UIImage *)image {
if (self = [super initWithFrame:CGRectMake(0,0,0,0)]) {
self.image = image;
}
}
Or provide a more 'useful' default value (Like UIImageView would do) and take the image dimensions as the default frame:
Initializing a UIImageView Object
- (instancetype)initWithImage:(UIImage *)image
Discussion
This method adjusts the frame of the receiver to match the size of the specified image. It also disables user interactions for the image view by default.
With an initialiser like:
- (instancetype)initWithImage:(UIImage *)image {
if (self = [super initWithFrame:CGRectMake(0,0,image.size.width,image.size.height)]) {
self.image = image;
}
}
Override init method of UIView in swift
In init(subviewColor: UIColor, subViewMessage: String)
, you aren't calling the designated initializer (as the compiler points out nicely).
If you don't know what designated initializers are, they are initializers that have to be called by the subclass at some point. From the docs:
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
In this case, the designated initializer for UIView
is init(frame: CGRect)
, meaning at some point, your new initializer init(subviewColor: UIColor, subViewMessage: String
must call super.init(frame:)
.
In order to fix this, make the following changes:
init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
super.init(frame: frame)
}
OR you can call your other initializer in your class which ends up calling the designated initializer.
override init(frame: CGRect) {
super.init(frame: frame) // calls designated initializer
}
convenience init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
self.init(frame: frame) // calls the initializer above
}
As for the convenience method with simply CustomLoadingView()
, you have to add another initializer for that. Add this code to your custom view:
convenience init() {
self.init(frame: DEFAULT_FRAME, subViewColor: DEFAULT_COLOR, subViewMessage: DEFAULT_MESSAGE)
}
If you want to learn more about designated and convenience initializers, read about them here and here.
Related Topics
Swift 3 How to Get Date for Tomorrow and Yesterday ( Take Care Special Case ) New Month or New Year
Memory-Mapped Files and Low-Memory Scenarios
Ios7 Uipickerview How to Hide the Selection Indicator
Should I Be Using Awakefromnib or Initwithcoder Here
How to Set Multi Line Large Title in Navigation Bar? ( New Feature of iOS 11)
Sending Push Notifications to iOS from Pwa
Set Uitableview Content Inset Permanently
Toplayoutguide in Child View Controller
How to Change "Initwithnibname" in Storyboard
How to Convert String to Date to String in Swift iOS
How to Add Done Button on Keyboard on Top of Keyboard in iOS
Uiview.Animatewithduration Swift Loop Animation
Swift Access Control with Target Selectors
Module Was Not Compiled for Testing' When Using @Testable
iOS 8.1 Simulator Localization Broken (Nslocalizedstring)
Assertion Failure When Using Uisearchdisplaycontroller in Uitableviewcontroller