iOS Recipes - Matt Drance [14]
The PRPWebViewController class creates a basic, resizable root view containing a UIWebView for displaying web content and creates a large white UIActivityIndicatorView to tell the user that content is loading. We create the hierarchy in code so we don’t need to move a xib file every time we reuse this class.
Our web view controller initially shows an activity indicator and then fades the web content on-screen when it’s loaded.
Figure 12. A reusable web view controller
* * *
The activity indicator is centered within the main view at load time, and all of the Margin autoresizing masks are set to ensure it will stay centered whenever the main view is resized or rotated.
SmartWebView/PRPWebViewController.m
activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleLeftMargin;
CGRect aiFrame = self.activityIndicator.frame;
CGFloat originX = (self.view.bounds.size.width - aiFrame.size.width) / 2;
CGFloat originY = (self.view.bounds.size.height - aiFrame.size.height) / 2;
aiFrame.origin.x = floorl(originX);
aiFrame.origin.y = floorl(originY);
self.activityIndicator.frame = aiFrame;
We flatten the calculated origin to avoid nonintegral coordinates, which can blur a view’s appearance.
Our controller implements the standard UIWebViewDelegate methods to detect when the request is finished. If the load was successful, it hides the activity indicator and fades the web view on-screen. This gives a smoother transition while the user waits for content to appear. The controller also pulls the title element from the loaded HTML and sets that as its navigation title.
When Does a Web View Really Finish Loading?
Depending on the content you load, UIWebView can be a bit unpredictable. If the page you request contains iframes or dynamic content, your code may receive multiple webViewDidFinishLoad: messages. Because every individual use case may have a different definition of “finished,” this recipe does not do anything to audit or monitor these multiple callbacks. You’re free to tailor the class to your specific needs.
SmartWebView/PRPWebViewController.m
- (void)webViewDidFinishLoad:(UIWebView *)wv {
[self.activityIndicator stopAnimating];
[self fadeWebViewIn];
if (self.title == nil) {
NSString *docTitle = [self.webView
stringByEvaluatingJavaScriptFromString:@"document.title;"];
if ([docTitle length] > 0) {
self.navigationItem.title = docTitle;
}
}
SEL sel_didFinishLoading = @selector(webControllerDidFinishLoading:);
if ([self.delegate respondsToSelector:sel_didFinishLoading]) {
[self.delegate webControllerDidFinishLoading:self];
}
}
Note that if the view controller’s title property was already set, the code respects that. So if you’re using PRPWebViewController and you want a static navigation title rather than one based on the web content, just set the title property on the view controller when you create it.
A backgroundColor property is also exposed for easy customization of the view’s appearance while loading.
SmartWebView/PRPWebViewController.m
- (void)setBackgroundColor:(UIColor *)color {
if (backgroundColor != color) {
[backgroundColor release];
backgroundColor = [color retain];
[self resetBackgroundColor];
}
}
Why create a special property for the background color? Why not just set it on the view directly? Because depending on when we do that, we might force the view to load prematurely. The resetBackgroundColor method sets the color only if and when the view is loaded. Calling this method from setBackgroundColor: and viewDidLoad respects both the caller’s wishes and UIKit’s lazy loading mechanics.
SmartWebView/PRPWebViewController.m
- (void)resetBackgroundColor {
if ([self isViewLoaded]) {
UIColor *bgColor = self.backgroundColor;
if (bgColor == nil) {
bgColor = [UIColor whiteColor];
}
self.view.backgroundColor = bgColor;
}
}
There’s also a convenient BOOL property that