I have a subclass of UIViewController that I want to add from the storyboard.
So I'm using what seems the standard methodology:
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
[self addChildViewController:svc];
[self.view addSubview:svc.view];
Which is fine but what if I want to call a custom init method on the subview?
I can do something like:
svc = [svc initWithFoo:#"Hello"];
Which seems to have to go after the addSubview call inorder for it to work.
Is this the best way to do this?
Seems a bit unorthodox. Calling an init method on an object that has already been created seems like its no longer truly an init method.
Maybe I should call it setWithFoo: or something and not have it return anything?
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
will cause the SubViewController to be inited with it's - (id)initWithCoder:(NSCoder *)decoder {} method.
Override that method (don't forget to call super)
If you want to do additional setup to your view controller after you instantiate it form the storyboard you can create some methods in the view controller's class and call them after the instantiate method fo the storyboard.
But be careful, if you try to make changes on any UI component in those methods, they wont be applied, and probably the app will crash. So use those methods to set params to the View Controller like array of objects, or any kind of data, and apply the UI changes for the view controller's view in viewDidLoad/viewWillAppear/viewDidAppear methods of your view controller.
Essentially I think the answer is that you can't use custom initialisers on ViewControllers added from the storyboard. Instead you have to set properties directly or through a method at the appropriate time in the life cycle as stated above.
Also as mentioned, the VC will be instantiated through initWithCoder, so calling an additional initialiser might be superfluous(?).
I encountered problems trying to use a custom initialiser that contains a call to super if I called it before the subview was added. I would just get a blank view added, I think because the superclass doesn't seem to know about the storyboard at that point. I had more success removing the call to super but that seems wrong.
This case would be more pertinent when adding subviews to a scrollview. For simplicity I left this out of my example.
Related
If a new iOS project is created with an Empty App template in Xcode 4.3.2, and in AppDelegate.m:
self.window.rootViewController = [[FooViewController alloc] init];
and in FooViewController's viewDidLoad, the following:
NSLog(#"self.view is %p", self.view);
NSLog(#"self.view is %#", self.view);
will print out the view, so it looks like the default loadView will instantiate a view and assign it to self.view.
So if I override loadView with an all empty method, and comment out the second NSLog statement above, I expect the first NSLog statement to print out 0x0, but instead the app crashed due to bad memory access right at that NSLog line. Why would that be?
Okay, after a knee-jerk and obviously wrong answer, I tried this. The Empty App template would not have a rootViewController, so I used a single screen template. After running, I see that you are getting a stack overflow. In trying to access self.view, you are calling the view property on the superclass, which is then trying to load the view in order to return it, which is calling viewDidLoad, etc., as far as I can see. The other NSLog statement does the same.
The documentation for the view property in UIViewController states:
Because accessing this property can cause the view to be loaded automatically, you can use the isViewLoaded method to determine if the view is currently in memory.
It also has a link to The View Controller Life Cycle, which states:
The steps that occur during the load cycle are as follows:
The load cycle is triggered when the view controller's view property is accessed and the view is not currently in memory.
The view controller calls its loadView method. The default implementation of the loadView method does one of two things:
If the view controller is associated with a storyboard, it loads the views from the storyboard.
If the view controller is not associated with a storyboard, an empty UIView object is created and assigned to the view property.
The view controller calls its viewDidLoad method to allow your subclass to perform any additional load-time tasks.
So when you say:
So if I override loadView with an all empty method
You're deliberately breaking the life cycle, because when your overridden version of loadView finishes, it should have loaded a view. Because it didn't, you get a crash.
In a UIViewController subclass, I have the following methods:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something
myTextField.text = #"Default";
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// do something
[myTextField selectAll:self];
[myTextField becomeFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
// do something
myTextField.delegate = self;
}
The NIB has been created using Interface Builder. The corresponding view controller object is pushed by the navigation controller through pushViewController.
The inteded behavior is to show a default text entry in a text field, to select the entire text and to set the text field as first responder. [Edit: I've noticed that selecting all and making first responder makes no sense as the selection would dissapear; still, I wonder why the methods behave as described next.]
However, while methods viewDidLoad and viewWillAppear are called, the method viewDidAppear is not called. Can anybody tell me why? Most questions I found on the web and here deal with both viewWillAppear/viewDidAppear are not working; I also understood that in subviews or programmatically created views these methods are not evoked; but this does not apply in case and also I wonder why one of these "lifecycle" methods is evoked and the other not.
Any idea? Thanks!
I had this issue happen to me: viewWillAppear was being called but viewDidAppear was not!
I finally figured out that this was because I had a tabBarController where I overloaded it's own viewDidAppear and forgot the [super viewDidAppear:animated];
It threw off every VC in every tab! adding that line back in fixed it for my other VC's.
Hope this helps someone!
There is one case when viewWillAppear will be called but viewDidAppear will not.
Suppose you have two viewControllers and you push from the first to the second controller. Then, using the swipe, you want to go back to the first one, both controllers are displayed at the same time and at that moment viewWillAppear will be called from the first controller.
This way, you do not finish the swipe and stay on the second controller and viewDidAppear will not be called from the first controller.
I had the same problem.
I had copy/pasted viewDidAppear to create viewWillAppear but had forgotten to change the super.viewDidAppear() call. This then seemed to stop viewDidAppear from being called.
It sounds like somewhere in your code you have missed or messed-up a call to the superclass.
The call to viewDidAppear: should always follow viewWillAppear: unless you are doing something custom, which you say you don't. I don't know why it doesn't work but here are a few ideas:
Could it be that you are doing something strange in one of the delegate methods for UITextFieldDelegate? It's unlikely that it would affect viewDidAppear: being called but it could be a culprit.
Have you loaded a lot of stuff into memory before pushing the view? I'm not sure what would happen if you got a memory warning between viewWillAppear: and viewDidAppear:.
Have you tried to do a Clean? Sometimes that can help.
In cases like these when it should work I usually create a new class and the introduce the functionality one at a the time to see if I can get it work that way. I tried your code in a new Navigation Based project where I added a new UIViewController with an outlet to the text field. Then I pasted the code from the question and it did work as expected.
This can be because you added a child view controller to your parent VC in viewDidLoad or viewWillAppear. The child's appearance prevents the call to viewDidAppear.
This is a crazy thing to do, and I only know because this was a bug in my code. I meant to add the child VC to this VC, not the parent VC.
I'm writing my own SplitViewController from scratch (i.e. by subclassing UIViewController and not UISplitViewController).
It has two sub-viewControllers (one for the left panel and one for the detail right panel), to which I need to send the appropriate messages (viewWillAppear, viewDidAppear, viewWillDisapppear and viewDidDisappear).
I am already forwarding those messages when my custom SplitViewController receives them and it works fine. However I am struggling to figure out when to send them when any of the two sub-viewcontrollers is replaced by a new one, which also needs to receive those messages. I am adding the view of the new UIViewController properly but the messages are not called adequately.
My initial approach was to call them in the setter of the sub-viewControllers, calling viewWillDisappear to UIViewController about to be released and viewWillAppear to the new UIViewController set, but this one is executed before viewDidLoad and therefore I presume is wrong.
I have also seen that UIView has a method didAddSubview: that might be useful to know when to call viewDidAppear on the correspondent UIViewController.
Any help would be much appreciated!
If you want to mirror UISplitViewController, it seems best to just have dummy UIViewControllers that print out whenever each method is called.
As for your current problem of the ordering of viewWillDisappear, viewWillAppear and viewDidLoad, just do:
-(void)setSomeViewController(UIViewController newVC)
{
[oldVC viewWillDisappear];
[newVC view]; // Causes newVC to load the view,
// and will automatically call -viewDidLoad
[newVC viewWillAppear];
[oldVC.view removeFromSuperview];
[self.view addSubview:newVC.view];
//retain and release as appropriate
// other stuff you'll need to mirror, etc. etc.
}
I've created a view controller and the associated view using the interface builder. I'm trying to call a function that I added to the UIView from the UIViewController. I'm not sure how to call that function though.
I've tried
[self.view myFunction]
but that just makes my program crash.
Did you declare your IB outlets and connect the method to it?
Your view (more accurately nib)'s file owner should be set to your viewController class.
Unless your calling drawing functions and methods you shouldn't call anything on the view. You just don't need to.
Edit: grammar corrections.
How is the view getting initialized? Is it a custom view type? If your view is being initialized from a nib, make sure that the class on the view type is the class that has the method implemented.
It is probably crashing because UIView does not have a method named myFunction. In order to add myFunction to a UIView object, you need to subclass it.
I assume you have subclassed your UIView, and called it something like MyView. You also probably have subclassed your UIViewController and called it MyUIViewController. Therefore you may have:
#interface MyViewController : UIViewController {
MyView *myView;
....
}
Now you can call [self.myView myFunction];
That's one way. Another way may be:
[(MyView *)self.view myFunction];
This depends on whether myView is the first view in the hierarchy (or should be set up in your NIB files).
The iphone program is set up in the Utility Application model where you have a root view and two subviews (mainViewController & flipsideViewController) which are outlets.
If I try to set the UISwitch at rootViewController's viewDidLoad (with [flipsideViewController.switchInstance setOn:YES]), it doesn't work.
Within flipsideViewController, that method works but not outside of it. Any idea why? I can pass other methods...
Even if I pass a method to the flipsideViewController that then does the setOn method, it still doesn't work...
Any ideas?
Just as a 'sanity-check' (as others have put it to me) have you made sure your flipsideViewController variable is not null at the time you are calling the above code?