October 18, 2010
Using Multiple UIWebView Objects
If you’ve ever tried to put multiple UIWebView objects on screen and load documents into all of them at once, you may have stumbled across an undocumented limitation of the iPhone SDK: the loadRequest method on UIWebView is not re-entrant, and you’re often left with one or more of the views showing an error icon. Fortunately, it’s very easy to leverage the UIWebViewDelegate protocol to resolve this issue, by ensuring that only one of the views is loading at a time.
Create a class that implements the UIWebViewDelegate protocol and includes a class-level UIWebView* variable named spinlock, then implement the following methods:
NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];
UIWebView* webView=(UIWebView*)[arr objectAtIndex:0];
NSURLRequest* request=(NSURLRequest*)[arr objectAtIndex:1];
@synchronized(self) {
spinlock=webView;
[webView loadRequest:request];
while (spinlock==webView) [NSThread sleepForTimeInterval:0.1];
}
[pool drain];
}
-(BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
if (spinlock!=webView) {
[NSThread detachNewThreadSelector:@selector(spinlockedWebViewLoad:) toTarget:self withObject:[NSArray arrayWithObjects:webView,request,nil]];
return NO;
}
return YES;
}
-(void)webViewDidFinishLoad:(UIWebView*)webView {
spinlock=nil;
}
-(void)webview:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
spinlock=nil;
}
Set your multiple web views to use the same instance of this object as their delegate before loading. (It’s often easiest to make the object that calls them be this delegate.) Here’s how it works:
Our own method spinlockedWebViewLoad will run on an alternate thread for each attempted load request coming from any UIWebView with this as its delegate. We use the class-level variable spinlock to track which UIWebView is currently allowed to perform a load operation (this is used in the next method). By synchronizing, we allow only one thread at a time to hold this spinlock, and it remains in the while loop inside the synchronized block until some other thread unsets the spinlock.
Any attempt to load, whether from your code or from the user clicking a link on the page already in the view, will ask your code whether it should load by calling the delegate method shouldStartLoadWithRequest. In our implementation of that method, we check whether the UIWebView that wants to load is the one (the only one) currently “allowed” to load, by checking whether it’s the one that spinlock is currently set to. If not, we tell it no, it’s not allowed to proceed with the load request, but we kick off a new thread on the above method, essentially requeueing that same request. Of course, if the UIWebView is the one that has the spinlock, it returns yes so that the load may proceed.
When the load either finishes or fails, we release the spinlock. The thread which is currently blocked by the while loop unblocks, exits the synchronized block, and one of the other threads waiting to enter the synchronized block (if any) is allowed to do so.
Basically, spinlockedWebViewLoad is traffic control, the delegate method shouldStartLoadWithRequest ensures that all requests do go through traffic control (as long as the UIWebView has its delegate set), and webViewDidFinishLoad and didFailLoadWithError signal traffic control that one load is done and the next can begin.
The only functionality that you may lose (and you can add more code to effectively regain it) is if you need to use these delegate methods for other functionality as well. It’s generally still doable by adding the code to these methods, but if you need to operate on the navigationType parameter of shouldStartLoadWithRequest, it can be tricky because by the time we allow the load to proceed, the type is always “other” – navigation kicked off by our code, not a user click. If you absolutely need this info when loading, I recommend trying to record the navigation type in an NSDictionary (indexed by the UIWebView in which it occurs) when the load is blocked, on the assumption that that’s the “real” navigation, and pull it back out when loading is allowed. But this occurs far less often than the larger issue of multiple web views, so I’ll leave the actual code as an exercise to the reader.
Piotr said,
July 21, 2011 at 6:24 am
-(BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:
(…)
if (spinlock!=webView) {
[NSThread deatchNewThreadSelector:@selector(spinlockedWebViewLoad:) toTarget:self withObject:[NSArray arrayWithObjects:webView,request,nil]];
(…)
It should be [NSThread detachNewThreadSelector … not “deatch”.
Ben said,
July 21, 2011 at 9:42 am
Thanks for the catch! I’ve corrected it in the post.
how to use this obective c code in monotouch | Jisku.com - Developers Network said,
September 15, 2012 at 1:27 pm
[...] found this code block here for UIWebiViewDelegate but I could not successfully convert it to [...]
harsh said,
December 12, 2012 at 11:11 pm
Thanks for posting. but how can i use it. simple project code display.
Thanks