7 tips for using UIWebView

December 11th, 2008 / in IPhone / by keremk /

For an IPhone app I have been building, I decided to use the UIWebView to render SVG files, instead of doing the vector rendering myself. I needed to have a way to read-in files generated from a vector authoring tool (Illustrator etc.) and after initially looking for an open-source SVG parsing/rendering engine of some sort, I decided on hosting the UIWebView itself instead and use the SVG rendering capability of WebKit. Another option could have been to read in PDF files as PDF is the default meta-file format for Quartz, but I needed programmatic access to each path drawn in the file and it was not apparent to me how I can do that once the PDF is rendered. (Any ideas on that is still welcome of course)

Anyways the point of this post is not really about different options for persisting and reading vector graphic files. I am rather planning to talk a bit about some of the tricky things I found out while trying to use the UIWebView programatically.

Loading the SVG file from your resources folder

This one is quite straightforward. You need to get the correct path for your resources folder and the SVG file in that folder, which can be easily accomplished using the below snippet:

NSString *filePath = [[NSBundle mainBundle]
	pathForResource:@"filenameWithoutExtension" ofType:@"svg";
NSData *svgData = [NSData dataWithContentsOfFile:filePath];

I also wanted to reference the javascript I used in the SVG file, instead of inline embedding. So I made sure that I set the baseURL correctly for the UIWebView to resolve the path to the javascript file in my resources directory:

NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSURL *baseURL = [[NSURL alloc] initFileURLWithPath:resourcePath isDirectory:YES];
 
[self.webView 	loadData:svgData 
	MIMEType:@"image/svg+xml"	
	textEncodingName:@"UTF-8" 
	baseURL:baseURL];	
[baseURL release];

However to my surprise the above did not work for locating my script file in the SVG referenced as below:

<script type="text/ecmascript" src="foo.js"/>

Well it turns out the src attribute is not actually in the SVG 1.1 specification at all?? The way I solved that problem is to write a build script to embed the script inline to the svg file (i.e. a preprocessing step) and make it part of the build process. (More on that in a later post…)

UIWebView loading contents when it is off-screen

Another surprise was trying to be able to load the SVG file when the UIWebView was off-screen. My design hosted the UIWebView inside a UIScrollView and I was loading the current page and caching the next and previous pages to have a smooth transition as I swipe the scrollview. But it turns out UIWebView has an issue with loading when it is off-screen. I.e. it does not render its contents, when it comes back into screen unless you double tap into it. It sounds more like a bug to me.

To solve this problem, I delayed the actual loading of the SVG file till:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

for the current view that is becoming visible and I also made sure that once it is loaded when the UIWebView is visible, I don’t ask it to reload it every time the user swipes the view to left and right.

Calling a javascript function from Objective-C

Part of my application is implemented in Javascript and using the DOM to manipulate the SVG document. I need to be able to call some of my Javascript functions from my Objective-C code. This is fairly straightforward and well documented in the UIWebView reference documentation in the SDK. All you need to do is to create an NSString which contains the Javascript function call, and ask the UIWebView to execute it as shown below:

NSString *jsCommand = [NSString stringWithFormat:@"setActiveColor(%d, %d, %d);", 
	redColor, greenColor, blueColor];
[self.webView stringByEvaluatingJavaScriptFromString:jsCommand];

Javascript communicating back with Objective-C code

What happens when you need to call back your Objective-C code from Javascript? Unfortunately at the time of this writing, there is no proper API for this for the IPhone. (See Apple Documentation for how to do it in MacOS X using WebKit) Fortunately there is a hack, a lot of projects have been using. It basically involves communication through a custom protocol that you make up to pass some parameters and then parsing those parameters in your Objective C code to call your corresponding Objective-C methods.

Basically you create a protocol like below:

myapp:myfunction:myparam1:myparam2

And then in Javascript:

document.location = "myapp:" + "myfunction:" + param1 + ":" + param2;

Then back in your Objective-C code:

- (BOOL)webView:(UIWebView *)webView2 
	shouldStartLoadWithRequest:(NSURLRequest *)request 
	navigationType:(UIWebViewNavigationType)navigationType {
 
	NSString *requestString = [[request URL] absoluteString];
	NSArray *components = [requestString componentsSeparatedByString:@":"];
 
	if ([components count] > 1 && 
		[(NSString *)[components objectAtIndex:0] isEqualToString:@"myapp"]) {
		if([(NSString *)[components objectAtIndex:1] isEqualToString:@"myfunction"]) 
		{
 
			NSLog([components objectAtIndex:2]); // param1
			NSLog([components objectAtIndex:3]); // param2
			// Call your method in Objective-C method using the above...
		}
		return NO;
	}
 
	return YES; // Return YES to make sure regular navigation works as expected.
}

Hacky: yes. Gets the job done: yes

Disabling the selection flash

Once you do the above steps, you are basically mostly on your way to have a functioning application that makes use of UIWebView as a component. But as any good IPhone application, you need to add more polish and get rid of the user annoyances.

The first annoyance is, what happens when you tap inside the UIWebView control that is hosting an SVG file. There is a default behavior that happens for all image files, including SVG files. The background of the image (including the border area) flashes quickly to some default grayish color.

Turns out there is a way to turn this off through the use of the WebKit CSS property -webkit-tap-highlight-color, and setting the alpha of the color to 0, in my Javascript code does the trick:

document.documentElement.style.webkitTapHighlightColor = "rgba(0,0,0,0)";

Disabling the “action” pop-up

The second thing I needed to disable is the “action” popup that appears if you tap and hold the contents of the UIWebView for a few seconds. This is also controlled through a CSS property called -webkit-touch-callout, and setting that to “none” in this case does the trick:

document.documentElement.style.webkitTouchCallout = "none";

Disabling default zoom effect

The final user annoyance I had was the zooming effect that happens by default, when you double tap the content area. Well for many applications this may be a desired effect, but in my case, I needed to disable the zooming. I was hoping that there is another CSS property that is something like webkit-disable-doubletap-zoom but unfortunately no such property exists (at least as of this writing.)

The only other idea I came up with was to be able to detect double tapping and calling the standard preventDefault on the event when that touch happens in my Javascript code. But it turns out that SVG DOM does not at the moment fully implement the touch and gesture events. You should be able to use those when you are hosting regular HTML though.

The final approach I came up with was to be able to intercept the touch events before they even reach UIWebView and stop them in my Objective-C code. To do this, you need to read a bit more about how the touch events are routed in Cocoa. (Requires login to the IPhone Dev Center) Basically the idea is to override the hit testing part of the UIWebView and detect the double tap there. In order to that I subclassed UIWebView and overrode the hitTest method in my implementation. Other than that I keep it up the superclass to do the rest.

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {	
	NSSet *touches = [event allTouches];
	BOOL forwardToSuper = YES;
	for (UITouch *touch in touches) {
		if ([touch tapCount] >= 2) {
			// prevent this 
			forwardToSuper = NO;
		}		
	}
	if (forwardToSuper){
		//return self.superview;
		return [super hitTest:point withEvent:event];
	}
	else {
		// Return the superview as the hit and prevent
		// UIWebView receiving double or more taps
		return self.superview;
	}
}

So this finally did the trick and I was able to prevent the double tap zooming effect as well.

Tags: , , , ,

Reader Comments

  • Hi, don’t you find manipulating SVG with javascript in UIWebView to be painfully slow? Just moving a shape around lags pretty bad.

  • In my application, the SVG images are pretty static. I am not trying to animate them. It would be a better idea to use the Canvas for animation though, as also recommended by Apple in its Web Development Guidelines for IPhone.

  • Hey, Great article. I am trying to build an app that displays high resolution image files and allows users to zoom right in.

    The problem is that the standard UIWebView (which we are currently using) is not adequate, as the images are too big for it to display.

    If you have any ideas, I’d be really appreciative.

    Cheers!

  • CATiledLayer is the way to implement handling of large images. The idea is to render only a small piece of the image at a time and then zoom in and zoom out much like how the Google Maps application works. Bill Dudney (author of Core Animation) posted a sample for that for MacOS here http://bill.dudney.net/roller/objc/entry/catiledlayer_example. You may be able to port that code to run on the IPhone.

    UIWebView also uses (I believe) the same Tiled Layer concept but you have much less control over it. So I would go with CATiledLayer instead.

  • Thanks for the informative post.

    I’m running into trouble trying to intercept the touches for a UIWebView. If I override the hitTest method, any event I receive has an empty set of touches. So I’m unable to get any information about the touches I’m trying to intercept (just single-taps for now). The UIWebView is contained within another UIView. The same thing happens if I create a subclass of UIView and override hitTest within that class.

    I thought it might be a bug in the version of the SDK I’m using, but I’m seeing this happen for all of the 2.* versions of the SDK.

    Any ideas why this might be happening?

    Thanks.

  • I initially ran into a similar problem. You need to ask for [event allTouches] instead of touches for just your view.

  • Nice stuff. Your post has been bookmarked at http://www.iphonekicks.com :-)

  • Thanks for that info. That tiling code from Bill Dudney works great for PDFs with vector graphics but we are using gifs/pngs and its not working well at all. I have it working, but the image quality is much worse than before, which pretty much tells me I am not doing something correct.

    I have contacted several other people to see if anybody has worked with this, they all are quick to point to “try tilling”, but nobody seems to know how to do it. :)

    If you have a solution please let me know.

    Thanks again for your suggestion and advice.

    Happy holidays!

  • I was looking all over to find a way to get rid of the “selection flash” when tapping a link, thanks for the tip with -webkit-tap-highlight-color, it did the trick.

    And a bonus was being able to disable the “action” pop up. Thanks.

  • I am having the same problem as Ryan. The touches are always empty. The code is the article, what I am using, requests [event allTouches]. Still, no touches. Anybody figure this out?

  • Hi all,

    I have problems in the hitTest function: the [event allTouches] is always empty. I have the hitTest on a trasparent view over the UIWebView, but the same is when i subclass the UIWebView to intercept the hitTest on the webview itself. There is something that I miss to do? Thanks.

  • I was never able to figure out why the UIEvent passed into hitTest:withEvent had an empty set of touches.

    I did come up with a solution to my problem though. I was only concerned with single-taps so I’m not sure how applicable it is to everyone else’s situation. See my blog post at: http://ryan-brubaker.blogspot.com/2009/01/iphone-sdk-uiwebview.html

  • Hi Kerem:

    Just wanted to check in to see why my last comment was filtered. I was hoping to help those out that were seeing the same problem I had. If it was because I included a link to my blog, I can certainly repost without the link.

    If you want to shoot me an email, my address is ryan.brubaker@gmail.com. Hope to hear from you soon and sorry for any confusion about my post.

    Thanks.

  • I did approve the comment earlier, but it looks like it remained as filtered. I guess a glitch with Wordpress. Should be working now. I am also looking into why the original solution did not work in the first place for everyone else. I was not actively working on my blog over the holidays.

  • Thanks Kerem.

    Yeah…scratching my head on why the UIEvent in your code has touches but in my code it does not. I thought maybe it was due to you catching double-taps and me catching single taps, the logic being the touches don’t get associated with the event until after the first call to hitTest:sendEvent. But I was still seeing an empty set of touches when the method was called for the second tap.

    Strange.

  • [...] menu if you hold your finger down.  And a few hacks to get sounds working.  Most of it comes from http://www.codingventures.com/?p=31 – thanks fellow iPhone [...]

  • Thanks for this great post… It made voodoo seem like magic!

  • hi kerem,

    can you share your code? I would like to see it performance.

    Thanks! bye philotas

  • Has anyone found a solution to the empty event?

  • Hi Kerem i was wondering how safari mobile shows the checkerboard background when scrolling quickly or zooming out this improves the performance a lot and how can i implement it using UIWebview

    my email is mauriceluca2000@gmail.com

    Thanks

  • The zoom thing, you overdid yourself there a bit. There is a much simpler method:

  • [...] a useful collection of tips on using [...]

  • 7 tips for using UIWebView…

    You’ve been kicked (a good thing) – Trackback from iPhoneKicks.com – iPhone SDK links, community driven…

  • how can i use this prototype in my button event? inside of html file myapp:myfunction:myparam1:myparam2

    document.location = “myapp:” + “myfunction:” + param1 + “:” + param2;

  • [...] can use this robust approach, or this smart method. But the first is using the UNDOCUMENTED (private) API, and second is not working for me (and for [...]

  • Mark Essien, the critical text is missing from you post – I’d love to know this simpler zoom-disabling method. Please update/email me!

  • Use meta name viewport, user-scalable=0

  • Nice article…. Helped me a lot .. thanks…..

  • Just an FYI, the SVG script element uses xlink:href to reference the file (not the @src attribute like in HTML).

    Note that this is a namespaced attribute so:

  • You have 3 snippets for the JavaScript calls Objective-C section. The second two make sense to me, but I don’t know exactly what this line indicates:

    myapp:myfunction:myparam1:myparam2

    I can see it is referring to a function and params, but I don’t know how to use it.

    Is this a protocol in Objective-C? I don’t recognize the format.

    Making a connection between JavaScript and Objective-C is a key capability for us. It would be great to get some more information about how this works. I have played with Phonegap, but have run into problems with that framework… and in any case I would prefer to have more control, if I can.

    Thanks for a great post.

  • [...] 7 tips for using UIWebView | Coding Ventures For an IPhone app I have been building, I decided to use the UIWebView to render SVG files, instead of doing the vector rendering myself. I needed to have a way to read-in files generated from a vector authoring tool (Illustrator etc.) and after initially looking for an open-source SVG parsing/rendering engine of some sort, I decided on hosting the UIWebView itself instead and use the SVG rendering capability of WebKit. (tags: content tutorial svg cocoatouch uiwebview javascript) [...]

Add Your Comment