NSThread for loading pdf files - iphone

I want to load a PDF file in a modal view controller.
Since the moment that I don't know the size of the file, I want to load it on a separate thread, so:
- (void)viewDidLoad {
[super viewDidLoad];
[[UISharedApplication sharedApplication] setNetworkActivityIndicatorVisibile:YES];
[NSThread detachNewThreadSelector:#selector(loadPDF) toTarget:self withObject:nil];
}
- (void)loadPDF {
NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
[threadPool release];
[self performSelectorOnMainThread:#selector(showPDF) withObject:nil waitUntilDone:NO];
}
- (void)showPDF {
[self.webView setHidden:NO];
[self.spinner stopAnimating];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
The method works pretty good in other files (at least I guess) where I parse some XML data, but here doesn't. Commenting:
[self performSelectorOnMainThread:#selector(showPDF) withObject:nil waitUntilDone:NO];
lets me to see the spinner and the activity indicator on the top. So I think that:
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
doesn't wait until finish and gives the control back to the caller. How can I fix it?

You can't/shouldn't (have got it to work once) update the UI on a separate thread, you should to it all on the main. As I believe webView is a UIWebView this is probably the reason.
Although you are changing the webview from hidden to visible on the main thread, all the updating and creating of the actual UIWebView is not.
Best thing to do is create the data from the URL then performSelectorOnMainThread and pass the data to a method on the main thread that updates the UIWebview with the data.

Implement the method webViewDidFinishLoad: of UIWebViewDelegate and call your showPDF there.

Related

Block main thread until UIWebView finishes loading

I'm trying to block the main thread until a UIWebView finishes loading, but calling [NSThread sleepForTimeInterval:] after calling [UIWebView loadRequest:] makes the loading never complete: The webViewDidFinishLoad delegate is called after the sleep finishes.
Why is this happening? and How can I block the main thread?
I'm calling loadPage from [ViewController willRotateToInterfaceOrientation:], what I'm trying to do is to prevent the iOS interface to rotate until the webview with the other orientation is loaded.
- (void)loadPage {
UIWebView* webview2 = [[UIWebView alloc] initWithFrame:rect];
webview2.delegate = self;
NSString* htmlPath = ..... ; // Path to local html file
NSURL *newURL = [[[NSURL alloc] initFileURLWithPath: htmlPath] autorelease];
NSURLRequest *newURLRequest = [[[NSURLRequest alloc] initWithURL: newURL] autorelease];
[webview2 loadRequest:newURLRequest];
[NSThread sleepForTimeInterval:10.0f]; // Why this blocks the uiwebview thread ??
[self.view addSubview:webview2];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"Finished loading!");
}
I think you're trying to solve the wrong problem.
You don't want to block the main thread. Firstly, the entire UI is drawn on the main thread, so it will appear as though your app has hung. Secondly, a UIWebView is part of the UI (which is probably why what you're doing doesn't work).
So, what to do?
Have a look at the following method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
When you start loading your page you can return NO from this method. When it's finished loading you can return values normally. UIWebView has a delegate that tells you the status.
Have a look at the class reference for NSURLRequest, there is a sync method in there.
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error

Iphone: controlling text with delay problem with UIWebView

I've opted to use UIWebView so I can control the layout of text I've got contained in a local html document in my bundle. I want the text to display within a UIWebView I've got contained within my view. So the text isn't the whole view, just part of it.
Everything runs fine, but when the web page loads I get a blank screen for a second before the text appears. This looks really bad. can anyone give me an example of how to stop this happening? I'm assuming I have to somehow hide the web view until it has fully loaded? Could someone one tell me how to do this?
At the moment I'm calling my code through the viewDidLoad like this:
[myUIWebView loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"localwebpage" ofType:#"html"] isDirectory:NO]]];
Any help is much appreciated. I've read round a few forums and not seen a good answer to this question, and it seems like it recurs a lot as an issue for beginners like myself.
Thanks for taking the time to read this post!
UPDATED info
Thanks for your response. The suggestions below solves the problem but creates a new one for me as now when my view loads it is totally hidden until I click on my toggle switch. to understand this it's maybe most helpful if I post all my code. Before this though let me explain the setup of my view. I've got a standard view within which I've also got two web views, one on top of the other. each web view contains different text with different styling. the user flicks between views using a toggle switch, which hides/reveals the web views. I'm using the web views because I want to control the style/layout of the text. Below is my full .m code, I can't figure out where it's going wrong. My web views are called oxford & harvard I'm sure its something to do with how/when I'm hiding/revealing views. I've played around with this but can't seem to get it right. Maybe my approach is wrong. A bit of advice ironing this out would be really appreciated:
#implementation ReferenceViewController
#synthesize oxford;
#synthesize harvard;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
[oxford loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"Oxford" ofType:#"html"] isDirectory:NO]]];
[harvard loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"Harvard" ofType:#"html"] isDirectory:NO]]];
[oxford setHidden:YES];
[harvard setHidden:YES];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if([webView hidden]) {
[oxford setHidden:NO];
[harvard setHidden:NO];
}
}
//Toggle controls for toggle switch in UIView to swap between webviews
- (IBAction)toggleControls:(id)sender {
if ([sender selectedSegmentIndex] == kSwitchesSegmentIndex)
{
oxford.hidden = NO;
harvard.hidden = YES;
}
else
{
oxford.hidden = YES;
harvard.hidden = NO;
}
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[oxford release];
[harvard release];
}
#end
Sure, in your UIViewController's designated initializer, set the hidden property of the UIWebView instance to YES:
[webView setHidden:YES];
Then, implement the UIWebViewDelegate method - (void)webViewDidFinishLoad:, and perform an if check inside the method like this:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if([webView hidden]) {
[webView setHidden:NO];
}
}
Also, don't forget to specify that your class conforms to the UIWebViewDelegate protocol:
#interface YourViewController : UIViewController <UIWebViewDelegate> {...}

NSThread problem on iPhone

I'm using two threads in an iPhone app for the first time and I've run into a problem. In this prototype, I have a view controller that loads a local web page. I want an activity indicator to show until the page has finished loading. With the code below, the activity indicator starts, the page loads properly, but the activity indicator does not stop or hide. It doesn't look like the "loading" function ever gets called.
What am I doing wrong?
- (void)viewDidLoad {
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[NSThread detachNewThreadSelector:#selector(getData) toTarget:self withObject:nil];
[super viewDidLoad];
}
- (void)getData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[detailWebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"page1" ofType:#"html"]isDirectory:NO]]];
[NSTimer scheduledTimerWithTimeInterval: (1.0/2.0) target:self selector:#selector(loading) userInfo:nil repeats:YES];
[pool release];
}
- (void)loading {
if(!detailWebView.loading){
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
}
There's an easier way to do this without creating your own thread.
- (void)viewDidLoad {
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[detailWebView setDelegate:self];
[detailWebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"page1" ofType:#"html"]isDirectory:NO]]];
[super viewDidLoad];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[activityIndicator stopAnimating];
}
You 'd betetr stop the activity indicator in the webview delegate method :
-(void)webViewDidFinishLoad:(UIWebView*)webView
Have you added debug output to loading to whether it gets called ? The code sure looks like that, it should get called after 0.5 seconds and I'd guess that detailWebView is still loading then.
Also, GUI stuff should be run on the main thread, so you may need to do:
- (void)loading {
if(!detailWebView.loading){
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
[activityIndicator performSelectorOnMainThread:#selector(removeFromSuperView) withObject:nil waitUntilDone:YES];
}
}
You're using a webview, which already provides a mechanism to know when a load has finished, and already loads data asynchronously - the use of a custom thread really isn't needed.
Register your object as the UIWebView's delegate. Call loadRequest on the UIWebView and start animating the progress indicator. Stop animating the progress indicator in:
-(void)webViewDidFinishLoad:(UIWebView*)webView
This method is defined by the UIWebViewDelegate protocol - make sure your object implements this protocol. You implement this method:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[activityIndicator stopAnimating];
}
http://developer.apple.com/iphone/library/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html

Activity indicator (spinner) with UIActivityIndicatorView

I have a tableView that loads an XML feed as follows:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([stories count] == 0) {
NSString *path = #"http://myurl.com/file.xml";
[self parseXMLFileAtURL:path];
}
}
I'd like to have the spinner to show on the top bar at the app launch and dissapear once the data is displayed on my tableView.
I thought that putting the beginning at viewDidAppear and the end at -(void)parserDidEndDocument:(NSXMLParser *)parser but it didn't work.
I'd appreciate a good explained solution on how to implement this solution.
Here's the problem: NSXMLParser is a synchronous API. That means that as soon as you call parse on your NSXMLParser, that thread is going to be totally stuck parsing xml, which means no UI updates.
Here's how I usually solve this:
- (void) startThingsUp {
//put the spinner onto the screen
//start the spinner animating
NSString *path = #"http://myurl.com/file.xml";
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
}
- (void) parseXMLFileAtURL:(NSString *)path {
//do stuff
[xmlParser parse];
[self performSelectorOnMainThread:#selector(doneParsing) withObject:nil waitUntilDone:NO];
}
- (void) doneParsing {
//stop the spinner
//remove it from the screen
}
I've used this method many times, and it works beautifully.
Starting a new thread can be overkilling and a source of complexity if you want to do things that are supposed to start on the main thread.
In my own code, I need to start a MailComposer by pushing a button but it can take some time to appear and I want to make sure the UIActivityIndicator is spinning meanwhile.
This is what I do :
-(void)submit_Clicked:(id)event
{
[self.spinner startAnimating];
[self performSelector:#selector(displayComposerSheet) withObject:nil afterDelay:0];
}
It will queue displayComposerSheet instead of executing it straight away. Enough for the spinner to start animating !
I typically implement an NSTimer that will call my spinner method, which I fire off right before I go into doing the heavy work (the work that will typically block the main thread).
The NSTimer fires and my spinner method is called. When the main work is finished, I disable the spinner.
Code for that is like:
IBOutlet UIActiviyIndicatorView *loginIndicator;
{
...
[loginIndicator startAnimating];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(executeAuthenticationRequest)
userInfo:nil repeats:NO];
...
}
- (void) executeAuthenticationRequest
{
/* Simulate doing a network call. */
sleep(3);
[loginIndicator stopAnimating];
...
}
You can also do:
IBOutlet NSProgressIndicator *pIndicator;
Start:
[pIndicator startAnimation:self];
[pIndicator setHidden:NO];
And Stop:
[pIndicator stopAnimation:self];
[pIndicator setHidden:YES];
In Cocoa (and most other app frameworks) the user interface is updated by the main thread. When you manipulate views, they are typically not redrawn until control returns to the run loop and the screen is updated.
Because you are parsing the XML in the main thread, you are not allowing the screen to update, and that is why your activity indicator is not appearing.
You should be able to fix it by doing the following:
In viewDidAppear, show/animate the spinner and then call
[self performSelector:#selector(myXMLParsingMethod) withObject:nil afterDelay:0];
In myXMLParsingMethod, parse your XML, then hide/stop the spinner.
This way, control will return to the run loop before parsing begins, to allow the spinner to begin animating.

UITabBar appearance problem + NSThreads

I'm having a problem when trying to add a UITabBar as a subview of my AppDelegate's window.
The link above shows a screenshot of the messy state of the screen.
TabBarInAMessyState.png
The results are unpredictable. In this picture only the UITabBarItem's titles were affected, but sometimes the TabBar background is not shown (consequently we can see the window's background). Sometimes the NavigationBar is also affected (not show in this picture).
When I start the Application I first have to check if there's network connection, so It is called a method (verifyNetworkAvailability:) that will run in a thread different from the main thread. This is done in order not to freeze the application.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window makeKeyAndVisible];
// check if there's network connection in another thread
[NSThread detachNewThreadSelector: #selector(verifyNetworkAvailability:) toTarget:self withObject:self];
}
- (void) verifyNetworkAvailability:(MyAppDelegate*) appDelegate {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Check if there's network connection..
// If so, call the verifyNetworkAvailabilityDidEnd method
[appDelegate verifyNetworkAvailabilityDidEnd];
[pool release];
}
- (void) verifyNetworkAvailabilityDidEnd {
[window addSubview:tabBarController.view];
}
I'd like to know if it is possible to add the tabBarController.view in this way (by a method call done in thread other than the main thread).
Thanks in advance
Try this
- (void) verifyNetworkAvailability:(MyAppDelegate*) appDelegate {
// other code here ...
[appDelegate performSelectorOnMainThread:#selector(verifyNetworkAvailabilityDidEnd) withObject:nil waitUntilDone:NO];
}
UIKit has some trouble when you try to access it from any thread but the main thread. Think about dispatching a notification to have your primary app thread to add the view rather than adding the view directly in your secondary thread.