I need a function,dealing with something as UIView display on the screen automatically,
I think the way to implement it is overwr "- (void)didMoveToSuperview" or "- (void)didMoveToSuperview", am I right?
I also want to know more solutions about that.
Really thanks for your help.
Sounds to me like you might want viewWillAppear or viewDidAppear.
Typically, you want to do one-time setup such as setting instance variables in viewDidLoad (technically this can be called again, but only if the view was unloaded and viewDidUnload was called).
For things that might change every time the view is shown, use viewWillAppear (or viewDidAppear).
In any case, make sure to call the super methods:
- (void)viewDidLoad {
[super viewDidLoad];
// do one-time setup here
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do any setup here that might change each time the view is shown
}
Related
This is bugging me quit a bit. It seems like there is no predictable pattern for knowing when a control's frame properties are guaranteed to be valid. Specifically I'm trying to alter a UILabel's frame position depending on the hardware. In the both viewDidLoad and viewWillAppear, the frame property is nil. If I wait till viewDidAppear to adjust it, the user will see it the label move, which is crappy.
On other view controllers in the past I've been able to get label's frame in the eariler methods. What gives? Is there anything I can do to kick start the layout?
I should mention that the UILabel in question was added using IB and storyboards which is usually how I do my VCs.
- (void)viewDidLoad{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
You could try viewDidLayoutSubviews -- that's called before viewDidAppear. Alternately, you could adjust the position with constraints rather than frames, which you can do in viewDidLoad.
When the user switches to another program and then back again., the original program's view will be replaced by a new view from another program. So when the user switches back to the original program, would viewDidLoad be called the second time ?
Am asking this because if this is the case, then the initialization code placed inside viewDidLoad would be executed every time the user switches the screen back and forth. And this could result in reseting views and loosing unfinished works of the user ...
Don't do view controller initialisation in viewDidLoad. This is a common mistake.
For stuff that should only happen once when the view controller is loaded, do it in the controller's init method, like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:bundleOrNil]))
{
//do your initialisation here
}
return self;
}
The initWithNibName:bundle: method is called before the view is loaded from the nib, and is only called once in the lifespan of the view controller.
The controller's view can be loaded and unloaded multiple times during the lifespan of the controller and viewDidLoad will be called every time. It may be unloaded whenever it's not on screen, usually if memory is low.
If you do set stuff up in viewDidLoad (e.g. adding subviews programmatically) you should always unset them again in viewDidUnload.
Think of viewDidLoad and viewDidUnload as being like the init/dealloc for the view property of the view controller. For stuff that relates to the views, create and release it in those methods. For stuff that relates to the controller itself, create and release it in initWithNibName and dealloc.
UPDATE: On iOS 6 and later, viewDidUnload is never called any more (unless the view is explicitly set to nil in the code), and so viewDidLoad will typically only be called once in the life of a view controller. This makes the advice above less critical, but it's still best practice, and still necessary if you need to support iOS 5 and earlier.
UPDATE 2: If you are loading your view controller from a Storyboard (which is now the recommended practice) instead of creating it programmatically then initWithNibName:bundle: will not be called. Use initWithCoder: or awakeFromNib to initialize your controller instead.
#Nick Lockwood provides excellent information, but there are a few more things to remember.
First, initWithNibName:bundle: is not called if the view controller is instantiated from a nib file or storyboard. In that case, initWithCoder: and awakeFromNib are called instead. This situation used to be somewhat uncommon on iOS, but with the addition of storyboards it is now much more common for view controllers to bypass initWithNibName:bundle:.
I recommend putting non-UI initialization code in a separate method (I call mine setup) and call it from both initWithNibName:bundle: and awakeFromNib. But I only do this if it's important that that initialization only run once. Otherwise I put it in viewWillAppear: to be as lazy-load as possible.
Second, you should not do anything that references self.view in init... or awakeFromNib. You should never reference self.view until viewDidLoad is called (otherwise you will force the nib file to be loaded sooner than it is needed). UI-related things should go in viewDidLoad if they're related to setting up the views, or viewWillAppear: if they're related to configuring the views (i.e. loading them with data).
So the way I usually set these things up:
#implementation
- (void)setup {
// Non-UI initialization goes here. It will only ever be called once.
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void)viewDidLoad {
// Any UI-related configuration goes here. It may be called multiple times,
// but each time it is called, `self.view` will be freshly loaded from the nib
// file.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Set all IBOutlets to `nil` here.
// Drop any lazy-load data that you didn't drop in viewWillDisappear:
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Most data loading should go here to make sure the view matches the model
// every time it's put on the screen. This is also a good place to observe
// notifications and KVO, and to setup timers.
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Unregister from notifications and KVO here (balancing viewWillAppear:).
// Stop timers.
// This is a good place to tidy things up, free memory, save things to
// the model, etc.
}
- (void)dealloc {
// standard release stuff if non-ARC
[[NSNotificationCenter defaultCenter] removeObvserver:self]; // If you observed anything
// Stop timers.
// Don't unregister KVO here. Observe and remove KVO in viewWill(Dis)appear.
}
#end
-viewDidLoad will be called once whenever the view controller needs to load its view hierarchy. Obviously, that'll happen the first time that the controller accesses its view. If the view controller later unloads its view, then -viewDidLoad will be called again the next time the view is loaded. A view controller won't unload its view just because the view is hidden, but it might do so if memory starts to run low.
A view controller should know the state of its views and be able to set them up as necessary in its -viewDidLoad method. Views shouldn't be used to store state -- nothing should be irrevocably lost just because the view is unloaded.
So when the user switches back to the original program, would
viewDidLoad be called the second time ?
(Above is from the op)
In those cases there are two methods to be called:
- (void)applicationWillEnterForeground:(UIApplication *)application;
reopening a backgrounded app (from task manager or from springboard again)
unlocking device which is locked when the app is active.
- (void)applicationDidBecomeActive:(UIApplication *)application
after phone calls
notification center dismissal
task manager dismissal (double tap home button & double tap again)
From the docs:
This method is called after the view controller has loaded its associated views into memory.
So, it is called whenever the view controller has its views loaded into memory. This could be the first time the view is loaded and never again, or every time the view is made visible if your view unloads constantly (viewDidUnload due to memory constraints, etc.)
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 have an application where I was using buttons on a toolbar to call up the views, but I am switching it over to using a tab bar.
When using the buttons I was using the following code in MainViewController.m to update the values on the page and it was working just fine:
-(IBAction) loadSummaryView:(id) sender {
[self clearView];
[self.view insertSubview:summaryViewController.view atIndex:0];
//Update the values on the Summary view
[summaryViewController updateSummaryData];
[summaryViewController calculateData];
}
However, with the Tab Bar I can not figure out how to do this. I tried putting all of the necessary code in the Summary Views viewDidLoad and it loads the initial values, but it will not update the values when I change them in another view.
Any help is appreciated. I am a bit new at this, so please don't be cryptic as I may not understand the response.
Thank you.
By placing your code in viewDidLoad, it will only be called when the view is loaded from the nib. Unless you're running low on memory, this view will remain loaded for the duration of the life of your app.
So if you need to update values every time the view will appear, consider moving that code to an override of viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// your stuff goes here...
[self updateSummaryData];
[self calculateData];
}
You can update your current view in the viewWillAppear:animated message of the view:
If you have everything setup correctly, there is nothing to do, the UITabBarController will show your view, an your UIViewController will receive a viewWillAppear message, where you can do your update:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self updateSummaryData];
[self calculateData];
}
I strongly recommend reading the View Controller Programming Guide for iOS which describes in detail the main interface paradigms supported by the iPhone.
Is there a way to know that a view controller is somewhere in the view controller lifecycle between -viewWillAppear and -viewWillDisappear?
I ask because I want to be damned sure that a method isn't fired when my view is either not on screen, or is about to disappear from the screen. When the view is about to disappear from the screen, certain objects which I cannot explicitly check at runtime may or may not be deallocated, so, obviously, interacting with them can lead to message sent to deallocated instance errors.
At present, I'm keeping track with a BOOL, like so:
- (void)viewWillAppear:(BOOL)animated {
isOnScreen = YES;
[super viewWillAppear:animated];
}
- (void)willWillDisappear:(BOOL)animated {
isOnScreen = NO;
[super viewWillAppear:animated];
}
And my method looks like this:
if (isOnScreen) [self doSomething];
Is there a simpler way to do this?
your way seems to be the simplest approach, if not the most robust. (simply checking if that instance of the view exists seems like the correct approach (if it hasn't been dealloced yet)).
I also don't REALLY understand the purpose of this, unless you have another view controller running methods that pertain to the view controller being showed that you are using the boolean for. In that case, its more a design problem than an upkeep problem.
Use viewDidDisappear
- (void)viewDidDisappear:(BOOL)animated {
// Do stuff that has to be done when this view is off screen.
}