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.
}
Related
I have a problem. My view controller (ViewController) implement a delegate method of a object (DataPuller, data get from the internet). DataPuller will retrieve data on the internet without blocking user interaction with the view. But when I navigate between screen, in some cases, that ViewController release before DataPuller return the list of objects. The DataPuller return, it checks:
if (delegate && [delegate respondsToSelector:#selector(getCommentDidDownloadFinish:)]) {
[self.delegate performSelector:#selector(getCommentDidDownloadFinish:) withObject:self];
}
And the application crash here because ViewController release, it becomes a zombie object. Does anyone have this problem before and how to solve it? I think another way is using NSNotification, but I wonder any other better solutions. Any ideas, solutions are welcomes. Thanks.
Your view controller must remove itself as the DataPuller delegate at some point. Typicially, this is handled in the dealloc method:
- (void)dealloc {
dataPuller.delegate = nil;
[dataPuller release];
[super dealloc];
}
You may also decide to do this in -viewDidUnload or -viewDidDisappear:.
Delegation (usually) implies some sort of ownership - i.e., if you make an object a delegate of another object, usually the delegate object holds a strong reference (i.e., retains) the delegating object.
As an example, a UITableViewController is the delegate of its UITableView. This is okay, because the controller retains the tableview through the "view" property.
If your design does not allow ownership, use notifications, like you already suggested. As a bonus, notifications can signal multiple listeners if you would ever need that.
Don't forget to remove your observer in the dealloc of the view controller!
I have made a UIView sub class to deal with iAds and adMob. The view controller is the sub class delegate and everything works ok. Sometimes however, the view controller disappears before the ad has been fetched. To resolve this I set the delegate to nil in my view controller's dealloc implementation.
The problem I have is that sometimes the respondsToSelector: method is ran at the same time as the view controller is getting deallocated. This causes a crash. Does anyone know how I may rearrange this?
The ad view is on about 10 different view controllers so I would like one place to create the ad code.
Many Thanks
One easy way is retaining the view until you had a chance to set the delegate to nil, and then release it. Then you can ensure that the object is still alive and prevent crashes.
If you can't retain the view, then use a static method to get the instance which is cleared in dealloc. ie: instead of:
if (delegate && [delegate respondsToSelector:#selector(...)])
Do this:
if (s_myViewDelegate && [delegate respondsToSelector:#selector(...)])
In the class:
- (id) init {
s_myViewDelegate = self;
...etc...
}
- (void) dealloc {
s_myViewDelegate = nil;
}
if (delegate && [delegate respondsToSelector:#selector(...)])
Although this is a question long ago, I really messed with it a bit and finally found something that might help.
Set a completion block rather than a delegate for finished or failed event, and this would help.
Yes it's a problem with iAd and admob. I had also this kind of problem. I have solved the problem by adding add view on main window and make delegate to app delegate so app delegate will never deallocated until you close the application.
There is actually a fast and not really good solution - to use #try/#catch block. Just if you get to the #catch block your delegate fails for sure... like:
#try{
if (delegate && [delegate respondsToSelector:#selector(...)])
[delegate callBackMethod];
}
#catch (NSException *e){
// if you get here then delegate is no longer valid regardless its reference is still valid
}
You should not have 10 individual ad views, that's wrong on so many levels. Instead you should have just one that you either move between individual views or - smarter - just keep on top.
You can for example add a view to a tabBarController.view and this will stay present even if you switch tabs. For views that you don't want the ad on you can simply hide it.
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 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
}
I am using UIImagePickerController to take a photo from the camera. However, the I find that randomly my calling controller (the one that is shown before the UIImagePickercontroller is shown) gets unloaded. I logged the viewDidUnload, and indeed it does get called. When the camera is done and dismissed, my controller's viewDidLoad will be called, unfortunately all the state is now gone. Things like text entered or things selected will be gone.
Obviously this is something to do with running out of memory. But is this behavior normal? Should I be handling it? This is NOT typically how modalViewController works. Usually you show it and dismiss it, everything should be intact.
What is a good way to avoid data lost in this case? Should I have to write a bunch of code to save the full state?
what's happening is that your view controller's didReceiveMemoryWarning is being called, and then since your view isn't visible, it's being unloaded.
the solution is that any data that wants to be persistent should be stored in your view controller rather than in your view.
you can prevent this from happening by providing an implementation of didReceiveMemoryWarning in your UIViewController class that doesn't call [super didReceiveMemoryWarning] which is where the unloading of the view happens, but it's still a good thing to try to understand what's going on.
It's really not advisable to override -didReceiveMemoryWarning so that UIViewController can't deallocate the view because then you may run into a "real" problem of low memory and your app will be forced to quit by the system.
What you really should do is store the text and other bits of entered data from the view in your -didReceiveMemoryWarning method (making sure to call super). Then in -viewDidLoad you can put the text and other bits back into the UI.
This will lighten the memory usage and reduce the likelihood of your app being forced to quit because there's no more memory left. All the while, the user won't know it ever disappeared!
My solution to the same issue described above was to declare an instance variable in my view controller which stores the contents of the UITextView should the view get unloaded. Thus in my viewWillDissapear method I save the contents of UITextView.text into my instance variable and in my viewWillAppear method I restore the contents.
- (void)viewWillAppear:(BOOL)animated
{
if (messageTextString != nil)
{
textEdit.text = messageTextString;
}
[super viewWillAppear:animated];
}
(void)viewWillDisappear:(BOOL)animated
{
self.messageTextString = textEdit.text;
[super viewWillDisappear:animated];
}