7 tips for using UIWebView

转自http://www.codingventures.com/2008/12/using-uiwebview-to-render-svg-files/ 

 

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 standardpreventDefault 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 thetouch 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.

posted @ 2010-08-18 00:20  junz  阅读(1974)  评论(0编辑  收藏  举报