iPhone app crashes with no justifiable reason? - iphone

I am developing an app in which I have a table. In the table cell I have an imageview ( the images are displayed via url ) and a textview/webview. I start threads for each row to get the images in the
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
method ( if image is not already got ) and set the text of textview/webview from an array.
The problem arises when the images are being received and I pop the view, the application crashes giving the following message:
bool _WebTryThreadLock(bool), 0x1a0670: 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...
Now the situation becomes more weird if I don't release the textview/webview that I added to the cell, then every thing works fine.
Edit: the crash does not happens when I replace textview/webview with a label
Hoping I am clear in my question. If any thing is confusing please comment. I need to resolve this.
Thanks,
Nikhil

That's a huge mistake to use threads. Try to avoid using threads if you have other solutions !
In your case, just use an asynchronous NSURLConnection which will take care of downloading your image while not slowing your app ;)
Here is part of the code :
- (void) startDownload {
self.activeDownload = [NSMutableData data];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:#"blablabla"]] delegate:self];
self.imageConnection = conn;
[conn release];
}
#pragma mark -
#pragma mark Download support (NSURLConnectionDelegate)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.activeDownload appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"ERROR DOWNLOADING");
// Clear the activeDownload property to allow later attempts
self.activeDownload = nil;
// Release the connection now that it's finished
self.imageConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"FINISH DOWNLOAD");
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
self.activeDownload = nil;
self.imageConnection = nil;
//do whatever you want with your image
[image release];
}

Related

NSURLConnection doesn't receive data when creating many downloading objects in iOS5

I have been searching for this problem on the SOF for several days and I still have not found the solution (say the same problem) yet.
I'm making and app that downloads 5 images simultaneously in an URL list (each image is on a different server).
I have an ImageDownloader class subclasses NSOperation and implements the NSURLConnectionDataDelegate.
So that I can add an instance of ImageDownloader to an operationQueue in the ViewController and it will run in a separate thread under the operationQueue. The line that add the downloader to the operationQueue is here:
downloader = [[ImageDownloader alloc] init];
[downloader downloadImageWithURL:[controller.URList objectForKey:[NSString stringWithFormat:#"%d",downloadIndex]] queue:queue andTag:downloadIndex + 100]; //my custom initialize
downloader.delegate = self;
[queue addOperation:downloader]; //use the addOperation method
Everything works fine in iOS6 but messed up in iOS5 (5.0 on my test device and 5.1 on my SDK), it just doesn't receive any response nor data by performing the methods didReceiveResponse and didReceiveData at all (these 2 methods are not jumped in).
After the timeout was exceeded, the runloop jumps into didFailWithError method and the program stalls.
As I understand, this means the runloop still runs right?
I tried to print out the error and all I got is: The request timed out.
When I reduce the number of downloading instances to 2 then it runs, but not with >=3 downloading instances.
One more information is that my network connection does limit the number of connection. But it work fine in iOS6, why it just doesn't work on iOS5?
I can still load the web in the simulator while the app is downloading.
So what kind of problem is this and how can I get over this problem?
Thanks in advance.
*Update:* as there are many classes and the problem's not been clearly detected yet, I will share here the whole project. You can download it directly from here:
DownloadingImage
As I just found out, if you're using credentials there is a chance that the server will reject them randomly every once in a while. So if you have a check to make sure previousFailureCount == 0 then you will most likely have a bug.
I've just figured out where my problem is, but not really understand why.
In my ImageDownloader class, I set up a runloop with done and currentRunLoop variables.
In the main method, I have a while loop for forcing the currentRunLoop run.
As I remove those "runLoop" stuffs, the app runs smoothly on both iOS6 and iOS5.
So change the entire ImageDownloader.m with these lines then it works (I commented out some useless (say harmful) lines):
//
// ImageLoader.m
// DownloadImagesTableView
//
// Created by Viet Ta Quoc on 6/25/13.
// Copyright (c) 2013 Viet Ta Quoc. All rights reserved.
//
#import "ImageDownloader.h"
#implementation ImageDownloader
#synthesize downloadData,delegate,queue,done,customTag;
NSRunLoop *currentRunLoop;
-(void)downloadImageWithURL:(NSString *)imageUrl queue:(NSOperationQueue*)opQueue andTag:(int)tag{
self.customTag= tag;
self.queue = opQueue;
// self.done = NO;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageUrl] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection start];
// currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(#"Start downloading image %d...",customTag);
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"Received response...");
downloadData=[[NSMutableData alloc] initWithLength:0];
expectedDataLength=[response expectedContentLength];
NSLog(#"Image %d size: %lld kb",customTag,[response expectedContentLength]/1024);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
float receivedLenght = [data length];
receivedDataLength=(receivedDataLength+receivedLenght);
float progress=(float)receivedDataLength/(float)expectedDataLength;
[delegate updateProgess:progress andIndex:[NSIndexPath indexPathForRow:customTag-100 inSection:0]];
[self.downloadData appendData:data];
// NSLog(#"Percentage of data received of tag %d: %f %%",self.customTag,progress*100);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
[delegate finishedDownloadingImage:downloadData andTag:customTag];
// done = YES;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Warning" message:#"Network Connection Failed?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:nil, nil];
// NSLog(#"%#",[error debugDescription]);
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription],[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
[alert show];
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
NSLog(#"Got here *(*&(**&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&");
}
-(void)main{
// do{
//// NSLog(#"Running....1");
// [currentRunLoop runUntilDate:[NSDate distantFuture]];
// // [currentRunLoop run];
// } while (!done);
// [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
#end
Thank you guys for your supports.
==================================================================================
P/s: for anyone who interested in this problem, I update here my entire solution: DownloadImage_Final

iOS image resolution issue

In My project I am using image view which downloads image from server. It is working fine on iOS 4 but it is not showing on iOS 5.
Is there any minimum resolution needs to be take care while using iOS 5. One of image which comes from server is of 72 dpi resolution which works on iOS 4 but not on iOS 5.
I have written category to image view which will download code from image URL
Here is code snippet:
- (void) setImageFromServer:(NSString *) imageURL
{
if (imageURL!=nil)
{
ImageDownloader *imageDownloader = [[[ImageDownloader alloc] init] autorelease];
imageDownloader.requester = self;
[imageDownloader startDownload:imageURL];
}
}
- (void) didDownloadImageData:(NSData *) data forImageURL:(NSString *) imageURL
{
[self setImage:[UIImage imageWithData:data]];
}
In downloader file :
- (void) startDownload:(NSString *)MyimageURL {
self.imageData = [NSMutableData data];
self.currentImageURL = MyimageURL;
self.downloadConnection = [NSURLConnection connectionWithRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:self.currentImageURL]]
delegate: self];
[self.downloadConnection start];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[imageData appendData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.requester didDownloadImageData:self.imageData forImageURL:self.currentImageURL];
isRewardTagImageAvailable = YES;
[connection release];
connection = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
}
I am not sure if this is a problem, but usually when I am writing a NSURLRequest I first initialize things as follows and the insert the request into a NSURLConnection. Sort of like this. Also note that once you initWithRequst in a NSURLConnection, you do not have to tell that connection to start, it will automatically.
NSURLRequest *tempReq = [[NSURLRequest alloc] initWithURL:someURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSURLConnection *tempCon = [[NSURLConnection alloc] initWithRequest:tempReq delegate:self];
I mean it should not matter, but give that a shot because looking at your, code it looks fine.
I would recommend adding the didFailWithError method:
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
To your files as well because maybe your connection is failing for some reason as well.
Is your connection synchronous or async ?
If async, you shouldn't autorelease your ImageDownloader, because there are some chance that it would be released for the connectionDidFinishLoading message.
Try to alloc your ImageDownloader normally, then release it when it finishes the download (both in connectionDidFinishLoading and didFailWithError)

iphone uiwebview authentication challenge keeps firing when signed in

i have recently implemented authentication challenge log in through the iPhones UIWebView. i've got it working to the point where i get challenged then i present an alert with text fields, then send the data to the site that needs authentication.
i have not yet tried to use this on anything else besides my netgear router. But my problem is when i navigate through the settings for the router, the authentication challenge gets called, thus presenting the alert even though the user is logged in.
below is the code i'm using, any advice would be grately appreciated
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#"Did start loading: %# auth:%d", [[request URL] absoluteString], _authed);
myRequest=[request copy];
if (_authed) {
NSLog(#"_authed");
_authed = NO;
// pretty sure i'm leaking here, leave me alone... i just happen to leak sometimes
[[NSURLConnection alloc] initWithRequest:request delegate:self];
return YES;
}
[[NSURLConnection alloc] initWithRequest:request delegate:self];
return YES;}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
NSLog(#"protection space %#", protectionSpace.authenticationMethod);
//if(![protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault]){
return NO;
//}
//else{
// return YES;
//}
//[protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic];}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;{NSLog(#"received response via nsurlconnection %#", connection);
NSLog(#"got auth challange %#", challenge);
UIApplication* app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible = NO;
/*NSString *aarrgghh=[NSString stringWithFormat:#"%#",connection];
NSString *searchForMe = #"login";
NSLog (#"arrgghhh %#",aarrgghh);
NSRange range = [aarrgghh rangeOfString:searchForMe];*/
if ([challenge previousFailureCount] <=1) {
//present alert with text fields for credentials
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}}
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
NSLog(#"Challenge cancelled");}
//`-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(#"received data");
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;{
NSLog(#"received response via nsurlconnection %#", response);
// THIS IS WHERE YOU SET MAKE THE NEW REQUEST TO UIWebView, which will use the new saved auth info
if(_authed){
//NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:self.webView.request.URL.absoluteString]];
[webView loadRequest:myRequest];
}
}
`
Might be a simpler way to do this, but this is what worked for me.
First off, when shouldStartLoadWithRequest returns YES, that tells UIWebView to create NSURLConnections and run them for you . Since you can't assign a delegate to this connection, that's not going to work. If you want to handle authentication via a NSURLConnectionDelegate, then shouldStartLoadWithRequest should always return NO for that UIWebView.
So you need to handle the connection yourself. Fire off an NSURLConnection with the request and use the rest of the NSURLConnection delegate methods to handle the loading (e.g. keep track of the MIME type and build up an NSMutableData)
Finally, when you get to connectionDidFinishLoading, you can call UIWebView's loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL with the NSData your connection downloaded.

Why NSURLConnection blocks my UI?

I've been reading several threads and questions about this issue but I didn't find the solution.
I have some asynchronous calls performed with
[NSURLConnection connectionWithRequest:anURLRequest delegate:self];
The problem is that I want the interface to be operative but it is blocked until the connection is finished.
Is this solved launching another thread? Where is exactly the problem?
EDIT
Ok, after retrieve data I parse it with NSXMLParser, that do it synchronously and blocks main thread. Is this correct? Then, maybe I need to parse in another thread. Anyone has a guide?
From the docs:
Messages to the delegate will be sent on the thread that calls this method. For the connection to work correctly the calling thread’s run loop must be operating in the default run loop mode.
Are you sure that this code is being called on a run loop in default mode and not from a thread created by yourself with a different type of run loop mode?
The UI should not be locking up when you use connectionWithRequest. Try creating a label in your UI and have your connection update it with the current amount of data, like so:
- (void)downloadContentFromUrl:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
receivedData = [[NSMutableData data] retain];
self.downloadProgressLabel.text = #"Downloading...";
} else {
// oh noes!
}
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[receivedData setLength:0];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
int kb = [receivedData length] / 1024;
self.downloadProgressLabel.text = [NSString stringWithFormat:#"Downloaded\n%d kB", kb];
}
connectionWithRequest does indeed run in it's own thread - no need for you to worry about this. In fact it must be started from the main thread. Check out the NSUrlConnection doc for more info.
+ (id)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate];
This method should create an asynchronous-request (that means that it runs in the background and it doesn't block the UI). You should check if there's another class/method in your file that blocks the UI (for example NSData's '+ (NSData *)dataWithContentsOfURL:(NSURL *)URL').
I don't know if it could help anyone, but I've the same problem (asynchronous URL request blocking the UI) but it was due to:
NSLog(#"dataReceived: %#", data);
in the connectionDidReceiveData method.
In my case I was trying to update the UIProgressView.progress property. I calculated the new value like that
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.progress setProgress:self.downloadedData.length / self.fileSize ];
[self.downloadedData appendData:data];
}
Which doesn't work, I replaced this snippet of code with
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
self.downloadedSize += data.length;
[self.progress setProgress:self.downloadedSize / self.fileSize ];
[self.downloadedData appendData:data];
}
And now the progress view updates with no problem.

UIWebView - stringByEvaluatingJavaScriptFromString - not changing text box value

Why doesn't this code work? It shows the Google screen but it doesn't change the text box value. I confirmed that the JS does work by running it in Safari, and this code seems to work otherwise since running alert('hi') does work.
NSURL *web_url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *web_request = [NSURLRequest requestWithURL:web_url];
[web_screen loadRequest:web_request];
NSString *js_result = [web_screen stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('input')[1].value='test';"];
Just expanding on the previous answer. You need to conform to the UIWebViewDelegate protocol by setting the delegate property of the UIWebView like this:
web_screen.delegate = self;
Then you can implement one of the delegate methods to know when a request has finished loading and is therefore ready to have scripts run like so:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *js_result = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('input')[1].value='test';"];
}
For more information on the UIWebViewDelegate protocol visit the Apple site http://developer.apple.com/library/ios/#documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html
The Load URL action takes place asynchronously. It does not even start until your method has ended. So your javascript is being pushed into an empty UIWebView, then your method ends, then the load happens.
You need to let your method end before the js is pushed in. The standard approach for this is to use a Delegate object, which will have a method called on it when the load completes. Only then does it make sense to push in the javascript.
Does it work if you wait for the page to finish loading first?
Consider looking at NSURLConnection and its delegate methods. You can use these to check on the status of a download.
#interface
...
NSURLConnection *connectionInProgress;
NSData *googleRequestResponseData;
NSURL *googleURL;
...
#implementation
...
- (void) setUpRequest {
googleURL = [[NSURL URLWithString:#"http://www.google.com/"] retain];
googleRequestResponseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30.0];
connectionInProgress = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
#pragma mark NSURLConnection delegate methods
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[googleRequestResponseData appendData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[web_screen loadData:googleRequestResponseData MIMEType:#"application/octet-stream" textEncodingName:#"utf-8" baseURL:googleURL];
NSString *js_result = [web_screen stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('input')[1].value='test';"];
NSLog (js_result);
[googleURL release];
[googleRequestResponseData release];
[connectionInProgress release];
connectionInProgress = nil;
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog (#"Connection failed to load data from Google!");
[googleURL release];
[googleRequestResponseData release];
[connectionInProgress release];
connectionInProgress = nil;
}
Alternatively, check out Ben Copsey's ASIHTTPRequest wrapper, which includes a simplified approach to asynchronous downloads (see ASINetworkQueue, specifically).
You can use ASINetworkQueue to run a method of your choice (to run the Javascript code, for example), once the request download is complete.
add '.innerHTML' after what you are searching for
In your case do the following
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *js_result = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('input')[1].value='test'.innerHTML"];
}
This worked for me .
check this for more info here