Instantiating View Controller from code - When is viewDidLoad called iOS - ios

I am using something like:
VC = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
viewDidLoad is not called yet.
But when I do:
VC.view.frame = CGRectMake(...);
At this point viewDidLoad is called.
But the issue is, that the view dimensions that I am passing in the above code statement is not used in the viewDidLoad method.
I think it sees that view is being used, so it is time to load the view, and after loading the view it must be assigning the frame dimensions to the view. But what if I want that view dimensions set before viewDidLoad gets called, so that I can use those dimensions in the viewDidLoad method..
Something like initWithFrame..
Also, I don't have the view dimensions in the view controller. I have to assign the view dimensions from outside of the VC.
So probably after calling initWithNibName:bundle: method I can save the view frame dimensions in some variable.. but that doesn't look like a clean solution, does it?

viewDidLoad is called when the view did load. (surprise)
so by the time you call VC.view, before it return, the viewDidLoaded will be executed and then the view is returned, and set the frame.
so from your current approach, it is not possible
anyway, why you need view frame in viewDidLoad? maybe you can move that part into viewWillAppear / viewDidAppear which is only get called when the view is about to present

You can do something like this:
In the interface
#interface SettingsViewController : ... {
CGRect _initialFrame;
}
...
- (id)initWithFrame:(CGRect)frame;
#end
In the implementation
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
_initialFrame = frame;
}
return self;
}
- (void)viewDidLoad
{
self.view.frame = _initialFrame;
[super viewDidLoad];
}
and then from the class you use these controller:
VC = [[SettingsViewController alloc] initWithFrame:CGRectMake(...)];

Related

Cannot form weak reference to UIScrollView sub class (EXC_BAD_INSTRUCTION) when dismissing modal view

Hi everyone I've been debugging this issue for quite some time but no luck so far. I am quite lost here and have no clue on the reason causing this crash and how to fix it. I will be very grateful if anyone can offer me some help on this, thanks a lot!
I've prepared a sample project to demonstrate the issue at GitHub here.
The scenario is as the following:
There are two view controllers, namely the root view and modal view, each has a custom scroll view (class namely SubScorllView) as sub view, and the modal view has a button for dismissing the modal view.
The scroll views are sub-classes of UIScrollView, each with their corresponding delegate protocol, and their class hierarchy is as below:
UIScrollView
∟ SuperScrollView
.....∟ SubScrollView
The app launches and runs in a very simple manner, in AppDelegate's didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor blackColor];
RootViewController * rootVC = [[RootViewController alloc] init];
self.navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
self.navVC.navigationBarHidden = TRUE;
self.window.rootViewController = self.navVC;
[self.window makeKeyAndVisible];
ModalViewController *modalVC = [[ModalViewController alloc] init];
[self.navVC presentViewController:modalVC animated:YES completion:nil];
return YES;
}
And the views are loaded from xib files, which the scroll views' delegate are also set inside, and there are some overrides regarding the methods for initiating and setting delegate for the scroll view sub-classes.
The problem occurs when I dismiss the modal view through clicking the "Close" button in the modal view, when the button is clicked, the following happens:
- (IBAction)didPressedCloseButton:(id)sender {
self.subScrollView.delegate = nil;
[self dismissViewControllerAnimated:YES completion:nil];
}
And the app crashes at the following segment in SuperScrollView:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
super.delegate = self; // app crashes at this line
}
With the following error message in the console:
objc[6745]: Cannot form weak reference to instance (0x7fa803839000) of
class SubScrollView. It is possible that this object was
over-released, or is in the process of deallocation.
I don't understand why the app would crash and giving the above error message, or how should I fix it. I tried to search with the error message but seems that message is mostly related to other classes like text views, while some others solved it with setting the delegate of the scroll view to nil before deallocating but it doesn't work in my case.
==========
Update: Just tested if this happens on iOS 8 with simulator, it doesn't crash like on iOS 9 at all.
When the SuperScrollView is deallocated, setDelegate is called implicitly. In iOS 9, you cannot set the delegate to self, because self is in the process of being deallocated (no idea why this worked in iOS 8). To work around this issue, you can check first to see if the passed in delegate parameter is not nil, and only then set super.delegate to self:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
if(delegate)
{
super.delegate = self;
}
}
If for some reason you need to support self responding to the UIScrollView delegate methods even when _superScrollViewDelegate is nil, you could create a parameter
#interface SuperScrollView ()
#property (nonatomic, weak) SuperScrollView * weakSelf;
#end
at the top of the file, and set it in setup
- (void)setup {
super.delegate = self;
self.weakSelf = self;
}
Then, in setDelegate, just check that weakSelf is not nil. If weakSelf is nil, then self is in the process of deallocating and you should not set it to the super.delegate:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
if(self.weakSelf)
{
super.delegate = self;
}
}
super.delegate = self ,super here is UIScrollView, super.delegate is of type UIScrollViewDelegate, and self is of type UIScrollView, so you are setting UIScrollView's delegate to be a scroll view, which doesn't make sense, normally controller should be the delegate of UIScrollView.
When you dismiss modal view controller, it is in the process of deallocation. super.delegate = self;, here self is a scroll view which is a subview of self.view, which belongs to the modal view controller. so self is also deallocating.
I had the same problem in Swift and cncool's answer helped me.
The following (considering being in the parent class's instance) fixed my issue:
deinit {
self.scrollView.delegate = nil
}

UICollectionView datasource not being called

Even though I am setting delegate and datasource, the data source methods are never being called.
I have a ViewController that adds a subview as such:
EVPhotoCollectionViewController *pc = [[EVPhotoCollectionViewController alloc] initWithNibName:#"EVPhotoCollectionViewController" bundle:nil];
self.damagePhotosView = pc.view;
Inside EVPhotoCollectionViewController I have delegate and datasource wired up in the xib, but also via code as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView reloadData];
}
None of the datasource methods are ever called. I have verified self.collectionView is not null when it calls reloadData.
Thanks!
I think there are several things wrong here--and there are some complexities to view controller containment that you may need to read up on.
First off, you're not adding the EVPhotoCollectionViewController view as a subview of your vc, eg:
[self addSubView:pc.view];
Also, you're not setting a frame for the EVPhotoCollectionViewController, so depending on how it's implemented, it might not show up with the right size/position.
Lastly, it doesn't look like you're retaining the EVPhotoCollectionViewController anywhere. Its view will be retained by the view hierarchy, but it looks like the instance of EVPhotoCollectionViewController will be dealloc'd once the function creating it goes out of scope.
View controller containment: How does View Controller Containment work in iOS 5?

Share a view between UIViewControllers? Second viewController "steals" view from first?

I am experiencing this behavior: I have a UIViewController UIView which I want to share between 2 or more UIViewControllers.
First I instance the shared UIViewController in AppDelegate with:
SharedViewController *sharedViewController = [[SharedViewController alloc] init];
Then, when I am instancing the new UIViewController I add the shared view controller view:
ViewController1 *viewController1 = [[ViewController1 alloc] init];
and inside it I add the shared view controller view:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:sharedViewController.view];
}
This works fine, as long as a second view controller - i.e. viewController2 - does the same thing, then viewController2 gets the view and viewController1 gets nothing!
I applied a workaround, placing this in viewWillAppear and it seems to work, but I am afraid I am adding the same view one onto another multiple times, isn't there a better way to do this?
-(void)viewWillAppear {
[self.view addSubview:sharedViewController.view];
}
-(void)viewWillDisappear {
[sharedViewController.view removeFromSuperview];
}
This should work.
But are you sure it has to be a shared UIViewController, if you only use the view? Why not just a shared UIView?

viewWillAppear is not fired

I have setup a basic test app that displays a view containing a label, with no use of IB. I want to use a custom UIView subclass AND custom UIViewController subclass.
This will run as anticipated, but the MyViewController's viewWillAppear and other similar delegates do not fire.
What am I missing to make these fire? In previous projects (using IB), these would fire just fine.
Here is the complete code:
AppDelegate - loads a 'MainVC' view controller and sets it as the root controller
#import "AppDelegate.h"
#import "MainVC.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize mainVC = _mainVC;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.mainVC = [[MainVC alloc] init];
self.window.rootViewController = self.mainVC;
[self.window makeKeyAndVisible];
return YES;
}
MainVC - creates a 'MyViewController' which allocates the 'MyView' (it also passes down the frame size that should be used for the view)
#import "MainVC.h"
#import "MyViewController.h"
#implementation MainVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
MyViewController *controller = [[MyViewController alloc] init];
CGRect frame;
frame.origin.x = 5;
frame.origin.y = 5;
frame.size.width = self.view.frame.size.width - (2 * 5);
frame.size.height = self.view.frame.size.height - (2 * 5);
controller.startingFrame = frame;
[self.view addSubview:controller.view];
}
return self;
}
MyViewController - creates the MyView
#import "MyViewController.h"
#import "MyView.h"
#implementation MyViewController
#synthesize startingFrame;
- (void)loadView{
self.view = [[MyView alloc] initWithFrame:startingFrame];
}
- (void)viewWillAppear:(BOOL)animated{
NSLog(#"appearing"); //doesn't do anything
}
- (void)viewDidAppear:(BOOL)animated{
NSLog(#"appeared"); //doesn't do anything
}
MyView
#import "MyView.h"
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
label = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 150, 40)];
[label setText:#"Label"];
[self addSubview:label];
}
return self;
}
Your mistake: You're setting a root view controller and then adding another's view controller view on top of that. While the second view is added to the view hierarchy, its view controller remains "unwired" this way. In fact if you check on your MainViewController's parentViewController, you will notice it's nil.
Why: The viewWillAppear method will be sent only to the root view controller or to view controllers in the hierarchy of the root view controller (those that were presented using presentModalViewController:animated: or presentViewController:animated:completion:).
Solutions: to solve it you have a few options:
Use your view controller as the root view controller
Present your view controller through one of the methods mentioned above
Keep your code as it is and manually wire those events to child view controllers (beware of this method though, as I believe the events you mention are automatically forwarded under iOS 5 - you can easily check this out).
If I recall properly another way to make these event get forwarded to your view controller is to add your view controller's view to the window, rather than to the parent view.
There's a number of very basic things that went wrong:
you're doing your whole setup in initWithNibNamed: for your MainViewController, yet you're creating it calling just init. So your setup will never happen
you're implementing a second VC (MyViewController), apparently just to create myView, which you then add to your rootVCs hierarchy. Not good! Only a single VC (in your case MainViewController) should be responsible to create and manage the views in its hierarchy
don't do VC controller setup in loadView, like you did in MyViewController. In your case it is the only way to make things work, because MyVC never actually gets fully up and running, but the approach is wrong - you're basically forcing the View Controller to set up the view, although the controller itself is never in control of anything
There's a few more things, but those are the most important ones - it appears like it would be a good idea for you to read about the whole basic concept of the Model - View - Controller concept again. Next, you should be digging through the class references for both UIViewController and UIView.
Even if you would get the results you desire at last using your current approach, it wouldn't help you in the long run, because you wouldn't learn to use the involved elements properly.
Methods are not invoked on a view controller inside another view controller. If developing for iOS 5 only then check out UIViewController Containment which can be used to solve this. If you want your application to be compatible with previous iOS versions you can forward method invocations to your child view controller. Personally I prefer subclassing SFContainerViewController which handles this automatically: https://github.com/krzysztofzablocki/SFContainerViewController

iPhone SDK: what is the difference between loadView and viewDidLoad?

When working with views and view controllers in an iPhone app, can anyone explain the difference between loadView and viewDidLoad?
My personal context, is that I build all my views from code, I do not and will not use Interface Builder, should that make any difference.
I've found that often when I add init code to loadView, I end up with an infinite stack trace, so I typically do all my child-view building in viewDidLoad...but it's really unclear to me when each gets executed, and what is the more appropriate place to put init code. What would be perfect, is a simple diagram of the initialization calls.
Thanks!
I can guess what might be the problem here, because I've done it:
I've found that often when I add init code to loadView, I end up with an infinite stack trace
Don't read self.view in -loadView. Only set it, don't get it.
The self.view property accessor calls -loadView if the view isn't currently loaded. There's your infinite recursion.
The usual way to build the view programmatically in -loadView, as demonstrated in Apple's pre-Interface-Builder examples, is more like this:
UIView *view = [[UIView alloc] init...];
...
[view addSubview:whatever];
[view addSubview:whatever2];
...
self.view = view;
[view release];
And I don't blame you for not using IB. I've stuck with this method for all of Instapaper and find myself much more comfortable with it than dealing with IB's complexities, interface quirks, and unexpected behind-the-scenes behavior.
loadView is the method in UIViewController that will actually load up the view and assign it to the view property. This is also the location that a subclass of UIViewController would override if you wanted to programatically set up the view property.
viewDidLoad is the method that is called once the view has been loaded. This is called after loadView is called. It is a place where you can override and insert code that does further initial setup of the view once it has been loaded.
viewDidLoad()
is to be used when you load your view from a NIB and want to perform any customization after launch
LoadView()
is to be used when you want to create your view programmatically (without the use of Interface Builder)
Just adding some code examples to demonstrate what NilObject said:
- (void)loadView
{
// create and configure the table view
myTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStyleGrouped];
myTableView.delegate = self;
myTableView.dataSource = self;
myTableView.scrollEnabled = NO;
self.view = myTableView;
self.view.autoresizesSubviews = YES;
}
- (void)viewDidLoad
{
self.title = #"Create group";
// Right menu bar button is to Save
UIBarButtonItem *saveButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Save" style:UIBarButtonItemStyleDone target:self action:#selector(save)];
self.navigationItem.rightBarButtonItem = saveButtonItem;
[saveButtonItem release];
}
To prevent an infinite loop from happening when you read self.view, call the class' super implementation when you load a view. The super implementation will allocate a new UIView for you.
- (void) loadView {
[super loadview];
// init code here...
[self.view addSubView:mySubview1]; //etc..
}
The easiest way to use loadView is to make some type of base view controller, like MyBaseViewController which is subclass of UIViewController. In it's loadView method create view in this way:
-(void) loadView {
if ([self viewFromNib]) {
self.view = [self viewFromNib];
} else {
self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
}
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.view.backgroundColor = [UIColor whiteColor];
}
And when you need to make some view controller you just use subclass of MyBaseViewController and in it's loadView controller you just call [super loadView] like this
//sucblass loadView
-(void) loadView {
[super loadView];
//rest of code like this..
UILabel *myLabel = [[UILabel alloc] initWithFrame:myFrame];
[self.view addSubview:myLabel];
[myLabel release];
}
loadView() is called when your controller is asked to create its self.view. You can do it by yourself like
self.view = [UIView alloc] init...];
Or your controller's parent UIController class has already a method name -loadView() which initializes your self.view into blank view. Then you can call
[super loadView];
I really recommend the second approach as it encourages the inheritance. Only if your view controller is not directly inherited from UIViewController.
The definition given by Apple on viewDidLoad mentioned that it is called after the controller’s view is loaded into memory. To put it in a simple term, it is the first method that will load.
You might be thinking under what condition will this method being fully utilized? The answer is, basically whatever you wanted the app to load first. For instance, you might want a different background color, instead of white, you could perhaps choose blue.

Resources