I was following a sample code from Apple here: http://developer.apple.com/library/ios/#samplecode/TableSearch/Listings/AppDelegate_m.html#//apple_ref/doc/uid/DTS40007848-AppDelegate_m-DontLinkElementID_4
It is an example showing how to use SearchDisplayController to do search on a table.
The codes related to my question are:
in viewDidLoad -
- (void)viewDidLoad
{
self.title = #"Products";
// create a filtered list that will contain products for the search results table.
self.filteredListContent = [NSMutableArray arrayWithCapacity:[self.listContent count]];
// restore search settings if they were saved in didReceiveMemoryWarning.
if (self.savedSearchTerm)
{
[self.searchDisplayController setActive:self.searchWasActive];
[self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
[self.searchDisplayController.searchBar setText:savedSearchTerm];
self.savedSearchTerm = nil;
}
[self.tableView reloadData];
self.tableView.scrollEnabled = YES;
in viewDidDisappear -
-(void)viewDidDisappear:(BOOL)animated
{
// save the state of the search UI so that it can be restored if the view is re-created
self.searchWasActive = [self.searchDisplayController isActive];
self.savedSearchTerm = [self.searchDisplayController.searchBar text];
self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];
}
So, it says that it is saving the state of the search UI so that it can be restored if the view is recreated.
It also says that "it restore search settings if they were saved in didReceiveMemoryWarning."
So my understanding is, when memory is low, this view might be unloaded. It might be recreated again when user click on that tab to view that view.
So I want to simulate the low memory situation where my view gets unloaded, so that I can confirm that the logic where search terms get restored is working. But I failed.
I put a trace in viewDidLoad; it never fires more than one time, i.e., the view was never unloaded
I used the iPhone Simulator - simulate memory warning - the view never gets unloaded also.
To summary my question:
Will a loaded view in a tab bar contoller gets unloaded by iOS because it faces low memory?
If the answer to the above question is Yes, then does the iOS Simulator able to reproduce that?
I'm can't be positive about this answer but in my experience, it works this way.
If your UIViewController is registered as one of your UITabBarController's viewControllers and if the UITabBarController is the rootViewController of your UIWindow, the memory warning should be relayed to your UIViewController. If not I believe there a missing transition. To find out where, you could NSLog every UIViewController from the rootViewController to the UIViewController you want, and see where it stops.
Now, if you want to test the didReceiveMemoryWarning of your UIViewController, you can just call the method yourself, when another View is loaded or by using a timer.
Related
I implemented the LazyTableImages project (link) by Apple, but in my version I used RestKit to obtain the data and my UItableviewcontroller was push onto navigation stack.
So I eschew whatever apple does in the app delegate to get the xml. I dont think that's the problem. My problem is that when you back out of the UITableviewcontroller using either the nav back button or accessing another tabbar item and coming back, the images that were loaded there previously show up, but immediately it loads the placeholder image. Basically, the opposite happens.
It's like the UITableview cached data, so when you come back it interferes with the Lazy Table Images. I need to know has anyone implemented this code where they had to back out?
EDIT:
Looks like imageDownloader is not nil the second time, which prevents the image from loading. I'm still figuring out how to bypass it. Of course, I can just take out the condition, but I dont know if that is "bad" for performance.
imageDownloadsInProgress, a mutable dictionary, still has all of its data even if you back out. It has become a different question now, how do I delete imageDownloadsInProgress if a user hits back or strays from the current view.
imageDownloadsInProgress is retained, but I added [imagesDownloadsInProgress release] in the dealloc method, however I don't think that runs.
-(void)startEventImageDownload:(WhatsonEvent *)eventRecord forIndexPath:(NSIndexPath *)indexPath
{
EventImageDownloader *imageDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if(imageDownloader == nil)
{
NSLog(#"%#",eventRecord.title);
imageDownloader = [[EventImageDownloader alloc] init];
imageDownloader.eventRecord = eventRecord;
imageDownloader.indexPathInTableView = indexPath;
imageDownloader.delegate = self;
[imageDownloadsInProgress setObject:imageDownloader forKey:indexPath];
[imageDownloader startDownload];
[imageDownloader release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
}
The way I do it is to build my own cache and save the images in the user documents directory. When I call [tableView reloadData] (you are calling that, right?) it first checks for each cell if the image is there locally, otherwise it will lazily load them from the feed. Tell me if you need code for this.
The problem was that the self.imageDownloadsInProgess = [NSMutableDictionary dictionary] was placed in the ViewDidLoad method with the intention of resetting the dictionary every time. However, if you place the code within a view pushed onto a navigation controller, the ViewDidLoad only executes the first time (I'm not positive that is the case). I added the line to ViewWillAppear since it runs every time the view is placed on screen.
I am getting an EXC_BAD_ADDRESS crash when selecting a table's cell which should push a new view on the navigation controller.
Here is the stack trace (the CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION_ is always there):
alt text http://dl.dropbox.com/u/71415/crash_threads.jpg
I strongly suspect the new code I have added to load the initial data the app needs in a separate thread, partly because the init and loadView of the new view controller are being called and return fine. I am doing a [NSThread detachNewThreadSelector:#selector(loadData) toTarget:self withObject:nil]; in applicationDidFinishLaunching and then load a view showing a progress indicator. loadData does a [self performSelectorOnMainThread:#selector(setupMainUI) withObject:nil waitUntilDone:NO]; when data is ready. The UI (table and all) is loaded fine, the fresh data shows up great; it's only when a new view has to be pushed that the crash happens. Switching views via tab controller works fine as well.
Ideas?
Thanks.
Update:
This is what I am doing to load the new view controller:
NSArray *arrayForSection = [filteredGobos objectAtIndex:indexPath.section];
Employee *selectedEmployee = [arrayForSection objectAtIndex:indexPath.row];
if (self.employeeVC == nil) {
EmployeeVC *emplVC = [[EmployeeVC alloc] initWithEmployee:selectedEmployee];
self.employeeVC = emplVC;
}
[self.navigationController pushViewController:employeeVC animated:YES];
You have overreleased an object. Your app is signaled when trying to release the autorelease pool. That means something in the pool already was released and dealloced before. Try Build&Analyse or NSZombies to find the problem.
I don't think it has to do with your threading, as want you mentioned there looks right to me.
I wrote simple code to test UIImagePickerController:
#implementation ProfileEditViewController
- (void)viewDidLoad {
[super viewDidLoad];
photoTaker_ = [[UIImagePickerController alloc] init];
photoTaker_.delegate = self;
photoTaker_.sourceType = UIImagePickerControllerSourceTypeCamera;
photoTaker_.showsCameraControls = NO;
}
- (void)viewDidAppear: (BOOL)animated {
[self presentModalViewController: photoTaker_ animated: NO];
}
#end
And I'm getting strange warnings like the following:
2010-05-20 17:53:13.838 TestProj[2814:307] Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
2010-05-20 17:53:13.849 TestProj[2814:307] Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
Got any idea what this is about? Thanks a lot in advance!
This message will appear if you are presenting the UIImagePickerController within another UIViewController. Because it isn't pushed like a UINavigationController stack, there is confusion at the UIWindow level. I don't know if the warning is a problem, but to eliminate the warning you can do the following:
// self = a UIViewController
//
- (void) showCamera
{
cameraView = [[UIImagePickerController alloc] init];
[[[UIApplication sharedApplication] keyWindow] setRootViewController:cameraView];
[self presentModalViewController:cameraView animated:NO];
}
- (void) removeCamera
{
[[[UIApplication sharedApplication] keyWindow] setRootViewController:self];
[self dismissModalViewControllerAnimated:NO];
[cameraView release];
}
Perhaps you are adding the root UIViewController's view as a subview of the window instead of assigning the view controller to the window's rootController property?
IT ALL FALLS BACK ON THE UI
This warning can be implemented for several different objects: Pickers, keyboard, etc.
I have found that it is related to the UI taking two steps to complete a transition or other animation. Or for any instance where the UI is trying to finish one thing and is being asked to execute another before it has finished. (therefore it covers a wide range of possible triggers)
I have seen the warning appear on 4.0 & 4.2. In my case I was working with rotating the device and catching whether the keyboard was still up-(i.e. text field was still first responder). If so, the keyboard needed to stay up for between the views, but this presented other complications with other views.
Therefore, I implemented a BOOL tracker to keep track if keybaordIsPresent, and if so I was {textfield resignFirstResponder]; when detecting the orientation change and the reset the textfield to becomeFristResponder after the transition that was wrapped in an Animation Block. My BOOL tracker worked better, I still use the NSNotifications for the Keyboard, but there were overlaps of notifications during rotations because the keyboard was being dismissed without requesting such. The BOOL is set to NO on Load, and when the [textfield resignFirstResponder]; is implemented. *not when "-(void)keyboardWillhide is trigger by the NSNotifications, which gives me both working triggers that never conflict. The BOOL is set to YES, only when the user touches textfield which automatically triggers becomeFirstResponder.
I removed the warning by taking the [textfild resignFirstResponder]; out of the
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}
}
and placing it back at the top of the code for the:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (keyboardIsPresent == YES) {
[self.entryTextField resignFirstResponder];
}
//Determine Which Orientation is Present:
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) || (fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
//LANDSCAPE VIEW:
[self configureLandscapeView];
}else {
//PORTRAIT VIEW:
[self configurePortraitView];
}
}
**Even though I had no code inside the -(void)willAnimatFirstHalfOfRotationToInterface:, the warning was still popping up. I think the warning was still popping up because the compiler still has to attempt the method while it is trying to execute the first animation and therefore gets the double animation call or overlap of resources. It doesn't know that there is no executable code with the method until after it runs through it. And by that time it already set aside resource in preparation for handling possible actions within the method.
**To ellimiate the warning I had to remove or nil out the code for the willAnimateFirstHalfOfRotation, so that the compiler does not have to even check to see if there is a possible 2nd animation or action that may need to be executed at the same time.
/*-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}}*/
After the transition is completed, within the original animation block I check to see if the "keyboardIsPresent" was YES prior to the rotation, and if so, I resign the First Responder once again. I use setAnimationDuration:0.3which comes out pretty clean and not jumpy.
Well, you are presenting UIImagePickerController modally inside viewDidAppear of ProfileEditViewController.
Think about this. That means when ProfileEditViewController view appears, the UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and.... you get the idea.
That warning is rather cryptic though, not sure if that is what it's trying to tell you. I would suggest making a button somewhere on the ProfileEditViewController that calls presentModalViewController and also make sure you have a way to dismiss the UIImagePickerController (I've never used it not sure if it has one automatically).
You may be trying to present two modal view controllers at the same time, and they are fighting for animation resources.
1) There is rarely any UI reason to do this. You could instead just go directly to the second view controller (the image picker); and, after dismissing it, then present the first view or view controller.
2) If you do want two stacked view controllers or a view controller on top of a view, then set a timer in viewDidAppear to present the second view controller after the first one has finished it's animation. (You could display a dummy png image of a blank picker in the first one to prevent too much display flashing until the second view controller goes live.)
EDIT - Added a random code example:
I might try substituting this as an experiment:
- (void)foo {
[self presentModalViewController: photoTaker_ animated: NO];
}
- (void)viewDidAppear: (BOOL)animated {
NSTimer *bar = [ NSTimer scheduledTimerWithTimeInterval: (2.0f)
target: self
selector: #selector(foo)
userInfo: nil
repeats:NO ];
}
A shorter time delay may work as well.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,
I think the warning here is about Core Animation performance. As a test, I loaded the image picker without any action sheet or other animations going on and the warnings are still there. I think these are warnings coming from the image picker class itself and not from any misuse of the API.
I m Copying my code here below :-
-(IBAction)referencewindow:(id)sender
{
frmReferences *reference = [[frmReferences alloc]initWithNibName:#"frmReferences" bundle:nil];
[self presentModalViewController:reference animated:YES];
}
There are number of places I m using presentModelViewController and my problem is that stack shows memory leakage due to presentmodelviewcontroller.
when we use presentModelViewController to call other nib as above stated then it just override to the previous view but previous view is still in process thats why memory lekage problem is occuring so please tell me when i call other nib file using presentModelViewController then how to unload that previous view from memory while switch to other view and then on other view to next view.
You must release your viewController after call the presentModalViewController method like here:
- (IBAction)referencewindow:(id)sender {
frmReferences *reference = [[frmReferences alloc]initWithNibName:#"frmReferences" bundle:nil];
[self presentModalViewController:reference animated:YES];
[reference release];
}
There is more information here: Modal View Controllers
Your building a navigation hierarchy using present modal view? It could work but you need to released it sometime.
I think if you tried using normal memory management rules it would work out. Try going back from your views and they should be released, you'd see the memory go down in instruments.
I have a view containing a UIWebView which is loading a google map (so lots of javascript etc). The problem I have is that if the user hits the 'back' button on the nav bar before the web view has finished loading, it is not clear to me how to tidily tell the web view to stop loading and then release it, without getting messages sent to the deallocated instance. I'm also not sure that a web view likes its container view disappearing before it's done (but I've no choice if the user hits the back button before it's loaded).
In my viewWillDisappear handler I have this
map.delegate=nil;
[self.map stopLoading];
this seems to handle most cases OK, as nil'ing the delegate stops it sending the didFailLoadWithError to my view controller. However if I release the web view in my view's dealloc method, sometimes (intermittently) I will still get a message sent to the deallocated instance, which seems to be related to the javascript running in the actual page, e.g.:
-[UIWebView webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:]: message sent to deallocated instance 0x4469ee0
If I simply don't release the webview, then I don't get these messages though I guess I'm then leaking the webview.
If I don't send the 'stopLoading' message, and simply release the webview within viewWillDisappear, then I see messages like this:
/SourceCache/WebCore/WebCore-351.9.42/wak/WKWindow.c:250 WKWindowIsSuspendedWindow: NULL window.
Possibly related, I sometimes (again totally intermittent) get an ugly heisenbug where clicking the back button on some other view's navbar will pop the title, but not the view. In other words I get left with the title of view n on the stack, but the view showing is still view n+1 (the result is you're trapped on this screen and cannot get back to the root view - you can go the other direction, i.e. push more views and pop back to the view that didn't pop corrrectly, just not to the root view. The only way out is to quit the app). At other times the same sequence of pushes and pops on the same views works fine.
This particular one is driving me nuts. I think it may be related to the view disappearing before the web view is loaded, i.e. in this case I suspect it may scribble on memory and confuse the view stack. Or, this could be completely unrelated and a bug somewhere else (i've never been able to reproduce it in debug build mode, it only happens with release build settings when I can't watch it with gdb :-). From my debug runs, I don't think I'm over-releasing anything. And I only seem to be able to trigger it if at some point I have hit the view that has the web view, and it doesn't happen immediately after that.
A variation on this should fix both the leaking and zombie issues:
- (void)loadRequest:(NSURLRequest *)request
{
[self retain];
if ([webView isLoading])
[webView stopLoading];
[webView loadRequest:request];
[self release];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
[self retain];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self release];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[self release];
}
- (void)viewWillDisappear
{
if ([webView isLoading])
[webView stopLoading];
}
- (void)dealloc
{
[webView setDelegate:nil];
[webView release];
[super dealloc];
}
There's a few ways to handle it, but this should work. You want the didFailLoadWithError message, it's what tells you it's stopped.
Set a flag isLeaving=YES;
Send the Webview a stopLoading.
In didFailLoadWithError:, check for the error you get when
the webview stops:
if ((thiserror.code == NSURLErrorCancelled) && (isLeaving==YES)) {
[otherClass performSelector:#selector(shootWebview) withObject:nil withDelay:0]
}
release the webView in shootWebview:
variations:
if you want to be cavalier about it, you can do the performSelector:withObject:withDelay: with a delay of [fillintheblank], call it 10-30 seconds without the check and you'll almost certainly get away with it, though I don't recommend it.
You can have the didFailLoadWithError set a flag and clean it up somewhere else.
or my favorite, maybe you don't need to dealloc it all when you leave. Won't you ever display that view container again? why not keep it around reuse it?
Your debug being different from release issue, you might want to check your configuration to make sure that it's exactly the same. Bounty was on the reproducible part of the question, right? ;-).
--
Oh wait a second, you might be taking a whole View container down with the WebView. You can do a variation on the above and wait to release the whole container in shootWebView.
The UINavigationController bug you're describing in the second part of your post might be related to your handling of memory warnings. I've experienced this phenomenon and I"ve been able to reproduce it on view n in the stack by simulating a memory warning while viewing view (n+1) in the stack.
UIWebView is a memory eater, so getting memory warnings wouldn't be surprising when it's used as part of a view hierarchy.
A simple release message in dealloc ought to be enough.
Your second problem sounds like a prematurely deallocated view, but I can't say much without seeing some code.
I had a similar problem to this using a UIWebView in OS3 - this description was a good starting point, however I found than simply nil'ing out the web view delegate before releasing the webView solved my problem.
Reading the sample code (the accepted answer - above) - it seems like a lot of overkill. E.g. [webView release] and webView = nil lines do exactly the same thing given the way the author describes the variable is declared (so you don't need both). I'm also not fully convinced by all the retain and release lines either - but I guess your mileage will vary.
Possibly related, I sometimes (again
totally intermittent) get an ugly
heisenbug where clicking the back
button on some other view's navbar
will pop the title, but not the view.
In other words I get left with the
title of view n on the stack, but the
view showing is still view n+1 (the
result is you're trapped on this
screen and cannot get back to the root
view - you can go the other direction,
i.e. push more views and pop back to
the view that didn't pop corrrectly,
just not to the root view. The only
way out is to quit the app). At other
times the same sequence of pushes and
pops on the same views works fine.
I have the same problem, when I'm use navigation controller with view controllers in stack > 2 and current view controller index > 2, if an memoryWarning occurs in this momens, it raises the same problems.
There is inly 1 solution, which I found after many experiments with overriding pop and push methods in NavigationController, with the stack of view controllers, with views and superviews for stacked ViewControllers, etc.
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface FixedNavigationController :
UINavigationController <UINavigationControllerDelegate>{
}
#end
#import "FixedNavigationController.h"
static BOOL bugDetected = NO;
#implementation FixedNavigationController
- (void)viewDidLoad{
[self setDelegate:self];
}
- (void)didReceiveMemoryWarning{
// FIX navigationController & memory warning bug
if([self.viewControllers count] > 2)
bugDetected = YES;
}
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
// FIX navigationController & memory warning bug
if(bugDetected){
bugDetected = NO;
if(viewController == [self.viewControllers objectAtIndex:1]){
[self popToRootViewControllerAnimated:NO];
self.viewControllers = [self.viewControllers arrayByAddingObject:viewController];
}
}
}
#end
It works fine for 3 view controllers in stack.