Aborting, montitoring a download after navigating away, thread issue? - iphone

I am trying to solve my last problem in my app which is the biggest for me.
I have this downloadView shown in the screenshot where I download a file/document from a web server through NSURLConnection asynchronously.
All of the view components work perfectly (download button, progressBarView, abortButton) and so on.
When the download starts, the progress bar increments and I can perfectly abort the download by setting the connection to nil and setting the data length to zero.
:: My challenging problem is that ::
when the download is processed in the background and I click on the "BACK" button and navigate back to the firstView and then navigate back to this downloadView, I lose the access to the download. I can no longer abort it or monitor it. The progress bar resets to zero. However, I can still see the download is going and running through NSLog for progressBar.progress.
I think this problem has something to do with retaining views and accessing threads and keeping a downladView alive when pressing on the "back" button.
Sorry for writing too much but I am trying to clarify this issue well.
here is a basic code to show how I am downloading the file.
-(IBAction)downloadButton:(id)sender{
urlLink= #"http://www.example.com/text.pdf";
NSURLRequest *request= [NSURLRequest requestWithURL:[NSURL URLWithString:urlLink] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
connectionbook = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
// using the regular delegate connectino methods
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{}

The core problem is that when you hit the Back button, the view controller object gets dealloc'd (or it should), and when you revisit that view, you get another view created from scratch.
So you have a couple of options. Have the view which you go back to keep a strong reference, so your view shown here is never really dealloced. Thus you always push the same object. You will need to keeps some state around to deal with the viewWillAppear etc getting called all the time.
The other solution is to have some other persistent object keep the connection and have some way for your view controller showing progress access the information.
Two other comments. You sadi "I can perfectly abort the download by setting the connection to nil and setting the data length to zero", which is not the right way to do it. When you want to stop a connection, you send it [conn cancel], then conn.delegate - nil, then you can release it.

Related

Moving stuff out of appdelegate

I have a tricky situation in my app . i am making the server call in application did finish launching method based on the server response i need to load different views .my app is working fine in simulator but in device the app getting crashed because of the time taking to get the server response .i tried NSThread to run the server call but screen is becoming wait until it gets the response from the server.how can i move the stuff out the app deleagte method
You need to call
[[NSURLConnection alloc]initWithRequest:request delegate:self];
This is asynchronous request, you can implement delegate methods of NSURLConnection in appdelegate.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
A better approach would be to display some temporary view and make the network request asynchronous or do it on a background thread. iOS will kill your app if you block the main thread for too long (this may be what's happening in your case), and users aren't likely to want to use an app that spends ten or twenty seconds doing what looks like nothing before putting up a useful display. When you get a response from the server, you can then reconfigure your display appropriately.
BTW, it'd be a good idea to state your question explicitly next time. It's a bit hard to know what you're asking for as the question stands now.
If you're targeting iOS 4.0 and greater, you can use Grand Central Dispatch to make your network call on a background thread. Here's a simple example.
// Create a dispatch queue
dispatch_queue_t networkQueue = dispatch_queue_create("NetworkQueue", 0);
dispatch_async(networkQueue, ^ {
// Start your network task here.
// Now when it completes we will dispatch back to the main queue
// and you can perform UI updates
dispatch_async(dispatch_get_main_queue(), ^ {
// Perform UI updates here on the main thread
};
// Release your dispatch queue
dispatch_release(networkQueue);
};
Now while this is going on be sure you've already put some preliminary unpopulated UI on screen. You should also show and hide UIApplication's networkActivityIndicator appropriately, and possibly some "Loading..." UI.
NSURLConnection is your friend. The best way to solve it is to do the server call asynchronous using NSURLConnection.
There is Sample Code on the Apple Developer Connection that downloads the images for each row in a UITableView asynchronously so the UI is more responsive. So if you take a look at that code you should be able to refactor your code.
Loading the server response asynchronously prevents your app from being shut down by the iOS runtime because you are blocking the main thread for more than 20 seconds.

UIWebView acts differnetly in app store version than dev version

I've got a problem with an app that works perfectly in the simulator as well as a physical iPhone 4 and an iPhone 3GS. The app was approved and is now in the App Store, but the distribution build downloaded from the App Store exhibits a bug not seen in the dev/release build.
This is a free app, but is supported by local advertising. When the app launches (or returns from background), the AppDelegate attempts to download some HTML from our ad server, and if successful, presents a modal view controller with a UIWebView and passes an NSData variable containing the HTML. In development/release builds, this works PERFECTLY; the app launches, and after a few seconds, a view slides up and shows the ad, which can be dismissed with a button.
However distribution build from the App Store is different. When the modal view controller slides up, the UIWebView never loads. Remember, I present the view controller ONLY if able to download the ad data -- otherwise, the view is never presented.
Thankfully I implemented a timer in the ad view controller which will cause the modal view to dismiss itself if the webViewDidFinishLoad never fires (in which the timer is invalidated), so at least app users aren't too annoyed. But it's still ugly to have an empty view controller slide up and then slide away for apparently no reason.
Here's the relevant methods in the AppDelegate:
- (void)launchAd
{
[NetworkActivity showFor:#"ad"];
if (!alreadyActive && [ServerCheck serverReachable:#"openx.freewave-wifi.com" hideAlert:YES])
{
alreadyActive = YES;
[self performSelectorInBackground:#selector(downloadAdData) withObject:nil];
}
[NetworkActivity hideFor:#"ad"];
}
- (void)downloadAdData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *baseURL = #"http://appdata.freewave-wifi.com/ad/";
NSString *file = (IS_IPAD) ? #"ipad.php" : #"iphone.php";
NSURL *adURL = [NSURL URLWithString:[baseURL stringByAppendingString:file]];
adData = [[NSData alloc] initWithContentsOfURL:adURL];
[self performSelectorOnMainThread:#selector(presentAdModal) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)presentAdModal
{
if (adData)
{
AdViewController *adView = [[AdViewController alloc] initWithNibName:nil bundle:nil];
[adView setAdData:adData];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:adView];
[navController setModalPresentationStyle:UIModalPresentationFormSheet];
[navController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[tabBarController presentModalViewController:navController animated:YES];
[navController release], navController = nil;
[adView release], adView = nil;
}
else
LogError(#"Not presenting ad; unable to create data object.");
}
By the way, adData is defined in header with NSData *adData;
The AdViewController simply contains a UIWebView, which is loaded with
[webView loadData:adData MIMEType:#"text/html" textEncodingName:#"utf-8" baseURL:nil];
Again, this all works PERFECTLY, EVERY TIME with dev/release builds in simulator and physical devices -- just not on distribution build from app store. I have even converted the NSData to an NSString and barfed it out with NSLog() just to prove that the HTML was downloaded before presenting the AdView modally.
[sigh...]
EDIT 1: In case my original post was not clear, the webViewDidFinishLoad never gets called in distribution build (but it does in dev/release build).
EDIT 2: Also, just before I call
[webView loadData:adData MIMEType:#"text/html" textEncodingName:#"utf-8" baseURL:nil];
in the AdViewController, I added a temporary NSLog() and converted adData to NSString and logged it to the console, and the HTML was there. So, the UIWebView just refuses to load the NSData?
HOLY COW. I figure it out.
Okay, before I say what I found, I did want to correct my own original wording: the modal ad has never worked in the simulator, but always on devices. I know the simulator can have its quirks, so I never thought anything of it, especially since it always worked on the devices. I know this is an important detail that was missing for this discussion, but it's been a couple of weeks since I worked on this project, and I'd forgotten all about it until today.
Now then... While tinkering with things, I noticed the AdView.xib was not in my project file list. I expanded a few folders thinking maybe it was accidentally dragged into one of them, but it was not listed at all. This really has me puzzled, though -- Xcode NEVER complained about a missing resource (no warnings or errors; always a perfect compile).
So, I navigated to the physical location and added the AdView.xib into the project. Now, the modal ad is displayed in the simulator, which is a first. I figure that since now the app works correctly in the simulator, it should work fine in the distribution build (odd correlation to make, but it's all I got until my update hits the App Store).
Obviously, I'll be submitting an update, so I won't accept my own answer until after the update hits the App Store (assuming I have actually fixed it).
Ok, this is an extremely long shot, but perhaps worth considering.
The docs for NSData state that with regards to initWithContentsOfURL "The returned object might be different than the original receiver." So, if it was a different object, and one which was in fact autoreleased, consider this line in your code:
adData = [[NSData alloc] initWithContentsOfURL:adURL];
This won't add a retain count for adData -- you didn't write self.adData = or similar. So, bearing in mind the scenario mentioned whereby the returned NSData was autoreleased: your method downloadAdData wraps its content in an NSAutoreleasePool. This is correct practice. However, that might result in adData being released BEFORE presentAdModal is called on the main thread. So...
In presentAdModal you just check that adData isn't nil -- but it can be not nil, and still have been deallocated from memory at that point by your NSAutoreleasePool -- hence, you would in this situation trigger the "show web view" code, but be attempting to load an NSData object that had been trashed. Which probably would contain complete garbage, hence no successful "web view loaded" call.
As I said, a long shot, but the ony thing that jumps out at me at this point.
UPDATE:
A completely different cause of your problem might be this:
Your test environment (i.e. non App-Store builds) is making requests from a certain part of the internet (i.e. your office) which has permission to access the web server containing ads, due to either IP blocking or whatever network setup there is, whereas your App Store release builds are attempting to access the ad server from parts of the internet which are forbidden. Again, probably not the case, but worth mentioning.

objective C Iphone open Google map application direction page url in webview memory warning

HI, WHat am trying to do is load a google direction page directly in webview by passing the lat long values for source and destination (am getting these stored_long lat values that i have previously stored. and am not using google api) everything works fine but the prob is as soon as webview loads the direction page, it starts getting memory warning and on further browsing get crash need a solution to fix these ...am setting webview delegate to nil and also releasing webview also sometimes it crashes wen back btn is pressed ....do need urgent help...guys
NSString* url = [NSString stringWithFormat:#"http://maps.google.com/maps/m?saddr=%f,%f&daddr=%f,%f&view=map&z=13",Stored_lat3,Stored_long3,Stored_lat4,Stored_long4];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
aWebView.delegate = self;
[aWebView loadRequest:request];
Have you tried profiling your application for Allocations using Instruments?
Memory warnings tend to imply your application is using too much memory. It could be that before you even open your web view you are using lots of memory. Opening the webview could be pushing your application over the edge so to speak.
Overriding didReceiveMemoryWarning in your view controllers to clean up memory is also a good tactic to prevent your application from crashing.
I hope this helps!

Cocoa Touch - Display an Activity Indicator while loading a UITabBar View

I have a UITabBar Application with two views that load large amounts of data from the web in their "viewWillAppear" methods. I want to show a progress bar or an activity indicator while this data is being retrieved, to make sure the user knows the app isn't frozen.
I am aware that this has been asked before. I simply need some clarification on what seems to be a rather good solution.
I have implimented the code in the example. The question's original asker later solved their problem, by putting the retrieval of data into another "thread". I understand the concept of threads, but I do not know how I would impliment this.
With research, I have found that I need to move all of my heavy data retrieval into a background thread, as all of the UI updating occurs in the main thread.
If one would be so kind as to provide an example for me, I would be very appreciative. I can provide parts of my existing code as necessary.
If you use NSURLConnection it runs on another thread automatically.
in your viewDidLoad:
NSURLRequest *req = [NSURLRequest requestWithURL:theURL];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:req delegate:self];
then you need some custom methods. If you type in -connection and press Esc you'll see all the different methods you can use. There are three you will need with this:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// this is called when there is a response
// if you're collecting data init your NSMutableData here
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// each time the connection downloads a
// packet of data it gets send here
// so you can do [myData appendData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
// the connection has finished so you can
// do what you want with the data here
}
That is basically all there is to it. NSURLConnection handles all the multithreading itself and you don't have to worry. Now you can create an activity indicator and display it and it will work because the main thread is empty. :)

UIWebView loading progress and adjust web page to fit the view page?

I am using UIWebView to load a web page.
There are 3 questions:
1.It it possible to track the percentage progress when UIWebView is loading the page?
2.I noticed that when Safari loading a web page, the URL textfield displays a blue background progress indicator to tell user the percentage of loading a web page. What is the technology for this?
3.I know there is property scalesPageToFit
scalesPageToFit
A Boolean value determining whether the webpage scales to fit the view and the user can change the scale.
I try to set it to YES, but it looks like that it is not in public API and my app stopped with black screen, I am not sure what is wrong?
To answer #1)
Instead of using a UIWebView, you can pull the webpage down as an NSData object using an NSURLConnection. When you get the initial response from your request from
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
the webserver should return a value of "expected content size" (which should be included in the response). Then you will keep getting the following method called each time you receive data:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
Keep appending the data to an existing NSMutableData object. Then you can check the size of your current data object (NSMutableData.length) against the expected response size.
percentage = (myData.length*100)/theResponse.expectedContentSize;
Then you can update a progress bar with that percentage! When
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
runs, use your data to call
[myWebView loadData:myData MIMEType:myMimeType textEncodingName:myEncoding baseURL:baseURL];
and it will load everything you pulled down into your web view.
Re #3:
You can try specifying scalePagesToFit as a YES in the viewDidLoad event of the UIView that contains your webview, e.g:
- (void)viewDidLoad {
self.webView.scalesPageToFit = YES;
//other code here...
}
For cases where this doesn't work, refer to the following StackOverflow question: UIWebView does not scale content to fit where the asker (and subsequently, answerer) gave this solution.
Apparently you can use javascript to resize your page to fit the browser:
NSString *jsCommand = [NSString stringWithFormat:#"document.body.style.zoom = 1.5;"];
[webLookupView stringByEvaluatingJavaScriptFromString:jsCommand];
You can try to use this subclass of UIWebView which uses private UIWebView methods - therefore, this solution is not 100% AppStore safe (though some apps do almost 100% use it: Facebook, Google app, ...).
https://github.com/petr-inmite/imtwebview
Re #2: I believe Safari uses a private API call on UITextField (so you can't do it if you want to submit to the app store), but you should be able to implement this yourself by putting a textfield with no border over the top of a progress bar.
To answer question #1, there is a solution that is App Store safe, and uses a similar method as Webkit to track progress.
https://github.com/otium/OTMWebView