Home > Coding, iPhone > Saving an Image from UIWebView

Saving an Image from UIWebView

April 30th, 2010

IMG_0049.jpg

I spent the last few days trying to wrap my head around how to save a user selected image in a UIWebView. The concept itself would seem really simple, and if you’re using Safari on the iPad or iPhone it is really simple. You just tap and hold on an image. Simple. Problem is, when you’re developing you’re own application, this functionality doesn’t exist in UIWebView. Don’t believe me? Try it out for yourself. Go ahead… I’ll wait.

Ok, so if you’re still here you must have found out I’m right. The worst part of all this is I found out the hard way… from a user. I rarely save images from the web, so it never occurred to me to try it in my own app (and honestly I thought this was built in freebie functionality). Well, I got an email the other day from a user who was shocked that this didn’t work. He was not nearly as shocked as I was. I was even more shocked to find out that there’s pretty much nobody on the web who has discussed how to do this. Much time was spent searching for a solution, a couple tweets sent with only crickets responding, time was spent filling out a bug report with Apple (radar) only to have it closed without a work around as a duplicate. So, I basically hit a brick wall.

Alright, let’s get down to it. My complaining is done. Well not really. Did you also know its a huge PITA to handle touches in a UIWebView? Like to the point where you’re either having to sub-class UIWindow to trap touches or you’re having to add a view above the UIWebView. I’m going to leave it up to you as to how you want to implement what we’re talking about. I personally decided to not bother with any of it. Why? Well, I’ve already started building in a bunch of gestures, so it just made sense use a gesture recognizer instead of adding in a bunch of code which may or may not end up breaking sooner than later. My hope is that Apple fixes the problem, but I’m not holding my breath. Anyway, choose for yourself. For this example we’ll just follow the KISS method.

Ok, here come the code. First things first, you need to have some kind of way to collect touches. For simplicity we’ll use a UITapGestureRecognizer:

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTap.numberOfTouchesRequired = 2;
[self.webView addGestureRecognizer:doubleTap];
[doubleTap release];

What we’ve done is simply define a gesture (two fingers on tap the screen at the same time) and add that to the webview. When the webview detects two fingers on the screen (tap) it’ll fire the doubleTap method which is where we’ll figure out what image has been selected (if any at all).

- (void) doubleTap:(UITapGestureRecognizer *)sender {
	int scrollPosition = [[self.webView stringByEvaluatingJavaScriptFromString:@"window.pageYOffset"] intValue];
	CGPoint startPoint = [sender locationInView:self.webView];
	NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).tagName", startPoint.x, startPoint.y+scrollPosition];
	NSString *value = [self.webView stringByEvaluatingJavaScriptFromString:js];
	if ([value isEqualToString:@"IMG"]) {
		NSString *imgURL = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", startPoint.x, startPoint.y+scrollPosition];
		NSString *urlToSave = [self.webView stringByEvaluatingJavaScriptFromString:imgURL];
		}

//Do whatever you want - like a UIAlertView or UIPopover to let the user chose what to do with the image
}

So, let’s walk through the code. The first thing we need to get is where we are on the page. Without this vital chunk of information we end up sending the same coordinates to the document so even if we’re at the bottom of the web page, when we tap the top of the screen it sends the coordinates for the header image. Not good. Next we get the location of the tap in the webView itself. After that we determine which element is at the location where the user tapped. Notice we added the scroll position to the y coordinate so again we don’t end up with the same x,y all the time. ‘tagName’ returns back the element type – ‘A’, ‘IMG’, ‘IFRAME’ etc. We only want images right now (you could always grab the a ref if you wanted to do other fun stuff) so we throw an if statement in to check if the element is an image. If it is, we grab the URL of the element and do stuff with it.

When that code is all said and done you’ll end up with an NSString like so:
http://justanotheripadblog.com/wp-content/uploads/2010/04/3GiPad_thumb.jpg
Nice right?

So, that’s how I ended up saving images from a UIWebView without resorting to private API calls or other wacky business.

Sound Off: Did this work for you? Am I crazy and there was a much easier way? Please let me know in the comments. Thanks!

Credits: iCab Blog for the elementFromPoint idea – see Alexander’s comment on 9/1 11:55am, and the support folks at perfectbrowser.com who got me started looking at doing this through javascript.

Post to Twitter Tweet This Post

brandon Coding, iPhone

  1. Jay
    May 6th, 2010 at 18:46 | #1

    Thanks for this, it helps a lot. However, i seem to get different results depending on the zoom level of the UIWebView- that is- if i’m zoomed in or using a smaller UIWebView than what would normally fill the screen, my touches don’t “hit” where they should be on the actual web page (so i miss the image). Is there any way to compensate for this?

  2. brandon
    May 6th, 2010 at 20:40 | #2

    You’re right, and unfortunately I really don’t have a way to compensate for this yet. I don’t think there is a way to query the level of zoom on a webview so you could work your way backwards. You might be able to find a way at a much lower level than this is at, but I’m not sure. Finding anything discussing how to do this was bad enough, I doubt there’s much discussion anywhere about how to compensate for zoom levels…

    I guess I’ll ask over at the apple dev forums, maybe someone there can provide some insight into how to compensate for zoom.

  3. Ryan
    May 20th, 2010 at 22:44 | #3

    Any idea why when a webview is a subview of a UIScrollView these gesture recognizers require more than one touch to work? This worked for me when I had two touches, but if I changed it to one and tried it never fired.

  4. brandon
    May 20th, 2010 at 22:52 | #4

    @Ryan
    If I had to take a guess the scrollview may be intercepting that single touch instead of the web view? Maybe throw some debug code into touches began on the scrollview and see…

  1. No trackbacks yet.

Twitter links powered by Tweet This v1.6.1, a WordPress plugin for Twitter.