NSString* path = [[NSBundle mainBundle] pathForResource:@"help" ofType:@"html"]; NSURL* url = [NSURL fileURLWithPath:path]; NSError* err = nil; NSString* s = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err]; // error-checking omitted [self.wv loadHTMLString:s baseURL:url];
Chapter 11. Web Views
A web view (UIWebView) is a UIView subclass that acts as a versatile renderer of text in various formats, including:
In addition to displaying rendered text, a web view is a web browser. If you ask a web view to display HTML that refers to a resource available on disk or over the Internet, such as an image to be shown as the source of an
<img>
tag, the web view will attempt to fetch it and display it. Similarly, if the user taps, within the web view, on a link that leads to content on disk or over the Internet that the web view can render, the web view by default will attempt to fetch that content and display it. Indeed, a web view is, in effect, a front end for WebKit, the same rendering engine used by Mobile Safari (and by Safari on OS X). A web view can display non-HTML file formats such as PDF, RTF, and so on, precisely because WebKit can display them.A web view is scrollable, but UIWebView is not a UIScrollView subclass (Chapter 7); it has a scroll view, rather than being a scroll view. You can access a web view’s scroll view as its
scrollView
property. You can use the scroll view to learn and set how far the content is scrolled and zoomed, and you can install a gesture recognizer on it, to detect gestures not intended for the web view itself.A web view is zoomable if its
scalesToFit
property is YES; in that case, it initially scales its content to fit, and the user can zoom the content (this includes use of the gesture, familiar from Mobile Safari, whereby double-tapping part of a web page zooms to that region of the page). Like a text view (Chapter 10), itsdataDetectorTypes
property lets you set certain types of data to be automatically converted to tappable links.It is possible to design an entire app that is effectively nothing but a UIWebView — especially if you have control of the server with which the user is interacting. Indeed, before the advent of iOS, an iPhone app was a web application. There are still iPhone apps that work this way, but such an approach to app design is outside the scope of this book.
A web view’s most important task is to render HTML content; like any browser, a web view understands HTML, CSS, and JavaScript. In order to construct content for a web view, you must know HTML, CSS, and JavaScript. Discussion of those languages is beyond the scope of this book; each would require a book (at least) of its own. The thing to bear in mind is that you can use a web view to display content that isn’t fetched from the Internet or that isn’t obviously (to the user) a web page. WebKit is a powerful layout (and animation) engine; HTML and CSS (and JavaScript) are how you tell it what to do. In my TidBITS News app, a UIWebView is the obvious way to present each individual article, because an article arrives through the RSS feed as HTML; but in other apps, such as my Latin flashcard app or my Zotz! game, I present the Help documentation in a UIWebView just because it’s so convenient for laying out styled text with pictures.
Web View Content
To obtain content for a web view initially, you’re going to need one of three things:
An NSURLRequest
- Construct an NSURLRequest and call
An HTML stringloadRequest:
. An NSURLRequest might involve a file URL referring to a file on disk (within your app’s bundle, for instance); the web view will deduce the file’s type from its extension. But it might also involve the URL of a resource to be fetched across the Internet, in which case you can configure various additional aspects of the request (for example, you can form a POST request). This is the only form of loading that works withgoBack
, because in the other two forms, there is no URL to go back to.- Construct an NSString consisting of valid HTML and call
Data and a MIME typeloadHTMLString:baseURL:
. ThebaseURL:
will be used to fetch any resources referred to by a partial (relative) URL in the string. For example, you could cause partial URLs to refer to resources inside your app’s bundle.- Obtain an NSData object and call
loadData:MIMEType:textEncodingName:baseURL:
. Obviously, this requires that you know the appropriate MIME type, and that you obtain the content as NSData (or convert it to NSData). Typically, this will be because the content was itself obtained by fetching it from the Internet (more about that in Chapter 24).There is often more than one way to load a given piece of content. For instance, one of Apple’s own examples suggests that you display a PDF file in your app’s bundle by loading it as data, along these lines:
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"MyPDF" ofType:@"pdf"]; NSData *pdfData = [NSData dataWithContentsOfFile:thePath]; [self.wv loadData:pdfData MIMEType:@"application/pdf" textEncodingName:@"utf-8" baseURL:nil];But the same thing can be done with a file URL and
loadRequest:
, like this:NSURL* url = [[NSBundle mainBundle] URLForResource:@"MyPDF" withExtension:@"pdf"]; NSURLRequest* req = [[NSURLRequest alloc] initWithURL:url]; [self.wv loadRequest:req];Similarly, in one of my apps, where the Help screen is a web view (Figure 11-1), the content is an HTML file along with some referenced image files. I used to load it like this:
NSString* path = [[NSBundle mainBundle] pathForResource:@"help" ofType:@"html"]; NSURL* url = [NSURL fileURLWithPath:path]; NSError* err = nil; NSString* s = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err]; // error-checking omitted [self.wv loadHTMLString:s baseURL:url];Observe that I obtain both the string contents of the HTML file and the URL reference to the same file, the latter to act as a base URL so that the relative references to the images will work properly.
NSString* path = [[NSBundle mainBundle] pathForResource:@"help" ofType:@"html"]; NSURL* url = [NSURL fileURLWithPath:path]; NSURLRequest* req = [[NSURLRequest alloc] initWithURL:url]; [self.wv loadRequest: req];You can use
loadHTMLString:baseURL:
to form your own web view content dynamically as an NSString. For example, in the TidBITS News app, the content of an article is displayed in a web view that is loaded usingloadHTMLString:baseURL:
. The body of the article comes from an RSS feed, but it is wrapped in programmatically supplied material. Thus, in Figure 11-2, the right-aligned author byline and publication date, along with the overall formatting of the text (including the font size), are imposed as the web view appears.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no"> <style type="text/css"> p.inflow_image { text-align:center; } div.indented_image { text-align:center; margin-left:0; } img { max-width:<maximagewidth>; height:auto; } </style> <title>no title</title> </head> <body style="font-size:<fontsize>px; font-family:Georgia; margin:1px <margin>px"> <!-- author and date --> <div style="width:100%"> <span style="float:right; margin-bottom: 15px; display:block; text-align:right; font-size:80%;"> By <author><br><date> </span> </div> <!-- body, from feed --> <div style="clear:both; margin:30px 0px;"> <content> </div> </body> </html>NSString* template = [NSString stringWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"template" ofType:@"txt"] encoding: NSUTF8StringEncoding error:nil]; NSString* s = template; s = [s stringByReplacingOccurrencesOfString:@"<maximagewidth>" withString:maxImageWidth]; s = [s stringByReplacingOccurrencesOfString:@"<fontsize>" withString:fontsize.stringValue]; s = [s stringByReplacingOccurrencesOfString:@"<margin>" withString:margin]; s = [s stringByReplacingOccurrencesOfString:@"<author>" withString:anitem.authorOfItem]; s = [s stringByReplacingOccurrencesOfString:@"<date>" withString:date]; s = [s stringByReplacingOccurrencesOfString:@"<content>" withString:anitem.content];Web view content is loaded asynchronously (gradually, in a thread of its own), and it might not be loaded at all (because the user might not be connected to the Internet, the server might not respond properly, and so on). If you’re loading a resource directly from disk, loading is quick and nothing is going to go wrong; nevertheless, rendering the content can take time, and even a resource loaded from disk, or content formed directly as an HTML string, might refer to material out on the Internet that takes time to fetch.
Your app’s interface is not blocked or frozen while the content is loading. On the contrary, it remains accessible and operative; that’s what “asynchronous” means. The web view, in fetching a web page and its linked components, is doing something quite complex, involving both threading and network interaction — I’ll have a lot more to say about this in Chapter 24 andChapter 25 — but it shields you from this complexity. Your own interaction with the web view stays on the main thread and is straightforward. You ask the web view to load some content; then you sit back and let it worry about the details.
Indeed, there’s very little you can do once you’ve asked a web view to load content. Your main concerns will probably be to know when loading really starts, when it has finished, and whether it succeeded. To help you with this, a UIWebView’s delegate (adopting the UIWebViewDelegate protocol) gets three messages: