I have implemented a subview which is supposed to load immediately when I click a button in the parent view. After loading the subview(which is basically holding an activityindicator), the program is supposed to process a method(which gets data from a server, so takes time) in it.
However, I am unable to do it.
What happens now is, when I click the button on the parent view, it processes the method first and only after that does the subview load on screen.
Why is this so? Is there any specific functions I could use to make my method load only after the view has loaded?
I have faced the same problem and i have used NSAutoreleasePool and solved this problem. I hope it will help you.
- (void)viewDidLoad {
[self performSelectorInBackground:#selector(loadXml) withObject:nil];
}
-(void) loadXml{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString * path =#"http:www.YOUR_RSS_FEED.com";
[self parseXMLFileAtURL:path]; //(Instead of ViewDidAppear)
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
[pool drain];
}
Thanks.
A few different approaches:
If the subview has functionality as loading an image and doing more complicated stuff than just being a view, a good approach is to make it into a ViewController instead.
In the top ViewController use:
- (void) loadView {
[super loadView];
}
to set up things that need to be ready upon displaying the view.
The use:
-(void) viewDidLoad {
}
to do additional setup.
In your case it sounds as if you need to add the indicator in the loadView method, then start the retrieval of data in the viewDidLoad method.
If you are accessing web services etc. always do this on a different thread. (look into NSOperation for a good, simple way of achieving this).
Related
I have a controller that uses an animated UIImageView to display a sequence of 30 512 x 512 frames. When I run the application the view quickly does the following.
- (void)viewDidLoad {
[super viewDidLoad];
[[self imageView] setAnimationImages:[[self dataModel] framesForLOOP]];
[[self imageView] setAnimationDuration:2.5];
[[self imageView] setAnimationRepeatCount:1];
[[self imageView] startAnimating];
NSLog(#"MARKER_001");
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"MARKER_002");
}
This all works fine but what I am trying to work out is that after viewDidLoad: is called there is a 2 second delay before viewDidAppear: is called (between MARKER_001 and MARKER_002).
I was thinking there might be a delay setting up the frames NSArray or after calling setAnimationImages: or maybe after startAnimating.
Is there anyway to reduce/remove this delay, I would prefer to preload the animation at startup and take a hit there rather than having the delay when the viewController loads as it makes the button that fires the segue to instantiate the new controller feel laggy.
Just a few ideas:
reduce the pain - do you need 30x512x512?
distribute the pain - load the first image on viewWillAppear, kick off an operation to load the others and update the animation images as new images are ready (can supply code e.g. if needed)
move the pain - prepare the array of UIImages in app init.
dig deeper - let's have a look at the framesForLoop method, maybe there's some more opportunity to reduce/distribute/move the pain in there.
Note that even if you call startAnimating in viewDidLoad, the animation won't start there. You can't get an animation running in a controller which has not been not displayed yet.
You should call the startAnimating in viewDidAppear instead.
Two seconds delay between these two methods is not anything strange. The delay can be much longer or shorter, depending on what happens inside your application.
Measuring the time between two methods which are not connected doesn't make sense. What about measuring how much time the individual methods take?
If anything is laggy, you should probably paste all the code that happens between the user action and the moment when everything is displayed and the lag happens.
I feel as though there is a really simple solution to my problem, but thus far I have had little success... I want to load my initial .xib file (exiting the default.png splash screen early), so that I may display an activity indicator while loading my html data and setting the text label fields created by my xib file.
Unfortunately, when I execute the following code below, I display my default.png image until all of the data is loaded from each website... What may I change in order to first display my mainView, then start the activity indicator, load my html data, and set the text labels in my mainView?
#implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[activityIndicator startAnimating];
[self runTimer];
}
- (void)viewDidAppearBOOL)animated {
[super viewDidAppear:animated];
[self loadHTMLData1];
[self loadHTMLData2];
[self loadHTMLData3];
[self loadHTMLData4];
[activityIndicator stopAnimating];
}
...
It's all to do with how iOS updates the ui. When you call
[activityIndicator startAnimating];
it doesn't mean start animating immediately, it means you're telling the ui the next time you are updating the display, start animating.
All of this updating happens on the main thread (if you haven't made a thread, you're already on the main thread) so if you do something else that takes a long time, it will do this before updating the display.
There are a few ways to fix this and they all involve making another thread that runs in the background.
Take a look at NSOperation (and NSOperationQueue) - this will let you queue up individual tasks that iOS will run in the background for you. then when they are complete you can update your display again and turn off your activity indicator.
There's NSOperationQueue tutorials all over google :)
Hope that helps.
I know there are lots of similar questions floating around, but none of the answers seem to fix my problem. I have an app that uses an NSURLConnection to download a file, and then does some calculations on the downloaded file. I set up a UILabel to display the current loading status (eg: "Loading file", "Parsing file"). I update the UILabel in the didReceiveResponse and connectionDidFinishLoading function of the NSURLConnection delegate, as well as some other places in my code. I update it by calling the following function:
[self performSelectorOnMainThread:#selector(updateProgress) withObject:nil waitUntilDone:NO]
where -(void)updateProgress is a function I defined to call [theLabel setNeedsDisplay]. I NSLog'd it, like
NSLog(#"theLabel: %#\n",theLabel.text);
and the information is updated correctly, but the label doesn't actually update in the view. Also, updateProgress is only called AFTER everything is loaded. It updates the label THEN, which is hardly useful. Any suggestions?
The NSURLConnection is blocking the main thread (no updates will be performed on the view until it finishes).
You can perform updateProgress in the background:
[self performSelectorInBackground:#selector(updateProgress) withObject:nil]
The first line of updateProgress should be:
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc]init];
The last lines should be:
[pool release];
pool = nil;
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
Of course, you can also perform the NSURLConnection in the background. Then you can update the label on the main thread.
I've been trying for about a day to get a table cell to display an activity indicator while it loads a new view. Upon didSelectRowAtIndexPath I'd like to display the indicator while the following runs
[self.navigationController pushViewController:subKeywordController animated:YES];
This controller then runs a fairly intensive SQL query
I've searched the web and read dozens of posts but none seem to help with my specific problem. I understand that I need to run the indicator in another thread because the push and subsequent load takes precedence but I'm unsure how to do it. I thought about running the SQL query in the controller before but that's getting very messy.
The odd thing is that I've been using MBProgressHUD to display busy cursors in this same table view with no issues. It's only when I apply a search and then select one of the results that causes this error:
bool _WebTryThreadLock(bool), 0x1d79b0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
The app continues on the iPhone but crashes the simulator.
Any help would be greatly appreciated.
The problem is that your task in the controller is holding up the UI code (but you probably already know that!). A cheap and easy way of solving this is to put a tiny delay on starting your slow task (in this case your SQL query) using a timer :
- (void)viewDidLoad {
[super viewDidLoad];
// instead of running the SQL here, run it in a little bit
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(doSQL:) userInfo:nil repeats:NO];
// Show the please wait stuff
[activityIndicator setHidden:NO];
}
- (void)doSQL:(NSTimer *)timer {
// Do your sql here
}
The other way of solving this is to move your SQL into a seperate thread :
- (void)viewDidLoad {
[super viewDidLoad];
// instead of running the SQL here, run it in a little bit
[self performSelectorInBackground:#selector(doSQL) withObject:nil];
// Show the please wait stuff
[activityIndicator setHidden:NO];
}
- (void)doSQL {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your sql here
// Tell the main thread
[self performSelectorOnMainThread:#selector(doneSQL) userInfo:nil waitUntilDone:YES];
// Cleanup
[pool release];
}
- (void)doneSQL {
// Update your UI here
}
Hope that helps!
Maybe someone can help me with this strange thing:
If a user clicks on a button, a new UITableView is pushed to the navigation controller. This new view is doing some database querying which takes some time. Therefore I wanted to do the loading in background.
What works WITHOUT leaking memory (but freezes the screen until everything is done):
WorkController *tmp=[[WorkController alloc] initWithStyle:UITableViewStyleGrouped];
self.workController=tmp;
[tmp release];
[self.workController loadList]; // Does the DB Query
[self.workController pushViewController:self.workController animated:YES];
Now I tried to do this:
// Show Wait indicator
....
WorkController *tmp=[[WorkController alloc] initWithStyle:UITableViewStyleGrouped];
self.workController=tmp;
[tmp release];
[self performSelectorInBackground:#selector(getController) withObject:nil];
}
-(void) getController {
[self.workController loadList]; // Does the DB Query
[self.navigationController pushViewController:self.workController animated:YES];
}
This also works but is leaking memory and I don't know why !
Can you help ?
By the way - is it possible for an App to get into AppStore with a small memory leak ? Or will this be checked first of all ?
Thanks in advance !
No, small memory leaks will not (most likely) you application to be rejected from appstore.
In your example as you run your method in separate thread you should create and dispose NSAutoreleasePool object for that thread to handle autoreleased objects. Following changes to getController method should do the trick:
-(void) getController {
NSAutoreleasedPool *pool = [[NSAutoreleasedPool alloc] init];
[self.workController loadList]; // Does the DB Query
[self.navigationController pushViewController:self.workController animated:YES];
[pool release];
}
For more details see Autorelease Pools section in memory management guide. Relevant quote from there:
If you spawn a secondary thread, you
must create your own autorelease pool
as soon as the thread begins
executing; otherwise, you will leak
objects. (See “Autorelease Pools and
Threads” for details.)
Btw, you're calling pushViewController: from a background thread. This is bad.
You should only do things to the UI - like pushing view controllers and changing UI items - from the main thread. If you don't, things break.
See the Cocoa Fundamentals Guide section titled "Are the Cocoa Frameworks Thread Safe?": it says "All UIKit objects should be used on the main thread only."