Deferring viewWillAppear until webViewDidFinishLoad - iphone

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.

Related

UIWebView webViewDidStartLoad Sometimes Not Firing

I have a UIWebView in my view controller and have set its delegate to the view controller via code (i.e. not through IB). I have also setup the appropriate delegate methods: shouldStartLoadWithRequest, webViewDidStartLoad, webViewDidFinishLoad and didFailLoadWithError.
In my view controller's viewDidLoad method I load an appropriate URL with this code:
[self.webView loadRequest:reqURL];
95% of the time everything works great and the page loads in the UIWebView object and displays as expected. Occasionally, however, the page doesn't load.
After stepping through my code I realized that on the times that it doesn't work the shouldStartLoadWithRequest delegate method fires, but webViewDidStartLoad doesn't.
Does anyone have any idea what's going on here? I couldn't find anything on Stack Overflow that specifically addressed this unique issue I'm having and am slowly reaching my breaking point. Thanks in advance!
You should make sure that your shouldStartLoadWithRequest implementation returns YES for all conditions at which you need your webView to load.

Block UITabBarController while contents of a view controller not been charged

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.

loading image asynchronously with spinner at the back

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).

Where to put cleanup code in UIViewController?

I have a UIViewController subclass that loads a bunch of images for each cell in a tableview asynchronously which is handled by a separate download class. I keep a list of all of these download requests in a dictionary which is keyed to the index of the cell that is requesting the image.
My question is i where should i put the code that cancels the image download if the viewcontroller is popped off the navcontroller? I need to do this because if the user hits back while there are still images being downloaded (which could take a while) then when they are finished downloading the viewcontroller has already been released.
I cant put it in the viewWillDisappear method because i do not want to stop the download if the user clicks on a separate tab and only when the hit the back button. For now i put this code in the viewcontrollers dealloc method which works fine although it doesnt seem right for some reason. I thought of using the viewDidUnload method but it seems this is only called when there is a low memory warning?
Any ideas?
dealloc is the perfect place for this as the view controller gets deallocated and you are responsible to clean your stuff up.
You may want to consider putting the code in viewWillDisappear: anyway, after all if the screen you are navigating to needs anything loaded it will be slowed by the background image load...

is applicationDidFinishLaunching the wrong place for setting an image for an UIImageView?

I found out this:
applicationDidFinishLaunching (an delegate method of the UIApplicationDelegate Protocol) seems to be called BEFORE my views from the nib file are loaded completely. So I tried all the day to change an image of an UIImageView right after my app launched in the iPhone simulator, but nothing happened.
Then I wrote a little action method that I call with the press of a button. And then it happened: WORKS!
So the applicationDidFinishLaunching delegate method isn't really the right place for stuff that has to be done after the app is really "ready". I gues there's something better that waits for the nib to be loaded completely. but where? and what?
I gues there's something better that waits for the nib to be loaded completely. but where? and what?
For application specific things like global settings, preferences, etc., -appDidFinishLaunching is the right place.
For UIView specific things, you typically use the -viewDidLoad method in a UIVIewController subclass. It is pretty much the only place you are guaranteed that the nib file is loaded, the IBOutlets are initialized and the IBActions are attached.
This is difference from the Mac OS X world, where -awakeFromNib was the place to do it.
Hey until your views and their view controllers instantiated you can't modify their ui. However just for the sake of your problem you can always declare the uiimageview as a property of your app delegate class and initialize it in the appDidFinishLaunching event. But that's the worst practise. As on the iPhone which has limited memory always lazy load ie: only initialize objects when and just before they are actually required by your UI. So ideally you should be doing this in the viewDidLoad event of the view where you want to use this UIImageView.
applicationDidFinishLaunching is usually used for stuff like database file checks, opening database connection, populating global variables, any other application wide logic, checking for an available Internet connection etc