Skip to content


WebKit on the iPhone (Part 1)

If you develop an application which should display a web page or HTML file, you can use the WebKit framework, which is part of the MacOS and also of the iPhone OS.

But while on the Mac, the WebKit framework provides almost 160 public header files which define even more public classes and tons of methods you can call to get control of all aspects of loading, rendering, displaying and modifying a web page, on the iPhone there’s only one single class (UIWebView) with has just about a dozen methods you can use. Though internally the UIWebView class uses the same WebKit framework that is available on the Mac as public API, this API is private on the iPhone and therefore can’t be used. The small number of methods of the UIWebView class are sufficient to display nicely formatted text in help screens for example, but for a web browser (like iCab Mobile) or other web-based apps this isn’t enough, many essential features are missing.

Some examples:

  • UIWebView doesn’t provide a method to get the title of the currently displayed web page,
  • it just ignores all attempts to open links which are meant to open in new windows or tabs
  • it doesn’t allow accessing the HTML tree

WebKit itself provides many classes for all these tasks, but all of them are private and not available on the iPhone.

Some of the alternative browsers which are available in the AppStore just declare these limitations as feature (for example they advertise the inability to open new windows or Tabs as “no anoying popup window”). This sounds great, but of course this doesn’t make such browsers useful in the real world.

So what can we do to overcome these limitations of the UIWebView class? Can we (re)implement all the cool features of the WebKit framework which is available on the Mac on the iPhone as well without violating the  iPhone SDK agreements with Apple? Unfortunately, we can’t. But we can implement many of the missing feature.

If you look at the available methods, there’s only one, which would allow access to the content of the web page, and this is more or less the only way to get back  the missing features. And this method is

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

This method is used to execute JavaScript code in the context of the current web page and get back a string as result. This means that we have to use JavaScript code to implement the features we need.

Let’s start with something that is very easy. We implement methods to get the title and the URL of the currently displayed web page. We implement this as an Objective C “Category”, so we don’t need to subclass UIWebView:

File: MyWebViewAdditions.h

@interface UIWebView (MyWebViewAdditions)
- (NSString*)title;
- (NSURL*)url;
@end

File: MyWebViewAdditions.m

#import "MyWebViewAdditions.h"

@implementation UIWebView (MyWebViewAdditions)

- (NSString*)title
{
    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];
}

- (NSURL*)url
{
    NSString *urlString = [self stringByEvaluatingJavaScriptFromString:@"location.href"];
    if (urlString) {
        return [NSURL URLWithString:urlString];
    } else {
        return nil;
    }
}

@end

What are we doing here?
From the JavaScript’s point of view a web page is represented by the “document” object which has several properties. One of the properties is the “title” property which contains the title of the page. So with “document.title” we can access the title of the document within JavaScript. And this is exactly what we need to pass as a parameter to the method “stringByEvaluatingJavaScriptFromString:” to get the document title.

For retreiving the URL we do something similar.

So whenever we need to get the title or URL of the web page that is displayed in a UIWebView object, we only need to call the “title” or “url” method:

NSString *title = [anyUIWebViewObject title];

The next limitation we may want to address is the inability to open links which would open in a new window. The WebKit on the Mac would just call a delegate method of the host application to request that a new WebView object is created for an URL request. The application would then create a new WebView object and load the new page there. But on the iPhone the UIWebView doesn’t support such a delegate method and so all attempts to open such a link are just ignored.

These links do usually look like this:

<a href="destination" target="_blank">Link Text</a>

The “target” attribute defines where the link will open. The value can be a name of a frame (if the web page has frames), the name of a window or some reserved target names like “_blank” (opens a new window), “_self” (the window itself), “_parent” (the parent frame, if there are nested frames) and “_top” (the top-level or root frame, or identical to “_self” if the page doesn’t use frames).

As a first step, we want to tap on a such a link in our iPhone App, and the link should open like any other normal link in the same UIWebView object. What we need to do is simple: we need to find all links with a “target” attribute set to “_blank” and change its value to “_self“. Then the UIWebView object will no longer ignore these links. To be able to modify all of the link targets we have to wait until the page has finished loading and the whole web page content is available. Fortunately UIWebView provides the delegate method

- (void)webViewDidFinishLoad:(UIWebView *)webView;

which will be called when the web page has finished loading. So we have everything we need: We get notified when the page has loaded, and we know a way to access and modify the web page content (using “stringByEvaluatingJavaScriptFromString:“).

First we write our JavaScript code. Because this will be a little bit more code than what was needed to get the document title, it’s a good idea to create an extra file for our JavaScript code and then we add this file to the resources of our project in XCode:

File: ModifyLinkTargets.js:

function MyIPhoneApp_ModifyLinkTargets() {
    var allLinks = document.getElementsByTagName('a');
    if (allLinks) {
        var i;
        for (i=0; i<allLinks.length; i++) {
            var link = allLinks[i];
            var target = link.getAttribute('target');
            if (target && target == '_blank') {
                link.setAttribute('target','_self');
            }
        }
    }
}

What is this JavaScript function doing, when called?
It gets an array of all links (“a” tags) and then loops through all of these tags, checks if there’s a target attribute with the value “_blank“. If this is the case it changes the value to “_self“.

Note: There are other tags which can have a “target” attribute, like the “form” tag and the “area” tag. So you can use the “getElementsByTagName()” call to get these tags as well and modify their target attributes in the same way as I’ve done this for the “a” tag.

In our iPhone App we need to define a delegate for the UIWebView object and this delegate object will be called whenever the web page has finished loading. This is the method that is called in the delegate by the UIWebView object:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"ModifyLinkTargets" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    [webView stringByEvaluatingJavaScriptFromString:jsCode];

    [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyLinkTargets()"];
}

What is this code doing?
At first the access path of the JavaScript file we created before is retreived from the application bundle and we load the content of the file (the JavaScript code) into a string. Then we execute/inject this code into the web page and finally we call out JavaScript function
which is modifying the link targets.

Some notes:

  • Getting the JavaScript file from the application bundle and loading it into a string should be usually done somewhere in the init methods of your UIWebView delegate object. This way the string with our JavaScript code is only loaded once and can be simply reused whenever a new link is clicked and a new page is loaded.
  • Using a long name for our JavaScript function which also includes a prefix like “MyIPhoneApp_” makes it unlikely that the code we inject into a web page will interfere or confict with functions and variables which the web page itself has already defined for its own purposes. This is especially important when we modify web pages we haven’t created ourselves and where we can not predict which function or variable names the JavaScript code of the web page is already using.
  • Using separate calls of “stringByEvaluatingJavaScriptFromString” to first injecting our own JavaScript code and then calling our own JavaScript function to start modifying the link targets seems to be more complicated that necessary. And for this simple example you would be right. But it is likely that you’ll define much more additional JavaScript functions for many different tasks as well. Some of the tasks are started when the page has finished loading (like modifying the link targets), but some tasks will be started later and maybe even multiple times. And so it makes much sense that injecting the code and calling the functions are done in separate calls.
  • The delegate method “webViewDidFinishLoad:(UIWebView *)webView” is called for each frame, not only when the page itself has finished loading. This means that this delegate method can be called multiple times while a single web page is loaded. I think that this can be called a bug in the iPhone OS, but nevertheless it is important to know. When you modify the web page, be aware that this might be done multiple times and so make sure that none of your modifications will have bad side effectes when being modified a second time.

What next?

  • The above example code does not cover web pages where new windows are opened using JavaScript.
  • The links will open in the same window, which is fine because they are no longer ignored. But they still don’t open in a new window or Tab.

More about this topic and the cases which are not yet covered will come in the second part of the “WebKit on the iPhone” article.

Feel free to ask questions and write comments. I’d like to get some feedback.

Posted in iPhone & iPod Touch, Programming.

Tagged with , , , .


81 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Travis says

    I just bought iCab Mobile (great app btw) and noticed that you have a custom popup when doing a tap and hold on a link. How did you override the UIWebView built in popup and implement this one?

  2. Alexander Alexander says

    I think the details would be worth another blog post tutorial. Here’s the short version:

    Switching off the default popup is easy, you only need to set a certain CSS property for the links or just the body of the document:

    -webkit-touch-callout: none;

    In iCab I’m doing this within JavaScript code while processing the links (as described in this blog post):

    document.body.style.webkitTouchCallout = ‘none’;

    And opening the new custom popup in iCab Mobile is done by first catching the click-and-hold gesture (I’m doing this by overwriting the “sendEvent” method of the “UIWindow” class) and then using JavaScript code to find the link or image at the coordinates where the click occured. If there are any links or images found, their addresses will be passed back to the Cocoa part of iCab and the new custom popup will be opened.

    BTW: you can do almost everything from within Javascript alone because the Mobile Webkit supports JavaScript events like onTouchStart, onTouchMove, onTouchEnd etc. This is probably a lot of easier because you don’t need to overwrite UIWindow and write your own code to catch the click-and-hold gesture, no need to find the link or image at a certain coordinate because the links and images can already trigger the onTouchEvents themselves, and no need to pass data between the JavaScript code and Cocoa code multiple times. But using the onTouch events within JavaScript can be noticable slower on very large web pages, which is the reason why I’ve chosen the much more complicated approach.

  3. kiran says

    Hi Alexander, I’m newbie to iphone dev, i got task to find the url which is currently connected in iphone, after a long search i found your snippet, i have apply and i’m not sure how to call the url method, if u don’t mine can u help me, what the way to url from my mainAppDelegate.m file

    thanks

  4. kiran says

    i have to call the url in which method,is it applicationDidFinishLaunching:(UIApplication *)application {} in the AppDelegate.m file, if u don’t mine can u help me here, do u have any sample project implement this getting current url, can u send me the project,

    thanks

  5. Alexander Alexander says

    @kiran
    You can only get the URL of a web page within the UIWebView objects which are created by your app. So if you have created a UIWebView object somewhere in your app and assigned it to a variable “myWebView” then you can get the URL by calling “NSURL *url = [myWebView url]“.
    If you’re new to iPhone and Objective-C programming, you should also try to find an Objective-C tutorial, which helps to understand the basic design patterns of the Objective-C programming language.

  6. kungfuslippers says

    Hi,

    I’m new to iPhone dev.

    I’ve embedded some videos in some customised UITableViewCells within a UITableView. (similar to the youtube application) I’ve got an accessory button which is a custom button which responds to accessoryButtonTappedForRowWithIndexPath().

    At the moment if I tap the youtube video icon (which is a UIWebView) within a cell directly the player launches the video, and plays it perfectly which is great.

    However if I select elsewhere within the cell I obviously get a call to accessoryButtonTappedForRowWithIndexPath or didSelectRowAtIndexPath.

    My question is…is it possible to somehow direct the cell touch events to the YouTube UIWebView within the cell so that tappping anywhere within the cell launches my video?

    Someone suggested I might be able to call stringByEvaluatingJavaScriptFromString() (from didSelectRowAtIndexPath() I assume) to launch my URL but I could do with some pointers as to how….. The code to embed the youtube url in the UIWebView is as follows:-

    - (void)embedYouTube:(NSString*)url frame:(CGRect)frame {
    NSString* embedHTML = @”\
    \
    \
    body {\
    background-color: transparent;\
    color: white;\
    }\
    \
    \
    \
    “;
    NSString* html = [NSString stringWithFormat:embedHTML, url, frame.size.width, frame.size.height];
    if(videoView == nil) {
    videoView = [[UIWebView alloc] initWithFrame:frame];
    [self.view addSubview:videoView];
    }
    [videoView loadHTMLString:html baseURL:nil];
    }

  7. kungfuslippers says

    Try again….

    - (void)embedYouTube:(NSString*)url frame:(CGRect)frame {
    NSString* embedHTML = @”body {background-color: transparent;color: white; } “;
    NSString* html = [NSString stringWithFormat:embedHTML, url, frame.size.width, frame.size.height];
    if(iWebView == nil) {
    iWebView = [[UIWebView alloc] initWithFrame:frame];
    iWebView.delegate = self;
    [self.contentView addSubview:iWebView];
    }
    [iWebView loadHTMLString:html baseURL:nil];
    }

  8. kungfuslippers says

    ok, so pasting the html isn’t going so well :- the code is available here:-

    http://apiblog.youtube.com/2009/02/youtube-apis-iphone-cool-mobile-apps.html

  9. Alexander Alexander says

    I don’t know if there’s a certain JavaScript function that can be called to start playing the video. If there is such a call, you can use “stringByEvaluatingJavaScriptFromString” to call this function, otherwise I don’t have an idea at the moment. Sorry.

  10. websurfer says

    Wondering what JavaScript code do you use “to find the link or image at the coordinates where the click occured”? Do you call “document.elementFromPoint” or something else? Thanks.

  11. Alexander Alexander says

    document.elementFromPoint() would work as well. But I’m using the offsetParent/offsetLeft/offsetTop properties to calculate the coordinates of the IMG and A elements. This seems to be a little bit more complicated, but this allows to be less strict in finding the right element if the finger doesn’t exactly hit the element. This can be especially useful when the text is very tiny because of the current zoom level. So if a link or image was not hit but the touch location is just a few pixles away, it’s a good idea to assume that the link or image was meant by the user.

  12. websurfer says

    Thank you…

  13. kungfuslippers says

    @Alexander – would you mind posting a code snippet for you usage of document.elementFromPoint() ?

    thanks

  14. Richard says

    Hi Alexander, First off, great blog. Your tips are very useful. I downloaded your app and noticed that it displays a progress bar. How are you able to determine the progress? I found the following blog http://winxblog.com/?p=6 which explains a way using private apis but I don’t think you can submit to the app store using that method. Thanks.

  15. Alexander Alexander says

    @kungfuslippers
    When using document.elementFromPoint(x,y) you don’t need much code because this function will already return the element at the location x,y. So for example if you define an onClick event handler for the document object in your JavaScript code, you get the click coordinates as part of the “event” argument of the event handler function. You can pass these coordinates directly to the elementFromPoint() function:

    function myOnClickHandler(event) {
        var element = document.elementFromPoint(event.pageX, event.pageY);
        alert("You've clicked on an HTML element named " + element.tagName);
    }
    document.onclick = myOnClickHandler;
  16. Alexander Alexander says

    @Richard
    The progress bar seems to be tricky. But you can get a progress bar without violating the SDK agreement.

    First of all, you should not link to private frameworks like it is explained in the blog post you’ve mentioned. Linking to private frameworks won’t be accepted by Apple and I assume your app would be rejected if you do.

    Of course you can “refine” the way that was explained in the blog post you’ve mentioned, so that it would no longer require to link to private frameworks, but you still have to “cheat” because you would rely on information about how WebView delivers the progress information and this information is private on the iPhone (it is part of the public MacOSX APIs, so it’s not really a secret). But I think Apple would no longer reject your App if you do this “right”, but because of the “cheating” Apple can still say “No”.

    But there’s also a totally legal way to implement a progress bar, which (as usual) requires a little bit of JavaScript. Basically what you need to do is to periodically check the number of resources a web page will load and how many of these resources are already loaded. And based on these numbers you can update the progress bar.

    You start your periodically checks when the UIWebView delegate “webViewDidStartLoad:” is called and stop them when “webViewDidFinishLoad:” is called.

    For example with “document.getElementsByTagName(‘img’)” you can get an array of all image tags and with the “complete” property of an image element you can check if it is already loaded. So just loop through the image elements and count the completed images. And then you can roughly calculate the progress of the page loading.

    You can include other data into your checks to make the results more “smooth”, but this is basically how this works in a SDK-compliant way. And of course the UIWebView object is doing more or less the same when it calculates the progress.

  17. Richard says

    Thanks for the help. I was able to get a choppy version of the progress bar working.

  18. Alexander Alexander says

    @Richard
    The more data you can take into account, the better the results. For example you can also check if the document (the HTML code) itself is still loading (the document height or the size of the source is still rising), check for frame and iframe elements etc.

  19. Renny Nanaiah says

    Hi Alexander,
    Firstly thank you for sharing your knowledge on iPhone development.

    I am trying to set form fields of a web page using the stringByEvaluatingJavaScriptFromString method. It doesn’t seem to be working. The field doesn’t get set & there are no errors thrown.

    This is from the UICatalog example from Apple. I have tried it in the viewDidLoad & the viewDidFinishLoad methods and it doesn’t seem to work. Here is the snippet I am trying. This was added to the end of the viewDidLoad method.

    [self.myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/"]]];

    NSString *mytext = [self.myWebView stringByEvaluatingJavaScriptFromString:@"document. getElementById('sp-searchtext').value='test';"];

    Any suggestions where I am going wrong?

    Thanks.

  20. Alexander Alexander says

    @Renny Nanaiah
    The “viewDidLoad” method is probably the wrong place to add both lines of code. You can add the first line of code here so start loading the web page. It will load in the background so the loadRequest call will immediately return and the web page will not be loaded at that time.
    “viewDidFinishLoad” will be called as soon as the web page is completely loaded and this is where you can add the second line of code from your example.
    Please note that “viewDidFinishLoad” is called when the page has finished loading and not when the page rendering is finished. So depending on the stuff you’re doing, you may need to wait a short time after viewDidFinishLoad is called to give the webView time to render the page.

  21. Renny Nanaiah says

    Hi Alexander,
    Thank you thank you thank you!!! It works!! You have solved a major issue in my prototype that was an integral part of the application and I was beginning to abandon it. I spent a lot of time trying to sort this out & on tons of forums. I wish I had found your site first. As a thank you I am going to buy iCab mobile right now (the features look great).

  22. Praveen says

    Hi Alexander,

    Thank you for this blog.

    I have using your webkit example as it is to see I can call java script from iPhone SDK 3.1. I have used exactly same java script. In my webview, ‘webViewDidFinishLoad’ delegate method the statement
    NSString *path = [[NSBundle mainBundle] pathForResource:@”ModifyLinkTargets” ofType:@”js”]; always returns ‘nil’ for the path even though “ModifyLinkTargets.js” in the resources of the project . If I use another file ‘help.rtf’ instead of “ModifyLinkTargets.js” that is also in the resource, I get right path. Why for ‘.js’ type the path is nil?

  23. Alexander Alexander says

    @Praveen
    You have to make sure that the javascript file is really copied into the resources of the final app. It can happen that XCode does not treat the javascript file as resource file and then it doesn’t copy it into the final app package (in some projects this can be correct, but in our case the JS file must be treated as a resource file).

    In the sidebar of the project window of your app in XCode you’ll find an item named “Target”. Open this item and you’ll find an item whose name is the name of your app. Open this as well and you’ll find several items for the different tasks which are done when building your app. The JavaScript file must be listed within “Copy Bundle Resources”. If it is not listed there, then just drag the JavaScript file from the projects window into the “Copy Bundle Resources” item.

    In case the JavaScript file was added to the “Compile Source” item of the “Target”, remove it from there.

    Then build your App again and it should now be able to find the javascript file.

  24. Praveen says

    Hi Alexander,
    Thank you very much. It works!!! Just as you said , JavaScript file was not within “Copy Bundle Resources.” After I copied the JavaScript file to the bundle, it was able to find the JS file. Thanks again.

  25. mobibob says

    Very well documented and clear explanation. I found other descriptions and none were as clear. Now that I read yours — the other ones might make sense :))

    Good job and thanks for sharing. I have put you in my credits page for my new application.

  26. ashish says

    I am subclassing MainWindow with UIWindow and in that using
    hit test method:
    -(UIView *)hitTestCGPoint)point withEventUIEvent *)event {

    UIView *hitView = [super hitTestoint withEvent:event];
    if (hitView == self)

    return [[self subviews] lastObject];

    else if([hitView isDescendantOfView:myView])
    {

    NSSet *touches = [event allTouches];

    if ([touches count] >= 2) {
    // prevent this
    NSLog(@”Count =2″);
    return [[self subviews] lastObject];
    }

    }
    return hitView;

    }

    But I am getting touches as zero object.
    I debugged and checked event is always having 0 object.
    But responder is having details of the events .
    why is like that and how should I get the tap count

    Any help??

  27. Alexander Alexander says

    @ashish
    I’m looking for touches in the method “sendEvent:” of a subclass of UIWindow. Here I can get all the touches before they get processed and filtered by any other object. Basically what I’m doing is to get the Touches, analyze them and post notifications about certain events which are of interest (like a long tap which might open a contextual menu, a tap with three fingers which might trigger a special feature etc). And of course I call “[super sendEvent:event]” to make sure that the normal event processing will be also done.

    The “hitTest” method might not be called at all times in all circumstances. For example if another object was asked first if it was “hit” and decided it was indeed hit for the event, your object might not be asked anymore for the same event.

  28. ashish says

    Are you trying for a UIwebView? Can you be able to filter touches for UIWebView. I can pass the notifications but filtering is not happening in my case? Can you explain me bit more how are you filtering, if touch appears stationary for some time on screen. ? And how are you preventing it. if you call [super sendEvent:event] it will be dispatched to view then how stopping events will take place?

  29. ashish says

    hitTest I am calling in Main window it has to call at any cost. And it is being called. I can figure out the correct subview which got touched but I am getting NSSet *touches = [event allTouches];
    touches as null?

  30. Alexander Alexander says

    @ashish
    If you analyse the touches within a subclass of UIWindow in the “sendEvent:” method, then you get the touches before the are processed from other objects. This means you are the first one who will see the touches and so you can do almost everything with these events.

    In iCab Mobile I’m using the information about the touches to implement by own contextual menu when tapping on a link for a longer time for example. I’ll probably write a blog post about this topic soon. This should give you some hints how you can work with the touches. I don’t know what feature exactly you want to implement. I’m also not sure yet why you don’t get any touches from the event.

    What I’m doing to detect a long tap is to save the coordinates of the touch down event. I’m also starting a timer. If the timer fires after a certain time and the finger is still down (no touch-up event yet) and the coordinates are still the same (small variations are allowed), I post a “long-tap” notification which another object can use to open a contextual menu for example. The timer is canceled when the touch-up event occurs or if additional fingers touch the screen or if the coordinates do change significantly.

    In general I can only say that overwriting a complex object like UIWebView and overwriting some of its event handling methods will usually not work as expected. The problem here is that the UIWebView object is internally build out of many other elements,like scroller elements, document views, and a couple of other nested view elements. Usually these internal objects will get and process the events but not the UIWebView element itself, which is only the (public) wrapper view of the (private) internal views.

    This is why overwriting UIWindow can be a solution, because here you can access all events before they are delivered to other (internal) views.

  31. ashish says

    thanks Alexander
    //**I don’t know what feature exactly you want to implement. **//
    Want to stop zooming and contextual menus in web View.
    //** I’m also not sure yet why you don’t get any touches from the event. **//
    I am perfectly getting the touches but even if I filter them they are being send to webview.

  32. Alexander Alexander says

    @ashish
    I think you can’t stop zooming and the contextual menu reliable by filtering the events. Detecting these gestures is very complex, so you would need to do all the gesture detection yourself. Also parts of the detection is done in internal objects you can not access.

    So switching off the contextual menu and the zooming should be done in another way.

    In my response to the very first comment for this blog post (see above), I’ve written how to switch off the contextual menu. In case the HTML code you’re loading in the UIWebView is written by yourself, you can control, the contextual menu and the zooming as well by simply adding some META tags. See the following site: http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/Introduction/Introduction.html

  33. chris says

    Hi
    nice link and well explained!
    I am displaying content in UIWebView and moving scroll by using java script
    to go to next page.
    int verScrollPos;
    [webView stringByEvaluatingJavaScriptFromString: [NSString stringWithFormat:@"window.scrollBy(0,%d);",verScrollPos]];
    but the text gets hidden mostly the last part of content.And the hidden part gets repeated on next page.
    I tried to adjust the scroll approximately but this problem comes when I increase the font
    any suggestions?

  34. Alexander Alexander says

    @chris
    This should work fine. I’m doing the same for the “Scrollpad” feature in iCab Mobile and I don’t get this issue. Just make sure that you do not scroll beyond the document boundaries
    (so if the document has only a height of for example 1000px, don’t try to scroll down to a point at 1500px). I’m not sure if this is causing the issue, but you may check this as well.

  35. chris says

    maybe I am not using a proper way to increase the font size
    NSString *jsString = [[NSString alloc] initWithFormat:@”document.getElementsByTagName(‘body’)[0].style.webkitTextSizeAdjust= ‘%d%%’”, textFontSize];

    [webView stringByEvaluatingJavaScriptFromString:jsString];

    Is there any other way to increase font size in a web view?

  36. chris says

    Also font does not changes inside the boxes in WebView by above code.

  37. Alexander Alexander says

    @chris
    Increasing the font size is very difficult. The problem is that by default UIWebView uses its own rules to determine a font size. And it is doing this for each block in the web page individually. If you write the HTML code yourself, you can easily add additional CSS rules
    and als META tags which can overwrite the default behavior of UIWebView. Then you can switch off the automatic text size adjustment and set your own font sizes.

    But if you do this after the web page has loaded and is rendered, it seems that changing the font size (by modifying/adding CSS rules) can’t be done in a reliable way. For some web pages it works, for some it doesn’t.

    I’m still evaluating this as well to find a reliable way to increase the font size of a web page.

  38. land says

    Hi Alexander, I have a problem like this:
    http://stackoverflow.com/questions/1724863/problem-with-the-scrollbar-of-a-uiwebview
    give me some suggestions, please. thanks.

  39. Alexander Alexander says

    @land
    This is a bug of the iPhone OS 3.1.x Firmware. So you can only wait for the next iPhone OS release and hope that Apple has fixed this issue.

  40. Dylan says

    Great tutorial — I stumbled across it trying to solve an issue I’m up against now.

    As you do, I use the JavaScript evaluation trick to access the HTML tree, but I’ve found it’s inconsistent for me. I load an HTML file that dynamically executes some Javascript and places the results in the innerHTML of this tag…

    …and when webViewDidFinishLoad is called, I run this…

    NSString *msString = [self stringByEvaluatingJavaScriptFromString:@"document.getElementById('message').innerHTML"];
    NSLog(@”My results = ‘%@’”, msString);

    …but much of the time msString returns empty. It’s confounding, because if I manually load the same URL in Safari as in the UIWebView, text always shows up in the message tag. The workaround has been just to reload the UIWebView and wait for it to succeed, as it eventually does. Have you seen anything like this? Any suggestions?

  41. Alexander Alexander says

    @Dylan
    The big problem with “webViewDidFinishLoad” is that this method is called when the download of the web site has finished, but this is usually before the rendering of the page has finished. And accessing the HTML tree before the rendering is complete can easily fail.
    Unfortunately there’s no delegate method which is called when the rendering is finished, so the only workaround is to wait for a short time (maybe 1/4 – 1/3 second) after webViewDidFinishLoad was called to access the HTML tree. This should give the HTML renderer enough time to finish the rendering.

  42. Dylan says

    Aha — that’s precisely it — thanks so much. I’ve implemented that workaround instead of mine — simply waiting beats reloading. Hopefully they’ll add a delegate method for when rendering finishes!

  43. Joe Pritchard says

    Hi Alexander, not sure if this is entirely webkit related but it might be and if it is you would be helping loads!

    Is there anyway to overide the event that is fired off when the user clicks a select tag (drop down) that displays the picker with previous , next etc above it?

    I want to use a custom picker with smaller text and I can get the custom one displaying through js back to obj c but cannot disable the original one.

    Any ideas?

  44. Alexander Alexander says

    @Joe Pritchard
    No, there’s no easy way to replace the “picker” control for the SELECT tag.

    I think the only solution would be to replace the SELECT element by your own replacement element which could be a normal button, link or image. This means that you have to remove or hide the SELECT element and insert your own element into the code of the form. And when the user taps this replacement element, you can open your custom controller without risking that the standard picker opens as well.

    But because this solution requires, that you modify the form elements of the page, you have to make sure that you restore the form to its original state before submitting the form. And if the form can be modified by JavaScript code from the web page itself (for example when the page populates the SELECT element with new items, when the user activates certain other form elements), you can get in trouble.

    But I think for most web pages this would probably work.

  45. chris says

    Hi,

    have you find any solution?
    ….
    I’m still evaluating this as well to find a reliable way to increase the font size of a web page.

    I am also trying on that but no success till.

  46. Alexander Alexander says

    @chris
    I haven’t found a really good solution yet. I’ve had the best luck with a solution which would destroy the old layout. The big problems are text boxes in web pages whose width is based on the current font size. In these cases increasing the font size will also increase the width of the text boxes and then even if the text is now larger in theory, nothing has changed in reality on the iPhone, because there’s still the same amount of text in one line. Most of the time the readability is even worse than before. So the only option seems to be to allow that all boxes have a variable width. And this will usually destroy the layout.

  47. chris says

    @Alexander, but how ereaders are formatting the text.
    like stanza and ereader are in app store.
    what is approach when they increase or decrease the font. are they editing the CSS for that?

  48. Alexander Alexander says

    @chris
    If you load the web pages from the web, you don’t have full control over the source code, this is why changing the font size can be difficult. If you have full control over the HTML source, which means if you write the HTML code yourself, then there’s no problem anymore in changing the font size, because you can write your source in a way which has no side effects when changing certain properties afterwards. Also you can decide the default font size from the beginning.

    And eBook Readers do have much more control over the HTML code, also the code itself is already optimized for the small device, without any multicolumn layouts, without tables and other stuff, which makes it so difficult to change the layout.

  49. David Brossa says

    Wouldn’t this be more inclusive? I often use my own target names in links (ex target=”_pdf”)

    if ( target && ( (target != ‘_self’) || (target != ‘_parent’) || (target != ‘_top’) ) )

  50. Alexander Alexander says

    @David Brossa
    Depending of your needs, you can be much more (or maybe less) specific when checking for target names. This blog post should not cover all possible aspects, it should show the general idea how you can solve certain tasks on the iPhone.

    According to the target name “_pdf” I have to say, target names with an underscore as the first character are “reserved” and should not be used for your own target names. Though when only used internally within your app and not in real web pages, this might be OK.

1 2

Continuing the Discussion

  1. Saving an Image from UIWebView | Steili.com linked to this post on May 1, 2010

    [...] iCab Blog for the elementFromPoint idea – see Alexander’s comment on 9/1 11:55am, and the support [...]



Some HTML is OK

or, reply to this post via trackback.