I see many apps which have an image frame that loads the image asynchronously and while the image is not loaded yet, it shows either a spinner at the back or a default image. Is there a library for doing that? If yes then what is it? And please no three20 (I've had a bad experience with it). If not then how do you create one.
The spinner is called UIActivityIndicatorView, which is a subclass of UIView, and as such, can be placed anywhere on the screen.
It has startAnimating and stopAnimating methods, which begin and end the "spin".
So create the UIActivityIndicatorView, place it with addSubview where you want, and call startAnimating (this, as UI activity, has to be done on the main thread). Then call the image loading code asynchronously, and when the image finishes loading, call stopAnimating on the spinner, put the loaded image on top of it, and then remove the spinner with removeFromSuperview.
AFAIK, there is no built-in Cocoa API to do this in a call or two (which sounds like what you want).
I personally set up a singleton image cache manager that uses the built-in NSURLConnection class to load the images, save them to the filesystem, then call delegate methods on the requesting class (which includes a little subclass of UIImageView) to hand it the loaded image data.
People on SO report success with ASIHTTPRequest as well, for similar tasks. Worth checking out, I imagine (though I don't use it).
Related
I'm doing an app that uses a TabBarController and each Tab uses its own navigation controller.
The app has dynamic content and I use viewDidDisappear viewDidAppear methods to create or destroy the objects that I need each time I enter or exit into the ViewController.
My problem is when I start to sail very fast and I don't give time to load the Threads that I use for uploading content such as XML peta app or destroy objects when I leave the ViewController.
How I could control the tabs of the navigationbar or tabbarviewcontroller for not respond until the viewcontroller has loaded all contents?
Excuse me if I'm not well expressed. Thanks!
No matter you use synchronous request or asynchronous request, just show an UIAlertView while loading the data. This will both serve as a notification to the user that something is being loaded, and the it will block the interactions with all the other views on the screen.
As others have suggested in comments, I believe that what you want to do is rearrange the order in which things are triggered. Perhaps something like this:
On viewWillAppear:, clear (or disable or whatever is appropriate) your objects that are no longer valid and begin the load-new-content thread. Perhaps display a UIActivityIndicator or similar.
On viewWillDisappear:, tell the load-new-content thread that it can stop, its results are no longer needed. If you put up an activity indicator, take it down.
At the end of the load-new-content thread, take down any activity indicator, update the UI with the new contents and activate.
I don't really see any way around this -- if the UI is not valid until the new content is loaded, then you have to wait for it.
Another solution might be to cache the contents from the previous fetch, and always display those on viewDidLoad. Then, at the end of your new-content-thread, cache the new contents, and update the UI.
Does someone know of a way, or has a creative idea as to how to obtain an UIImage that is rendered from a UIWebView? The catch is, that is must be on a background thread.
I'll elaborate:
I'm trying to obtain the image from multiple UIWebViews every second+-, and display it on screen (iPhone's of course). Because rendering a layer to a CGContext is a CPU consuming job, I wouldn't like to use the main thread, so not to hang the UI.
My attempts so far were:
I tried to use renderInContext on the webView's layer to the UIGraphicalContext, but the _WebTryThreadLock error crashed the webviews.
I tried creating a CGBitmapContext, and render the webview's layer to it, but got the same result.
I tried implementing a copy method (by adding a Category) to CALayer, that deep copied all of the public properties, and sublayers. Afterwards, I tried to renderInContext the layer I copied. I got a UIImage that was partially "correct" - meaning, not all of the layers were rendered, so for example, i would get only the website header (or footer, or body, or search bar, or just a some of the frames). The UIWebview's layer consists of all sort of subclassed CALayers, so this is probably why this approached didn't work.
I tried setting the kCATransactionDisableActions in a CATransaction, but it didn't appear to change this behavior (neither of them).
I'm pretty close to giving up.
Is there a savior among you people?
UIWebView hates, and I mean really hates, having anything done to it on a background thread. UIKit is not fully thread safe. Drawing to a graphics context is (this was added in iOS 4), but not creating UIViews on a secondary thread.
Are you creating your UIWebViews off the main thread? Do you perhaps have some code to share? I would suspect your issues are being caused by the fact you're trying to perform operations to a UIWebView on a secondary thread. The drawing operation to render the view's contents as an image can happen off the main thread, but creating the view itself can't.
I've been working on this, too.
In a thread I create a view hierarchy, loop until the web-view has finished loading content.
The webview I create is inside of a UIViewController's viewdidload-- I've tried doing
if ([NSThread isMainThread] == NO) {[self performselectorOnMainThread: #selector(viewDidLoad)return;)}
And I've done the same for dealloc'ing the webView.
But that didn't work.. I've only found that we avoid UIWebView exceptions UNTIL we hit the autorelease pool...
I'm using instruments to figure out why.
Here's my attack strategy...
I'm going to perform the render operation on the main thread with an off-screen view, having a separate thread running some sort of queue to manage them. I'm worried about UI lag, so it'll have to be fairly efficient.
I have an application that uses UIWebViews in several view controllers. The UIWebViews are used to render locally generated html, no slow network access required.
To save memory I only load these on demand as prompted by the viewcontroller viewWillAppear callback. (And unload offscreen instances in response to didReceiveMemoryWarning messages.)
The problem is that the user gets to see the html being rendered, sometimes accompanied by flashes of styling and other assorted unpleasant artifacts. I would much rather the rendering be done offscreen, and reveal the fully rendered view when its ready.
It would be very tidy to be able to have the viewWillAppear not return until the UIWebView is fully rendered. But how?
I tell the UIWebView what to render by sending it a loadHTMLString:baseURL: message. This is asynchronous, and some time (soon) later the webview's delegate gets called back webViewDidFinishLoad.
I experimented with running a runloop inside viewWillAppear, running either the NSDefaultRunLoopMode or UITrackingRunLoopMode. This works in the simulator (it complains to the log
[CATransaction synchronize] called within transaction
but does work) but on a device it deadlocks, with webViewDidFinishLoad never being called.
(Also, it seems like the UIWebView loading property doesn't work. At least, after I call loadHTMLString:baseURL: and before getting the callback it's not true.)
Lots of solutions here I think. A quick one is to load your UIWebView with it's hidden property set to YES. Then set your UIViewController as the UIWebViews delegate and implement:
- (void)webViewDidFinishLoad:(UIWebView *)webView
where you set the property back to NO.
A thing to note is that webViewDidFinishLoad will fire more than once if you have framed/embedded content. So you have to keep track of this. Shouldn't really be a problem if you are loading local content.
I like monowerker's solution best, but another solution would be to hold onto the already-rendered UIWebView all the time (in some more permanent object than the view controller). I'd only do that if the look of monowerker's solution is too disruptive.
I'm using the three20 project for my iPhone app. I've narrowed my problem down and I'm now just trying to re-create the 'Web Images in Table' example that comes with the project. I've copied the code exactly as in the project, with the exception that I do not use the TTNavigator (which the example does) but I am adding my TTTableViewController manually to a tabBar.
The problem is as follows; the images in the table should load automatically from the web, like in the example. But they only load after I scroll the table up and down.
In the console it clearly says it is downloading the images, and you see the activity indicator spinning like forever.. And unless I scroll up and down once, the images will never appear.
Anyone? Thanks in advance.
P.S:
If I'm using this code in any random UIView, It also doesn't work (only shows a black square):
TTImageView* imageView = [[[TTImageView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)] autorelease];
imageView.autoresizesToImage = YES;
imageView.URL = #"http://webpimp.nl/logo.png";
[self.view addSubview:imageView];
If I put this code in my AppDelegate (right onto the window), it does work .. strange?
POSSIBLE SOLUTION:
Although I stopped using TTImageView for this purpose, I do think I found out what the problem was; threading (hence accepting the answer of Deniz Mert Edincik). If I started the asynchronous download (because basically that is all the TTImageView is, an asynchronous download) from anywhere BUT the main thread, it would not start. If I started the download on the main thread, it would start immediately..
Sounds like a threading problem to me, are you creating TTImageView in the main runloop?
I find one interesting thing. When I use combination TTTableViewController, TTTableViewDataSource and TTModel I have same problem with loading TTImageView. My problem was, that my implementation of Model methods 'isLoading' and 'isLoaded' don't return proper values after initialization of model. That forces me to call reload on model manualy in 'viewDidAppear' method and that causes image loading problem. So I repair my 'isLoading' and 'isLoaded' methods to both return 'NO' after Model init, and everything is fine.
When an image finishes loading try sending a reloadData message to the table view. This forces the table to recalculate the size of the rows and redraw the table. Just be careful that you don't start downloading the image again in response to this message.
I've written something similar to this where an image view will load its own image from the web.
Im my experience, when the image had loaded successfully but was not shown in its view, it was a case that the cell needed to be told to redraw.
When you scroll the table view, the cells are set to redraw when the come onscreen, which is why they appear when you scroll.
When the image loads, tell the cell that it is sitting in to redraw by sending it the message setNeedsDisplay.
That way, when the image finishes downloading, the cell its sitting in (and only that cell) will redraw itself to show the new image.
It's possible that you might not need to redraw the entire cell and might be able to get away with simply redrawing the image view using the same method call. In my experience, my table cells view hierarchy was flattened, so I had to redraw the whole cell.
I don't have an answer for what you want to do, but I will say that this is considered a feature, and the expected behavior. You use TTImageView in UITableView when you want to do lazy loading of images. TTImageView will only load the images whose frames are visible on the screen. That way, the device uses its network resources to download images that the user has in front of them, rather than a bunch of images that the user isn't even trying to look at.
Consider having a long list that may contain a couple hundred thumbnail images (like a list of friends). I know from experience that if you kick off 100+ image requests on older devices, the memory usage will go through the roof, and your app will likely crash. TTImageView solves this problem.
This is a thread problem. You can load the images by including the line:
[TTURLRequestQueue mainQueue].suspended = NO;
in - (void)didLoadModel:(BOOL)firstTime.
I've set up an Objective-C category for an iPhone app's UIImageView class. The category's mission is to help load URL-based images asynchronously with memory/disk caching.
Now, in UIImageView+Cache.m I have access to an NSOperationQueue so I can kick off a loading thread. I create an NSOperation-derived object, initialized with the image URL and the target UIImageView, and a selector to perform on the target once the operation is complete. In the selector method, we set our freshly-loaded image (or, if not found, we set an alternate placeholder image), and we're done!
This works fine, until a UIImageView happens to be removed before the NSOperation completes. For instance, I have a previous/next segmented control in my UI that causes these UIImageViews to be removed and added anew (they're part of a larger "item" that is being viewed in the app), so it's very easy to tap these in rapid succession.
So if you decide to start tapping away before all the images are loaded - KABLAM! Unhappy thread has an invalid object and doesn't know it. :(
The closest thing I can find to help mitigate this is NSOperation's cancel and isCancelled methods, except you can't keep track of which operation object to cancel within a Category, because - if I understand correctly - Categories can't add IVARs to objects!
Maybe that means Categories aren't a good idea here? (Whines: "But I liiiiike Categories! Waaah!")
Advisement appreciated!
I probably wouldn't use a category for this situation. Categories are useful, but are usually unnecessary. I'd only use a category if you have a really good reason to. What exactly are you putting in the category?
I think you could implement the whole thing in the NSOperation subclass, which would be the best solution. Put a retain on the image view so it doesn't get deallocated before the image is downloaded, and cancel the download if the view is not visible anymore. If that's not possible, then subclass UIImageView instead of using a category.
I would say there is no harm in moving this into your own UIImageView subclass. Sure, you may like categories - but if they don't do the job then why hesitate in moving to a design that does?
Are you retaining the UIImageView by the NSOperation? Otherwise the imageView might be freed before the NSOperation completes, leading to kablooi central. You should do a retain and then, once you've done the setImage, do a release.