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.
}
}
Related
I was working in a client project. I have written lot many view customisation code inside ViewDidLoad. I have models for data store and access.
The project was working fine. They hired a new iOS developer he said the code is not compliant with MVC architecture. The asked the reason why? He said the views are created inside viewDidLoad which is a controller of the Class hence it is not acceptable code. What should we do when its really dynamic views and can not be created using storyboard.
My answer is No
From apple doc
Controller Objects
A controller object acts as an intermediary between one or more of an application’s view objects and one or more of its model objects. Controller objects are thus a conduit through which view objects learn about changes in model objects and vice versa. Controller objects can also perform setup and coordinating tasks for an application and manage the life cycles of other objects.
I think that the controller has the responsibility to manage what the view look like.
In viewDidLoad,it is good to write one-time view customisation code here.
But if you write a lot configuration code to a view. I think it is better to use a subclass of UIView. This makes your code clear and easy to debug and maintain.
You can make differentiate between Controller and View in this way -
Use - (void)loadView {} delegate method to load your view from viewcontroller.
Suppose you have a view class -
CustomView.h
CustomView.m -
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor greenColor];
[self addSubview:self.centerView];
}
return self;
}
Now in your ViewController -
- (void)loadView
{
self.view = [[CustomView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
Please refer below url for more details -
http://matthewmorey.com/creating-uiviews-programmatically-with-auto-layout/
Hope this can be help you.
You would ideally have a dedicated class for your main view (the one that's accessed by self.view inside viewcontroller) - derived from UIView.
Inside this view class (say Myview.m) - you will create its subviews inside various view methods - such as init (for instantiation of subviews), layoutsubviews (for frame setting of subviews) and drawrect (any graphics drawing etc.) If the view has NIB, some processing will go inside awakeFromNib too.
I want to draw a chart in an UIView. The question is how do I get the data (Points) to the view. If I create a protocol and set the UIViewController as the delegate where in the UIView do I call the delegate methods (initWithFrame? might be to early, and the delegate might not be set, awakeFromNib? but the view is 100% created in code, it has no nib file) ..
initWithFrame? might be to early, and the delegate might not be set.
In fact, the delegate cannot be set by the time you're in initWithFrame:, since the first thing you do with an object after allocation is initialization, i. e. until the init method returns, you can't call (well, it's idiomatic not to do so, at least) any other methods.
What you have to do is have a loadData or reloadData method, that the delegate must call explicitly after having set itself as the delegate of your view. I. e., from the view controller, you can call it like this:
#implementation ChartViewController
- (id)init
{
if ((self = [super init])) {
chartView = [[ChartView alloc] initWithFrame:self.view.frame];
chartView.delegate = self;
[self.view addSubview:chartView];
[chartView reloadData];
}
return self;
}
Then, in your chart drawing view, implement - reloadData as follows:
- (void)reloadData
{
// Call the delegate here,
// then do the drawing
}
A better way is to use UIViewController instead of a UIView. Because your that view has to manage data. Managing data is a UIViewController's job.
make a protocol but dont call it delegate. call it dataSource :D
anyways, call it when you first need the data .... as late as possible.. NOT in init.. maybe in the setDataSource call.
or when you draw for the first time and see you have no data..
look at UITableView to see how he does it and imitate that
Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}
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.
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.