I am loading an image from an API in my tableviewcell, it downloads the image everytime I scroll down the UITableView. So what is the best way to save this image and so it doesn't have to download the image again if it is already there?
Same thing for text, how can I save the text...
If it's not very big, you can download it once and save it into user preferences (NSUserDefaults) as NSData object. Works for me.
Alternatively, you can use asynchronous requests with NSUrlConnection and implement caching in any way you like. (For example, update image only once a week.)
Moreover, even default cache settings of NSUrlConnection might work good enough.
More on caching
edit
An example of asynchronous request.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: url]];
URLConnectionDelegate *delegate = ...;
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:delegate];
if (!connection) {
// something went wrong
}
In delegate, you mainly need methods to handle received data and to finish connection.
Assume you have NSMutableData *receivedData object.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// success, do whatever you want with data
[receivedData release];
[connection release];
}
The link above and API documentation provide more details about delegate structure.
Look at the LazyTableImages sample application in the iOS site sample code section. It has a great example of how to download images for a table cell using NSUrlConnection asynchronous calls and storing the images (and text) in an NSMutableArray.
This sample demonstrates a multi-stage
approach to loading and displaying a
UITableView. It begins by loading the
relevant text from an RSS feed so the
table can load as quickly as possible,
and then downloads the images for each
row asynchronously so the UI is more
responsive.
Related
in my app, I'm loading a detailsView from a tableView. I need to get an XML document (with SOAP), parse it, and then display the informations I got in my detailsView. So, when loading my detailsView I want to wait for the parser to parse the entire document BEFORE displaying my detailsView.
In my parser I'm using a NSURLConnection, and methods of NSXMLParserDelegate like parserDidStartDocument, etc...
I've thought of using threads but I'm not getting anything conclusive.
I'm trying to be clear on what I want to do but it's pretty hard :s
I'll provide more infos if needed.
You can do it in either viewDidLoad or viewWillAppear Method but you have to make all your detailview's control hidden until it loads data from the server and then show it when you get response from the parser with the value you get in response. You can show loading indicator while you are parsing your response. You need to call your url Asynchronously.
Do something like this in your viewWillAppear Method :
NSURL *url = [NSURL URLWithString:#"www.yourwebserviceurlhere.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
Implement Connection delegate methods.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
After you FinishLoadingData and you got response you need to parse your data by implementing XMLParser delegate method and when you finished parsing you need to show your detailview.
That's it. Hope this help.
I'm working on a TableView which controller downloads data from a web feed, parse and populate its content in this TableView. The feed provides data in chunks of 10 items only. So, for example loading data when there are 112 items could require about 12 requests to server. I would like to make these requests without blocking user screen and it should load data in order, like it can't load items on page 5 unless it has already fetched previous one (1,2,3,4 in this exact order for the example).
Any idea on how to implement this ?
Thx in advance for your help,
Stephane
Make your web calls asynchronous. Dont do web calls on the main UI thread...
For example if you are using ASIHttp library to make http calls (this is built on top of Apple's NSURLConnection), making an async request is as simple as -
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
And when the data is fetched these selector callbacks are invoked -
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
This will definitely make your UI responsive...
Also keep in mind to update UI elements only on the main thread. It's easy to start updating ui elements from background threads. So keep in mind...
You do not need to use another API and can use Apple's own NSURLConnection. It can retrieve the data synchronously or asynchronously. Of course the latter is necessary in your case. You save the data in the requests's delegate methods.
– connection:didReceiveResponse:
– connection:didReceiveData:
– connection:didFailWithError:
– connectionDidFinishLoading:
Also, see my recent more complete answer to this question.
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. :)
I'm sorry if this is a basic question. I've been googling, searching StackOverflow, and looking through example code for hours and haven't found anything satisfactory for my skill level.
I'm wanting something like a design pattern for handling network functions on the iPhone SDK. I have heard of people using a singleton class but have heard there are better ways for asynchronous connections. Would NSOperation be useful? I am fairly new to object oriented programming but am needing to make occasional calls to my webserver through HTTP for my current app and hope to find a solution that is easily reusable.
I've looked through the NSURLConnection docs and can get the basic functionality but the programming structure is messy and I'm not sure how to better organize it. Is there sample code for this that separates out these functions into their own class? A link to an example that does this would be greatly appreciated!
thanks!
I've been dealing with this same question for a while now...
If you're effectively doing a GET on a simple resource, and you're confident that the resource will always be there & accessible, there's an easy way to do it:
NSURL *URL=[[NSURL alloc] initWithString:#"http://www.google.com/"l];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
//TODO This needs to have timeouts & such set up, maybe parallelism
NSString *results = [[NSString alloc] initWithContentsOfURL :URL];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
That's a REALLY simple way to do it, but as my comment says, not very robust or reliable.
A slightly more robust, yet still reasonably simple, is replacing the NSString line with:
results = [[NSString alloc] initWithContentsOfURL:URL encoding:NSASCIIStringEncoding error:&err]; // possibly NSUnicodeStringEncoding
if (err!=nil) NSLog(#"Error occurred: %#", [err localizedDescription]);
That will at least TELL you if there's an error...
ASIHTTPRequest provides a lot of neat & useful network functionality for dealing with resources over the internet. http://allseeing-i.com/ASIHTTPRequest/ - the developer has been VERY responsive on his Google Group. I really wanted to use that, and may get back to it when/if it supports SSL Client Certificate authentication (which is what my project requires).
NSURLConnection, as described above - that's what I'm using now in my project. I would imagine that this will satisfy almost all needs, but it's (in my opinion) more tricky to use. And to be honest, I'm still having a little trouble wrapping my mind around how to integrate asynchronous data loading into my application. But if it will work for you - and it probably will, Apple is using it all over the OS and its apps - that's your best bet!
One possible approach is to use the NSURLConnection (as you mentioned).
Inside your .h file:
NSMutableData *connectionData;
Also add a property for connectionData...
Inside your .m file:
- (void)updateFromServer {
// You might want to display a loading indication here...
NSMutableData *connectionDataTemp = [[NSMutableData alloc] init];
self.connectionData = connectionDataTemp;
[connectionDataTemp release];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: your_url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
}
#pragma mark -
#pragma mark NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Add the received bulk of data to your mutable data object
[self.connectionData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Use your data
// If there is a loading indication displayed then this is a good place to hide it...
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Handle error
// If there is a loading indication displayed then this is a good place to hide it...
}
How can I use the UIWebView in Xcode so that when it loads up pages it DOESN'T download the images (to cause a faster loading page)?
UIWebView is a pale, poor little shadow of WebKit's full WebView, for which this is easy. -webView:shouldStartLoadWithRequest:navigationType: only gets called for navigation. It doesn't get called for every request like WebPolicyDelegate does on mac. With UIWebView, here's how I would attack this problem:
Implement -webView:shouldStartLoadWithRequest:navigationType: and set it to always return NO. But you'll also take the request and spawn an NSURLConnection. When the NSURLConnection finishes fetching the data, you're going to look through it for any IMG tags and modify them to whatever placeholder you want. Then you will load the resulting string into the UIWebView using -loadHTMLString:baseURL:.
Of course parsing the HTML is not a trivial task on iPhone, and Javascript loaders are going to give you trouble, so this isn't a perfect answer, but it's the best I know of.
expanding on Rob's answer.
I noticed that when loadHTMLString:baseURL: and always returning NO, that webView:shouldStartLoadWithRequest:navigationType: just keeps getting called. (i suspect loadHTMLString invokes another shouldStartLoadWithRequest).
so what I had to do was alternate between returning YES/NO
and I used NSScanner to parse the HTML and change src="http://..." to src=""
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (pageHasNoImages==YES)
{
pageHasNoImages=FALSE;
return YES;
}
NSString* newHtml;
NSString* oldHtml;
NSData *urlData;
NSURLResponse *response;
NSError *error;
urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
oldHtml=[[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding];
newHtml=[self.detail scannerReplaceImg:oldHtml]; // my own function to parse HTML
newHtml=[self.detail scannerReplaceAds:newHtml]; // my own function to parse HTML
if (newHtml==nil)
{
NSLog(#"newHtml is nil");
newHtml=oldHtml;
}
[oldHtml release];
pageHasNoImages=TRUE;
[web loadHTMLString:newHtml baseURL:request.URL];
return NO;
}
Be the delegate for the UIWebView, then intercept the call:
– webView:shouldStartLoadWithRequest:navigationType:
Check the values of navigationType in the documentation. I believe you'll be best served by returning NO on navigationType == UIWebViewNavigationTypeOther.
does this actually cause the page to load faster?
it sounds like the images are still being downloaded, but we're just not feeding them to the UIWebView.
or does shouldStartLoadWithRequest just load the HTML text first?