<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iCab Blog</title>
	<atom:link href="http://www.icab.de/blog/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.icab.de/blog</link>
	<description>iCab related stuff; Mac, iPhone and Cocoa programming</description>
	<lastBuildDate>Mon, 17 Oct 2011 15:03:14 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>elementFromPoint() under iOS 5</title>
		<link>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/</link>
		<comments>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/#comments</comments>
		<pubDate>Mon, 17 Oct 2011 14:58:07 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Web Technology]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[UIWebView]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=214</guid>
		<description><![CDATA[The JavaScript call elementFromPoint(x,y) can be used to find the element of a web page at a certain coordinate. If you have used this call in the past on a web page or in a native iOS App which was developed for iOS 4.x or older, you&#8217;ll notice that your web page or App might [...]]]></description>
			<content:encoded><![CDATA[<p>The JavaScript call elementFromPoint(x,y) can be used to find the element of a web page at a certain coordinate. If you have used this call in the past on a web page or in a native iOS App which was developed for iOS 4.x or older, you&#8217;ll notice that your web page or App might fail when used under iOS 5. Under iOS 5 the elementFromPoint(x,y) call finds different elements or even returns null instead of an element. It looks like the call is now broken. But this is not the case, in fact, under iOS 5 it works correct the first time, it was broken before.</p>
<p>elementFromPoint(x,y) was defined to return the element at the given coordinates within the view port (the visible area) of the web page, or null (if the coordinates are outside of the viewport). The coordinates are measured relative to the origin of the view port. This is how iOS 5 finally works.</p>
<p>Before (iOS 4.x and older), elementFromPoint(x,y) completely ignored the view port. It measured the coordinates relative to the origin of the document. And even elements outside of the visible area could be found. This behavior seems to make much more sense than the new iOS 5 behavior, but according to the official JavaScript specification, it&#8217;s not the correct behavior.</p>
<p>The different behavior between iOS 5 and older iOS versions can cause some serious problems. The coordinate systems are no longer compatible, so when the web page or App has to run under old an new iOS versions, it is necessary to find out the correct coordinate system that is used.</p>
<p>In a native iOS App you could simply check the iOS version, and based on its value you can decide which coordinate system you need to use when calling elementFromPoint(x,y). But when writing a web page, this is not so easy: the iOS version is not exposed to the web page (it might be part of the UserAgent information, but because almost all browser do allow to use a fake userAgent information, this information is not reliably at all). Also on the Mac and on other platforms different WebKit releases might be used which do use different coordinate systems for the elementFromPoint(x,y) call as well. Therefore it makes sense to find a way to identify the coordinate system independent of the iOS version, and if necessary correct the coordinates.</p>
<p>At first when we compare the two coordinate systems, we notice that the coordinates are offset by the scroll location. If we scroll the web page so that the top left corner of the page is visible, both coordinate systems are the same. The origin of the view port is identical to the origin of the web page. And the scroll offset is also 0 in both directions. If you scroll down 100 px, the origin (the coordinate (0,0)) of the viewport is located at the coordinate (0,100) of the web page. So the scroll offset is exactly the offset between the two coordinate systems. Therefore, transforming one coordinate system into the other is very easy. We only need to add or subtract the actual scroll offsets.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function documentCoordinateToViewportCoordinate(x,y) {
  var coord = new Object();
  coord.x = x - window.pageXOffset;
  coord.y = y - window.pageYOffset;
  return coord;
}

function viewportCoordinateToDocumentCoordinate(x,y) {
  var coord = new Object();
  coord.x = x + window.pageXOffset;
  coord.y = y + window.pageYOffset;
  return coord;
}
</pre>
<p>These JavaScript functions take a coordinate of one system and transform them into a coordinate of the other system.</p>
<p>But in order find out if and which of the functions we need to use, we have to find out, in which coordinate system the call elementFromPoint(x,y) expects the coordinates. To do this we use the fact that elementFromPoint() returns null when the coordinates are outside of the view port, when it expects coordinates measured relative to the viewport (as noted above, when the coordinates are relative to the origin of the document, elementFromPoint() will always return an element, even when outside of the visible area, so we can distinguish between the two cases).<br />
Good test coordinates would be (0, window.pageYOffset + window.innerHeight -1) and (window.pageXOffset + window.innerWidth -1, 0), for vertical scrolling and horizontal scrolling. As noted above, when no scrolling is done, both coordinate systems are identical, and we don&#8217;t need to take care about anything. But if the page is scrolled, we need to check which system is used. The test coordinates take the actual scroll offset and add the width or height of the visible area (this is the innerWidth and innerHeight of the &#8220;window&#8221; object) and subtract 1. This makes sure that the coordinate addresses the very last pixel line or column of the visible area measured relative to the document origin. This is always a valid document-based coordinate which lies within the document boundaries (a coordinate outside of the document boundaries would return null even with the elementFromPoint() call for the document-based coordinate system). If the page is scrolled by at least one single pixel, the test coordinates would lie outside of the viewport, when interpreted as relative to the viewport, so elementFromPoint() would return null. When elementFromPoint() would interpret them relative to the document, these coordinates are always valid and would always return an element. And this is how we can easily detect, which coordinate system elementFromPoint() is using.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromPointIsUsingViewPortCoordinates() {
  if (window.pageYOffset > 0) {     // page scrolled down
    return (window.document.elementFromPoint(0, window.pageYOffset + window.innerHeight -1) == null);
  } else if (window.pageXOffset > 0) {   // page scrolled to the right
    return (window.document.elementFromPoint(window.pageXOffset + window.innerWidth -1, 0) == null);
  }
  return false; // no scrolling, don't care
}
</pre>
<p>We can combine this to one custom elementFromPoint() function that is using a document-based coordinate system as input and will internally do all the magic for us:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromDocumentPoint(x,y) {
  if (elementFromPointIsUsingViewPortCoordinates()) {
    var coord = documentCoordinateToViewportCoordinate(x,y);
    return window.document.elementFromPoint(coord.x,coord.y);
  } else {
    return window.document.elementFromPoint(x,y);
  }
}
</pre>
<p>And the counterpart for viewport-based coordinates:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromViewportPoint(x,y) {
  if (elementFromPointIsUsingViewPortCoordinates()) {
    return window.document.elementFromPoint(x,y);
  } else {
    var coord = viewportCoordinateToDocumentCoordinate(x,y);
    return window.document.elementFromPoint(coord.x,coord.y);
  }
}
</pre>
<p>So instead of using elementFromPoint() directly, you simply use elementFromViewportPoint() or elementFromDocumentPoint() instead, depending of the coordinates you have to deal with. It will then work correct in old and new WebKit releases.</p>
<p>Please note: if you use the code of my older blog post &#8220;<a href="http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/">Customize the contextual menu of UIWebView</a>&#8221; in your projects, you need to update this as well, because it also uses the elementFromPoint() call. But this should be really easy to do.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Adding JavaScript files as &#8220;Resources&#8221; to an XCode project</title>
		<link>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/</link>
		<comments>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/#comments</comments>
		<pubDate>Tue, 02 Aug 2011 18:53:21 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[XCode]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=202</guid>
		<description><![CDATA[Because of the limited API of the UIWebView class in the iOS, developers are often forced to use JavaScript in order to implement features which are not supported by the UIWebView class itself. The amount of JavaScript code that is needed can easily reach limits you can&#8217;t manage by using normal strings anymore. So you [...]]]></description>
			<content:encoded><![CDATA[<p>Because of the limited API of the UIWebView class in the iOS, developers are often forced to use JavaScript in order to implement  features which are not supported by the UIWebView class itself. The amount of  JavaScript code that is needed can easily reach limits you can&#8217;t manage by using normal strings anymore. So you would like to write the JavaScript code in separate files, organized by task, like you would do this for Objective C classes. In the sample code in my blog posts I&#8217;ve done this as well. Though this is not very complicated, there are nevertheless a lot of developers having problems with this. This is why I would like to explain more detailed, what you need to do, and why there are these difficulties.</p>
<p>In general, when you have to use JavaScript code to do certain tasks on a web page, you need to inject this code into the web page when it is loaded. The UIWebView provides a delegate method &#8220;webViewDidFinishLoad:&#8221; which is called whenever a web page has finished loading. This is the correct location to inject your JavaScript code into the web page. And to inject JavaScript code into a web page you can use the method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; of the UIWebView class.</p>
<p>In most cases you can just use the view controller which manages your UIWebView object as delegate for the UIWebView. When defining the view hierarchy and the view controller in Interface Builder, you can set the delegate for the UIWebView directly within Interface Builder, so you even don&#8217;t need to write any code for this. But you can also set the delegate in code, for example in the &#8220;viewDidLoad&#8221; method of the view controller. And in addition, the delegate (the view controller) has to implement the &#8220;webViewDidFinishLoad:&#8221; method, in order to get notified when the web page has finished loading.  Please note that the &#8220;loadRequest:&#8221; method of UIWebView loads the page asynchronously, so when  this method returns you can not assume that anything from the page has already loaded. Which means it is essential that you wait until the method &#8220;webViewDidFinishLoad:&#8221; is called.</p>
<p>The following example shows how this can be done in a view controller implementation (this example assumes that the JavaScript code is stored in a file &#8220;JavaScriptFile.js&#8221; within the resources of your App):</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)viewDidLoad
{
  // If not done in the InterfaceBuilder, you need to set the delegate for the UIWebView object
  // so that you're notified when the page has loaded (via "webViewDidFinishLoad:")
  [webView setDelegate:self];

  // ...
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  // Loading and injecting your JavaScript code into the webView, so you can actually use it
  // in the context of the web page that was loaded...
  NSString *path = [[NSBundle mainBundle] pathForResource:@"JavaScriptFile" ofType:@"js"];
  NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
  [webView stringByEvaluatingJavaScriptFromString:jsCode];

  // ...
}</pre>
<p>This is more or less everything you need to care about when programming JavaScript helper functions and injecting them into the web view objects. You can use the   method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; to call your helper functions afterwards and getting the results from your JavaScript functions as Cocoa string.</p>
<p>But there&#8217;s another detail you need to care about which is related to XCode itself. And this seems to be something many users are not aware of and what can cause your App to fail.</p>
<p>When building an App, XCode is doing this is several phases, like compiling the source code files, linking the object files and frameworks and copying all resources into the App bundle. The problem here is that XCode recognizes a JavaScript file as &#8220;Source Code&#8221; and therefore adds this file into the &#8220;Compile Sources&#8221; phase. But this is not correct in our case because the JavaScript file can not be compiled and linked to the object code of our App. The JavaScript file will be executed in the context of a web page within the UIWebView, so it&#8217;s not part of the &#8220;native&#8221; executable code of the App. The JavaScript code must be treated as &#8220;Resource file&#8221; and added as Resource file into the App bundle. Then we can load the file as resource within the App and inject its content to the web view.</p>
<p>XCode lets you inspect and edit the build phases of your project. Usually you don&#8217;t need to care about this because XCode is doing everything correct automatically. But not when adding JavaScript files. So you have to make sure that all JavaScript files which are listed in the &#8220;Compile Source&#8221; phase are moved to the &#8220;Copy Bundle Resources&#8221; phase. If you forget this, the JavaScript file won&#8217;t be copied to the resources of the App and when the App is running, it won&#8217;t be able to find and load the JavaScript files. So, always make sure that the JavaScript files are listed in the correct build phase.</p>
<p>The following screenshots will show you, where you find the &#8220;build phase&#8221; settings in your XCode project. The red circles indicate where you need to click to open lists or tables to find the settings.</p>
<p>This shows the location in XCode 3.x:<br />
<a href="http://www.icab.de/blog/wp-content/uploads/xc3.jpg"><img class="alignnone size-full wp-image-203" title="XCode 3" src="http://www.icab.de/blog/wp-content/uploads/xc3-small.jpg" alt="" width="181" height="241" /></a></p>
<p>And this shows the location in XCode 4.x:<br />
<a href="http://www.icab.de/blog/wp-content/uploads/xc4.jpg"><img class="alignnone size-medium wp-image-205" title="XCode 4" src="http://www.icab.de/blog/wp-content/uploads/xc4-small.jpg" alt="" width="475" height="268" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>iCab Mobile Modules and Apple</title>
		<link>http://www.icab.de/blog/2011/03/26/modules-for-icab-mobile-and-apple/</link>
		<comments>http://www.icab.de/blog/2011/03/26/modules-for-icab-mobile-and-apple/#comments</comments>
		<pubDate>Fri, 25 Mar 2011 23:46:55 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[modules]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=190</guid>
		<description><![CDATA[Recently I received a phone call from Apple. They asked me to remove the ability to download and install modules from the internet in iCab Mobile. So as of iCab Mobile 4.6, the ability to download and install modules is no longer working. But the modules feature is not gone. I&#8217;ve simply included all modules [...]]]></description>
			<content:encoded><![CDATA[<p>Recently I received a phone call from Apple. They asked me to remove the ability to download and install modules from the internet in iCab Mobile. So as of iCab Mobile 4.6, the ability to download and install modules is no longer working.</p>
<p>But the modules feature is not gone. I&#8217;ve simply included all modules which could be downloaded before from my server directly in iCab Mobile, so all the modules are automatically available without downloading them before. So for most users nothing serious has changed.</p>
<p>The only problem is with the modules (written by users) which I don&#8217;t know of. Users can no longer write and share their own modules themselves. If you know of such a module or if you still want to write a module, please contact me. I can add these modules to the next updates of iCab Mobile and this way the modules can be still shared with all iCab Mobile users.</p>
<p>I really can&#8217;t say that I like Apple&#8217;s decision, and technically it doesn&#8217;t make much sense.  &#8221;Modules&#8221; for iCab Mobile are simply a more comfortable variation of bookmarklets, and these are still allowed by Apple (and I assume Apple can not forbid that the user will save and use bookmarklets). Maybe if I would have called the modules &#8220;smart bookmarks&#8221; and would have made installing them much more complicated, Apple would have never asked to remove the ability to download them from the internet. The great user experience of installing modules has probably created a suspicion that these modules are more than just a piece of JavaScript code. From a pure technical point of view, if Apple does not allow to download modules (JavaScript code), Apple would also have to disallow to load web pages in general, because these do also contain JavaScript code.</p>
<p>I think the current solution in shipping all modules together with iCab Mobile should be fine for all users and should not cause much trouble. Hopefully one day Apple will rethink their decision and will allow module downloads and also some other stuff again.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2011/03/26/modules-for-icab-mobile-and-apple/feed/</wfw:commentRss>
		<slash:comments>58</slash:comments>
		</item>
		<item>
		<title>Changing the range of the zoom factor for UIWebView objects</title>
		<link>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/</link>
		<comments>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/#comments</comments>
		<pubDate>Mon, 27 Dec 2010 21:23:45 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=184</guid>
		<description><![CDATA[When using a UIWebView to display a web page, you have probably noticed that the maximum zoom factor is very limited. In mobile Safari you can zoom in much farther than in your own App. This blog post will explain, how you can increase the maximum zoom factor. The UIWebView class does not have any [...]]]></description>
			<content:encoded><![CDATA[<p>When using a UIWebView to display a web page, you have probably noticed that the maximum zoom factor is very limited. In mobile Safari you can zoom in much farther than in your own App. This blog post will explain, how you can increase the maximum zoom factor.</p>
<p>The UIWebView class does not have any methods to set the zoom factor. But this can be done with certain instructions within the HTML code. The META tag can be used to configure the viewport, which includes the initial, the minimum and the maximum zoom factor.</p>
<p>The META tag can look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;meta name="viewport" content="minimum-scale=0.6; maximum-scale=5;  initial-scale=1; user-scalable=yes; width=640"&gt;</pre>
<p>The following viewport parameters can be used:</p>
<ul>
<li><strong>minimum-scale</strong>:<br />
This is the minimum zoom factor that is allowed. The default value is 0.25, the range is from &gt;0 to 10.</p>
</li>
<li><strong>maximum-scale</strong>:<br />
This is the maximum zoom factor that is allowed. The default value is 1.6, the range is from &gt;0 to 10</p>
</li>
<li><strong>initial-scale</strong>:<br />
This is the zoom factor that is used when the web page is loaded before the user zooms in or out. The default value is calculated so that the web page fits in the visible area. But the final range will be also within the range from the minimum to the maximum factor.</p>
</li>
<li><strong>user-scalable</strong><br />
This parameter can be used to allow or disallow that the user can zoom the web page.</p>
</li>
<li><strong>width</strong>:<br />
This parameter defines the width for the viewport. By default the width is set to 980 px on an iPhone. The possible range for this value is from 200 to 10000. The special value &#8220;device-width&#8221; represents the width of the device (which is 320 on an iPhone and 768 on an iPad). Please note that the device width is not the same as the width of the user interface. The device width is always represented by the width of the device in portrait orientation (the &#8220;natural&#8221; orientation of the device).  If we want to increase the maximum zoom factor of a web page (the default factor is 1.6), we only need to add a META tag in the HTML code of the page, which defines the maximum zoom factor. If you can change the original HTML code, you can simply add the META tag and you&#8217;re done. If your App loads web pages from the internet and you can&#8217;t change the original HTML code, you have to write JavaScript code which creates a META tag and adds it to the HTML code of the web page. If you&#8217;ve read my other blog posts, you&#8217;ll already know how this works.
</li>
<li><strong>height</strong>:<br />
This parameter defines the height for the viewport. Usually this is calculated based on the width.
</li>
</ul>
<p>The JavaScript code could look like this:</p>
<p>File: <span style="color: #993300;">IncreaseZoomFactor.js</span>:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function increaseMaxZoomFactor() {
  var element = document.createElement('meta');
  element.name = "viewport";
  element.content = "maximum-scale=10";
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(element);
}</pre>
<p>Within the <span style="color: #003300;">webViewDidFinishLoad:</span> delegate method of the UIWebView object you can then inject this JavaScript code into the web page and call the function to increase the maximum zoom factor:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  NSString *path = [[NSBundle mainBundle] pathForResource:@"IncreaseZoomFactor" ofType:@"js"];
  NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
  [webView stringByEvaluatingJavaScriptFromString:jsCode];

  [webView stringByEvaluatingJavaScriptFromString:@"increaseMaxZoomFactor()"];
}</pre>
<p>When dealing with web pages from the web, you have to be careful because many of these web pages do already use META tags to change the zoom factor or other viewport parameters. If you add the new META tag as described above, you&#8217;ll overwrite the maximum-scale parameter and the other parameters remain unchanged. Which means, if the same parameter is defined in multiple META tags, the last one wins. Most of the time this is fine, but in some cases this can have some side effects.</p>
<p>For example if the web page defines some parameters which would result in an initial zoom factor of 4, but because the page does not define the maximum-scale parameter, the default value of 1.6 would also limit the initial zoom factor to 1.6. If you now increase the maximum-scale parameter, the initial zoom factor will be increased as well because it is no longer limited by the maximum zoom factor. If this can be critical for your App, you need to check for the existing parameters and based on their values you may need to define some other parameters as well (for example you may explicitly add the initial-scale parameter with a value of 1.6 to prevent that the maximum zoom factor initially zooms the page much more than it would do without your modification).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Scaling images and creating thumbnails from UIViews</title>
		<link>http://www.icab.de/blog/2010/10/01/scaling-images-and-creating-thumbnails-from-uiviews/</link>
		<comments>http://www.icab.de/blog/2010/10/01/scaling-images-and-creating-thumbnails-from-uiviews/#comments</comments>
		<pubDate>Fri, 01 Oct 2010 19:58:33 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=174</guid>
		<description><![CDATA[In this blog post I would like to show how you can do some common image-related tasks, like scaling an UIImage object or creating a thumbnail image from a UIView. I&#8217;m implementing these features as category for the UIImage class, so these can be used with all UIImage objects without the need to subclass the [...]]]></description>
			<content:encoded><![CDATA[<p>In this blog post I would like to show how you can do some common image-related tasks, like scaling an UIImage object or creating a thumbnail image from a UIView. I&#8217;m implementing these features as category for the UIImage class, so these can be used with all UIImage objects without the need to subclass the UIImage class.</p>
<h3>Scaling UIImages</h3>
<p>Scaling images is important for example when you want to create thumbnail images which are used as an overview for real documents or larger pictures. The standard UIImage class doesn&#8217;t provide a method to scale images, but scaling an image isn&#8217;t very complicated. You can do it with just 4 lines of code&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">UIImage *originalImage = ...;
CGSize destinationSize = ...;

UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();</pre>
<p>At first we have to create and start an image context with the size of the resulting image. All drawing operations which take place after the UIGraphicsBeginImageContext() call will be done in the newly created image context. This means, we are no longer drawing on the screen. All we need to do now is to draw the original image in the size of the final image. The drawing takes place in the newly created image context. Afterwards we can use UIGraphicsGetImageFromCurrentImageContext() to get an image from the image context. Finally we need to call UIGraphicsEndImageContext() to make sure that the system will release the image context again, because we don&#8217;t need it anymore. And of course the drawing will take place on the screen again after calling this function.</p>
<h3>Creating an UIImage from a UIView</h3>
<p>If you need an image from the content of a UIView object (like a whole web page that is displayed in a UIWebView object) or even a complex hierarchy of UIView objects so you can save it as PNG or JPEG image or use it as thumbnail image, you can do this also with just a few lines of code&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">UIView *view = ...;

CGSize size = [view bounds].size;
UIGraphicsBeginImageContext(size);
[[view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();</pre>
<p>The code is very similar to the code from above to scale the image. But instead of drawing an image into the new image context, we use the &#8220;renderInContext:&#8221; method to draw the content of the view (which includes its subviews) into the image context. The resulting image will have the same size as the original UIView.</p>
<h3>Retina Display</h3>
<p>With the new iPhone 4 and the 4th generation iPod Touch, the iOS does also support high resolution screens. But the larger screen resolution is not used to display more content, instead it is used to display a sharper image with more details. Basically the iOS uses the same coordinate system on all screens with a size of  480&#215;320 points. The only difference of the retinal displays is that there&#8217;re additional pixels between these points. Most drawing functions of the iOS automatically use these additional pixels to draw sharper and more detailed content. But there are exceptions when the iOS can not create more details itself. For example for bitmap images (UIImage objects), there&#8217;s no way to automatically generate a more detailed image for the retina displays. So iOS 4.0 introduces a new &#8220;scale&#8221; property for images. This property can be 1.0 (the default value) which is used for standard resolution and 2.0 for the more detailed retina resolution with twice as much details. If the &#8220;scale&#8221; property has a value of 1.0, one pixel of the image will be drawn on 2*2 (= 4 pixels) on the retina display, if the &#8220;scale&#8221; property has a value of 2, one image pixel is drawn on one screen pixel.</p>
<p>The above code will work with all iOS releases (including iOS 3.x which is the latest version that can be used on the first generation iPhone and iPod Touch devices), but it will always use the default &#8220;scale&#8221; property of 1.0 for images. Which means the code can not improve the image quality on the new retina displays. In order to get more detailed images on retina displays, we need to use &#8220;UIGraphicsBeginImageContextWithOptions()&#8221; instead of &#8220;UIGraphicsBeginImageContext()&#8221;, so we can define the &#8220;scale&#8221; property for the new image context as well.  But &#8220;UIGraphicsBeginImageContextWithOptions()&#8221; is only available since iOS 4, so we have to check if we can use this function. If it is not available or if the device does not have a retina display, we still need to use the default scaling of 1.0 &#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
     if ([[UIScreen mainScreen] scale] == 2.0) {
        UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
     } else {
        UIGraphicsBeginImageContext(size);
    }
} else {
    UIGraphicsBeginImageContext(size);
}</pre>
<h3>Combining everything into a Objective-C Category</h3>
<p>Now it&#8217;s time to combine all of the above and create a category for the UIImage class. I&#8217;m implementing three simple methods to create a new UIImage from a UIView or UIImage object.</p>
<ul>
<li>&#8220;<strong>imageWithImage:scaledToSize:</strong>&#8221; creates an image from a UIImage and scales it to a given size</li>
<li>&#8220;<strong>imageFromView</strong>:&#8221; creates an image from a UIView</li>
<li>&#8220;<strong>imageFromView:</strong>&#8221; creates an image from a UIView and scales it to a given size.</li>
</ul>
<p><span style="color: #993300;">MyImage.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface UIImage (MyImage)
+ (UIImage*)imageFromView:(UIView*)view;
+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize;
+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize;
@end</pre>
<p><span style="color: #993300;">MyImage.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "MyImage.h"

@implementation UIImage (MyImage)

+ (void)beginImageContextWithSize:(CGSize)size
{
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
        } else {
            UIGraphicsBeginImageContext(size);
        }
    } else {
        UIGraphicsBeginImageContext(size);
    }
}

+ (void)endImageContext
{
    UIGraphicsEndImageContext();
}

+ (UIImage*)imageFromView:(UIView*)view
{
    [self beginImageContextWithSize:[view bounds].size];
    BOOL hidden = [view isHidden];
    [view setHidden:NO];
    [[view layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    [view setHidden:hidden];
    return image;
}

+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
    UIImage *image = [self imageFromView:view];
    if ([view bounds].size.width != newSize.width ||
            [view bounds].size.height != newSize.height) {
        image = [self imageWithImage:image scaledToSize:newSize];
    }
    return image;
}

+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
    [self beginImageContextWithSize:newSize];
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    return newImage;
}
@end</pre>
<h3>Optimizations</h3>
<p>Scaling images can temporarily require lots of additional memory, depending of the size of the images. The original image is first drawn in an image context. This image context must provide a bitmap buffer internally in which the drawing takes place. This buffer needs some memory. Also when the final image is created from this image context, some more memory is required. Only afterwards it is possible to free up the memory again by releasing the image context and maybe the original image.</p>
<p>For retina displays, the bitmap buffers for images where the &#8220;scale&#8221; property has a value of 2.0 are four times larger as for non-retina displays (scale = 1.0). So a single full screen image requires approximately 2.5 MB of memory. And because multiple bitmap buffers are required while scaling, the memory requirements can be much higher than without retina display.</p>
<p>This is not a big deal for the iPhone 4 which has much more main memory (512 MB) than all other devices. But for the 4th generation iPod Touch, the increased memory usage for the image buffers for the retina display is a problem. The 4th generation iPod Touch only has 256 MB of main memory, so it&#8217;s almost always running on its memory limits.</p>
<p>So to reduce the memory usage when creating a small thumbnail image from a UIView (which means you&#8217;re only interested in a scaled-down(!) image from a UIView), you should first create an image from the UIView with a &#8220;scale&#8221; property of 1.0. This will reduce the memory usage for the newly created image from the view. And because you&#8217;re about to scale down this image, it doesn&#8217;t matter that it doesn&#8217;t have all the details it would have with scale=2.0. When you then scale down this image using a &#8220;scale&#8221; property of 2.0, the final (smaller) image can still can get all the details it needs from the image you&#8217;ve created before.</p>
<p>If you have to create many images form large UIViews, you may consider to try to reduce the memory requirements this way (especially for iPod Touch with its limited amount of RAM). If you only need to deal with a few smaller views, you probably don&#8217;t need to care about this.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/10/01/scaling-images-and-creating-thumbnails-from-uiviews/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Customize the contextual menu of UIWebView</title>
		<link>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/</link>
		<comments>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/#comments</comments>
		<pubDate>Sun, 11 Jul 2010 17:15:26 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=159</guid>
		<description><![CDATA[When you tap and hold your finger on a link in a UIWebView object for about a second, a contextual menu will open and provides a few choices: copy the URL to the pasteboard, open the link and a button to close the menu again. Unfortunately there&#8217;s no API available to add additional menu items [...]]]></description>
			<content:encoded><![CDATA[<p>When you tap and hold your finger on a link in a UIWebView object for about a second, a contextual menu will open and provides a few choices: copy the URL to the pasteboard, open the link and a button to close the menu again. Unfortunately there&#8217;s no API available to add additional menu items to this contextual menu. But if you look at iCab Mobile, you&#8217;ll notice that there are a lot of additional menu items here. How is this done?</p>
<p>First of all, you really can&#8217;t add additional menu items to the default ones of the standard contextual menu. But you can switch off the contextual menu using a certain CSS property. So the solution would be to switch off the default menu and implement your own from scratch. And to implement your own contextual menu, you have to first catch the tab-and-hold gesture, get the coordinates of the finger on the screen, translate these coordinates into the coordinate system of the web page and finally look for the HTML elements at this location. Based on the HTML elements, you can then decide which menu items to include in the contextual menu, and then create and display the contextual menu.</p>
<p>The first step is to switch off the default contextual menu of UIWebView. This is easy because we only need to set the CSS property &#8220;-webkit-touch-callout&#8221; to &#8220;none&#8221; for the body element of the web page. We can do this using JavaScript in the UIWebView delegate method &#8220;webViewDidFinishLoad:&#8221;&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)webViewDidFinishLoad:(UIWebView *)webView
{
   [webView stringByEvaluatingJavaScriptFromString:@"document.body.style.webkitTouchCallout='none';"];
}</pre>
<p>Now, the default Contextual menu won&#8217;t open anymore.</p>
<p>The next step is to catch the &#8220;tap-and-hold&#8221; gesture. If your App only needs to run on iOS 3.2, iOS 4.0 or newer, you can simply use the new UIGestureRecognizer API. But if you still want to address the 1st generation iPod Touch and iPhone devices (where you can install at most iOS 3.1) where these UIGestureRecognizer classes are not available, you need to do a little bit more. I&#8217;ll show here a solution which will work with iOS 3.0 as well, so it&#8217;s compatible to all iPhone, iPad and iPod Touch devices.</p>
<p>A big problem when you need to catch gestures within UIWebView is that UIWebView internally consists of many nested views, and only the inner ones do respond to events and gestures. Which means, if you subclass UIWebView and overwrite the methods &#8220;touchesBegan:withEvent:&#8221;, &#8220;touchesMoved:withEvent:&#8221; etc., you&#8217;ll notice that these methods won&#8217;t be called. The events are delivered directly to these private inner views which are actually doing all the work. And if you overwrite &#8220;hitTest:withEvent:&#8221;, which is used by the iOS to find the view to which the events will be delivered, you would be able to get the touch events, but then you would have to deliver the events to these inner views yourself so these can still do their work. And because these inner views are private and the relationships between these views are unknown, it can be extremely dangerous to mess with the events here. </p>
<p>A much easier way would be to subclass UIWindow and overwrite the method &#8220;sendEvent:&#8221; of the UIWindows class. Here you&#8217;ll get all events before they are delivered to the views. And we can deliver the tap-and-hold events using the notification manager to the rest of the app. Each object that is interested in this gesture can listen to this notification.</p>
<p>Recognizing the tap-and-hold gesture is not very complicated. What we need to do is to save the screen coordinates of the finger when the finger first touches the screen. At that time we will also start a timer which fires after about a second. As soon as another finger touches the screen, the finger moves or the touch event is canceled, we invalidate the timer because then it can not be a simple tap-and-hold gesture anymore. If the timer fires, we can be sure that a single finger has touched the screen for about a second without moving and then we&#8217;ve recognized the &#8220;tap-and-hold&#8221; gesture. We post the gesture as notification.</p>
<p>This is the implementation of the UIWindow subclass:</p>
<p><span style="color: #993300;">MyWindow.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
@interface MyWindow : UIWindow
{
   CGPoint    tapLocation;
   NSTimer    *contextualMenuTimer;
}
@end</pre>
<p><span style="color: #993300;">MyWindow.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "MyWindow.h"

@implementation MyWindow

- (void)tapAndHoldAction:(NSTimer*)timer
{
   contextualMenuTimer = nil;
   NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
             [NSNumber numberWithFloat:tapLocation.x],@"x",
             [NSNumber numberWithFloat:tapLocation.y],@"y",nil];
   [[NSNotificationCenter defaultCenter] postNotificationName:@"TapAndHoldNotification" object:coord];
}

- (void)sendEvent:(UIEvent *)event
{
   NSSet *touches = [event touchesForWindow:self];
   [touches retain];

   [super sendEvent:event];    // Call super to make sure the event is processed as usual

   if ([touches count] == 1) { // We're only interested in one-finger events
      UITouch *touch = [touches anyObject];

      switch ([touch phase]) {
         case UITouchPhaseBegan:  // A finger touched the screen
            tapLocation = [touch locationInView:self];
            [contextualMenuTimer invalidate];
            contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8
                        target:self selector:@selector(tapAndHoldAction:)
                        userInfo:nil repeats:NO];
            break;

         case UITouchPhaseEnded:
         case UITouchPhaseMoved:
         case UITouchPhaseCancelled:
            [contextualMenuTimer invalidate];
            contextualMenuTimer = nil;
            break;
      }
   } else {                    // Multiple fingers are touching the screen
      [contextualMenuTimer invalidate];
      contextualMenuTimer = nil;
   }
   [touches release];
}
@end</pre>
<p>Some remarks for the UITouchPhaseMoved phase: it can be sometimes useful to allow small movements on the screen. You can add some code to check for the distance the finger has moved and if it is within a certain range, you just don&#8217;t abort the timer. This helps users which have difficulties to hold the finger still for about a second.</p>
<p>Another important thing you have to do when the App window is created by a NIB file: you have to change the UIWindow class of the window within the NIB file in Interface Builder to the new subclass MyWindow. This way the window is created with our subclass, which is important.</p>
<p>The next step is to listen for the &#8220;TapAndHoldNotification&#8221; notification  within the UIWebView delegate and when this notification is received, we need to check which HTML element was touched.</p>
<p>When initializing the UIWebView delegate, we need to add the delegate as observer for the notification&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextualMenuAction:) name:@"TapAndHoldNotification" object:nil];
}</pre>
<p>And here&#8217;s the method &#8220;contextualMenuAction:&#8221;&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)contextualMenuAction:(NSNotification*)notification
{
   CGPoint pt;
   NSDictionary *coord = [notification object];
   pt.x = [[coord objectForKey:@"x"] floatValue];
   pt.y = [[coord objectForKey:@"y"] floatValue];

   // convert point from window to view coordinate system
   pt = [webView convertPoint:pt fromView:nil];

   // convert point from view to HTML coordinate system
   CGPoint offset  = [webView scrollOffset];
   CGSize viewSize = [webView frame].size;
   CGSize windowSize = [webView windowSize];

   CGFloat f = windowSize.width / viewSize.width;
   pt.x = pt.x * f + offset.x;
   pt.y = pt.y * f + offset.y;

   [self openContextualMenuAt:pt];
}</pre>
<p>The method &#8220;scrollOffset&#8221; and &#8220;windowSize&#8221; are implemented as category for the UIWebView class. &#8220;scrollOffset&#8221; is required to make sure the coordinates are also correct when the web page was scrolled. The &#8220;windowSize&#8221; returns the visible width and height of the HTML document from the point of view of the HTML document. So based on the windowsSize of the &#8220;HTML window&#8221; and the view size of the UIWebView, you can calculate the zoom factor, and the zoom factor is necessary to transform and scale the screen coordinates to the correct HTML coordinates.</p>
<p>Here&#8217;s the implementation of &#8220;scrollOffset&#8221; and &#8220;windowSize&#8221;&#8230;</p>
<p><span style="color: #993300;">WebViewAdditions.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface UIWebView(WebViewAdditions)
- (CGSize)windowSize;
- (CGPoint)scrollOffset;
@end</pre>
<p><span style="color: #993300;">WebViewAdditions.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "WebViewAdditions.h"

@implementation UIWebView(WebViewAdditions)

- (CGSize)windowSize
{
   CGSize size;
   size.width = [[self stringByEvaluatingJavaScriptFromString:@"window.innerWidth"] integerValue];
   size.height = [[self stringByEvaluatingJavaScriptFromString:@"window.innerHeight"] integerValue];
   return size;
}

- (CGPoint)scrollOffset
{
   CGPoint pt;
   pt.x = [[self stringByEvaluatingJavaScriptFromString:@"window.pageXOffset"] integerValue];
   pt.y = [[self stringByEvaluatingJavaScriptFromString:@"window.pageYOffset"] integerValue];
   return pt;
}
@end</pre>
<p>Finally, we need to implement the method &#8220;openContextualMenuAt:&#8221; for the UIWebView delegate, which first checks for the HTML elements that are at the touch locations and the creates the contextual menu. Checking for the HTML elements at the touch location must be done via JavaScript&#8230;</p>
<p><span style="color: #993300;">JSTools.js</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function MyAppGetHTMLElementsAtPoint(x,y) {
   var tags = ",";
   var e = document.elementFromPoint(x,y);
   while (e) {
      if (e.tagName) {
         tags += e.tagName + ',';
      }
      e = e.parentNode;
   }
   return tags;
}</pre>
<p>This JavaScript function simply collects the tag names of all HTML elements at the touch coordinates and returns the tag names as string list. The JavaScript file must be added as &#8220;resource&#8221; to your XCode project. It can happen that Xcode treats JavaScript file as normal code and tries to compile and link it instead of adding it to the resources. So make sure the JavaScript file is in the &#8220;Copy Bundle Resources&#8221; section within the XCode project target and not in the &#8220;Compile Sources&#8221; or &#8220;Link Binaries&#8221; section.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)openContextualMenuAt:(CGPoint)pt
{
   // Load the JavaScript code from the Resources and inject it into the web page
   NSString *path = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];
   NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
   [webView stringByEvaluatingJavaScriptFromString: jsCode];

   // get the Tags at the touch location
   NSString *tags = [webView stringByEvaluatingJavaScriptFromString:
            [NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];

   // create the UIActionSheet and populate it with buttons related to the tags
   UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Contextual Menu"
                  delegate:self cancelButtonTitle:@"Cancel"
                  destructiveButtonTitle:nil otherButtonTitles:nil];

   // If a link was touched, add link-related buttons
   if ([tags rangeOfString:@",A,"].location != NSNotFound) {
      [sheet addButtonWithTitle:@"Open Link"];
      [sheet addButtonWithTitle:@"Open Link in Tab"];
      [sheet addButtonWithTitle:@"Download Link"];
   }
   // If an image was touched, add image-related buttons
   if ([tags rangeOfString:@",IMG,"].location != NSNotFound) {
      [sheet addButtonWithTitle:@"Save Picture"];
   }
   // Add buttons which should be always available
   [sheet addButtonWithTitle:@"Save Page as Bookmark"];
   [sheet addButtonWithTitle:@"Open Page in Safari"];

   [sheet showInView:webView];
   [sheet release];
}</pre>
<p>This method injects the JavaScript code which looks for the HTML elements at the touch location into the web page and calls this function. The return value will be a string with a comma-separated list of tag names. This string will start and end with a comma so we can simply check for occurrences of a substring &#8220;,tagName,&#8221; if we want to find out if an element with a certain tag name was touched. In our example, we simple add some buttons if an &#8220;A&#8221; tag was hit and some other buttons if and &#8220;IMG&#8221; tag was hit. But what you&#8217;re doing is up to you. Also the information that is returned from the JavaScript function (in this example &#8220;MyAppGetHTMLElementsAtPoint()&#8221;) is up to you. In iCab Mobile it returns the HREF and SRC attributes of A and IMG tags, so the URLS can be directly processed.</p>
<p>The example doesn&#8217;t include the UIActionSheet delegate method which is called when you tab on one of the buttons in the contextual menu. But I think you should already know how to handle this. Also a few other details might be missing, but I think you should be able now to implement your own custom contextual menu for UIWebView objects with the information from the blog post.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/feed/</wfw:commentRss>
		<slash:comments>132</slash:comments>
		</item>
		<item>
		<title>Changing the headers for UIWebKit HTTP requests</title>
		<link>http://www.icab.de/blog/2010/04/07/changing-the-headers-for-uiwebkit-http-requests/</link>
		<comments>http://www.icab.de/blog/2010/04/07/changing-the-headers-for-uiwebkit-http-requests/#comments</comments>
		<pubDate>Wed, 07 Apr 2010 15:51:58 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=144</guid>
		<description><![CDATA[I was asked several times, in which way the &#8220;User-Agent&#8221; header can be modified for the HTTP requests that are initiated from within the UIWebView object. iCabMobile is doing this, and also some other iPhone Apps, but the UIWebView API doesn&#8217;t provide anything which allows to modify the &#8220;User-Agent&#8221; information or any other HTTP header. [...]]]></description>
			<content:encoded><![CDATA[<p>I was asked several times, in which way the &#8220;User-Agent&#8221; header can be modified for the HTTP requests that are initiated from within the UIWebView object. iCabMobile is doing this, and also some other iPhone Apps, but the UIWebView API doesn&#8217;t provide anything which allows to modify the &#8220;User-Agent&#8221; information or any other HTTP header.</p>
<p>When you load a web page from the internet through UIWebView, you can provide a delegate which is called for each web page that is loaded. And in the method &#8220;webView:shouldStartLoadWithRequest:navigationType:&#8221; of the delegate, you&#8217;ll even get an NSURLRequest object you can look at, but unfortunately you can not modify this object. So there&#8217;s no way to change the default &#8220;User-Agent&#8221; information that is sent to the server, nor can you modify any other data.</p>
<p>When you&#8217;re loading data from the internet outside of UIWebView, you would probably use the NSURLConnection class. In this case you would create an NSURLRequest object (or the mutable counterpart NSMutableURLRequest) with all the HTTP headers for the request yourself (using the method &#8220;setValue:forHTTPHeaderField:&#8221;). You have full control over all of the HTTP headers you want to send to the server, including the &#8220;User-Agent&#8221; information.</p>
<p>When we assume that the UIWebView object will internally also use NSURLRequest or NSMutableURLRequest to create a HTTP request before this request is passed to the networking classes like NSURLConnection, we need a way to subclass or overwrite the method &#8220;setValue:forHTTPHeaderField:&#8221; of the NSMutableURLRequest class. Then we would be able to check for each HTTP header that is set for a NSMutableURLRequest, if this is the &#8220;User-Agent&#8221; header and if it is, we can modify it.</p>
<p>The only problem is that we can&#8217;t overwrite or subclass the NSMutableURLRequest class and force UIWebView to use our subclass instead of the original class. But iPhone Apps are written in Objective C and this programming language does allow exchanging and modifying classes, methods, variables etc. at runtime any time. So we can tell the Objective C runtime system that each time the method &#8220;setValue:forHTTPHeaderField:&#8221; of the &#8220;NSMutableURLRequest&#8221; class is called, our own method is called instead. This way it doesn&#8217;t matter that UIWebView will never call our method directly. Exchanging methods is called &#8220;Method Swizzling&#8221; and you can learn more about it on the <a href="http://www.cocoadev.com/index.pl?MethodSwizzling">CocoaDev page</a>.</p>
<p><b>The method swizzling is very powerful, but it can be also very dangerous if you don&#8217;t know what you&#8217;re doing. So be very careful.</b></p>
<p>Now to the sources. I&#8217;ve implemented the method swizzling as a category of NSObject, so you can use it for all classes very easy (but as I said above, be careful, don&#8217;t use it if there are other options).</p>
<p><span style="color: #993300;">MethodSwizzling.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector;

@end</pre>
<p><span style="color: #993300;">MethodSwizzling.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "MethodSwizzling.h"

@implementation NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Method origMethod = class_getInstanceMethod(self, origSelector);
    Method newMethod = class_getInstanceMethod(self, newSelector);

    if (origMethod &amp;&amp; newMethod) {
        if (class_addMethod(self, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
            class_replaceMethod(self, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        } else {
            method_exchangeImplementations(origMethod, newMethod);
        }
        return YES;
    }
    return NO;
}

@end</pre>
<p>You can call &#8220;swizzleMethod:&#8221; for an object, passing in the selectors of the original and the new replacement methods. If the &#8220;swizzleMethod:&#8221; method returns with the result YES, each call of the original method will then call the replacement method and each call of the replacement method will call the original method. So within your replacement method you can still call the original method.</p>
<p>Here&#8217;s the implementation of the new replacement method for the NSMutableURLRequest class:</p>
<p><span style="color: #993300;">MyMutableURLRequest.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface NSMutableURLRequest (MyMutableURLRequest)

+ (void)setupUserAgentOverwrite;

@end</pre>
<p><span style="color: #993300;">MyMutableURLRequest.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "MyMutableURLRequest.h"
#import "MethodSwizzling.h"

@implementation NSMutableURLRequest (MyMutableURLRequest)

- (void)newSetValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
{
    if ([field isEqualToString:@"User-Agent"]) {
        value = @"The new User-Agent string";
    }
    [self newSetValue:value forHTTPHeaderField:field];
}

+ (void)setupUserAgentOverwrite
{
    [self swizzleMethod:@selector(setValue:forHTTPHeaderField:)
            withMethod:@selector(newSetValue:forHTTPHeaderField:)];
}

@end</pre>
<p>This new method is implemented as a category, we don&#8217;t need to subclass. The replacement method for &#8220;setValue:forHTTPHeaderField:&#8221; is called &#8220;newSetValue:forHTTPHeaderField:&#8221; and it is simply checking if the &#8220;field&#8221; variable is equal to &#8220;User-Agent&#8221;. If it is, the value is modified. Afterwards the original method is called.<br />
Please note: because the method swizzling exchanges the original and replacement methods, we have to call &#8220;newSetValue:forHTTPHeaderField:&#8221; to call the original method &#8220;setValue:forHTTPHeaderField:&#8221;. This looks confusing, but this is the way you can give control back to the original method.</p>
<p>The method &#8220;setupUserAgentOverwrite&#8221; has to be called once after the App is launched (for example in the Application delegate in the &#8220;applicationDidFinishLaunching:&#8221; method, or even in &#8220;main()&#8221;).</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">   [NSMutableURLRequest setupUserAgentOverwrite];</pre>
<p>This should be done before any UIWebView objects are created to make sure that the &#8220;User-Agent&#8221; is modified for all requests.</p>
<p>You can also use this approach when you need to modify other HTTP headers.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/04/07/changing-the-headers-for-uiwebkit-http-requests/feed/</wfw:commentRss>
		<slash:comments>49</slash:comments>
		</item>
		<item>
		<title>Modules for iCab Mobile (Updated 2011/03/26)</title>
		<link>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/</link>
		<comments>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/#comments</comments>
		<pubDate>Wed, 17 Feb 2010 21:39:38 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[modules]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=118</guid>
		<description><![CDATA[Update 2011/03/26 Please also read the blog post &#8220;iCab Mobile Modules and Apple&#8221; for recent events, affecting the modules feature. The version 2.1 of iCab Mobile introduces a new &#8220;modules&#8221; feature. Modules make it possible to add new features in iCab Mobile just by downloading them. They can be used for simple things like increasing the font [...]]]></description>
			<content:encoded><![CDATA[<div style="background-color: #fee; padding: 1em;"><strong>Update 2011/03/26</strong><br />
Please also read the blog post &#8220;<a href="http://www.icab.de/blog/2011/03/26/modules-for-icab-mobile-and-apple/">iCab Mobile Modules and Apple</a>&#8221; for recent events, affecting the modules feature.</div>
<p>
The version 2.1 of iCab Mobile introduces a new &#8220;modules&#8221; feature. Modules make it possible to add new features in iCab Mobile just by downloading them. They can be used for simple things like increasing the font size so a page is more easy to read on the small iPhone screen, but also more complex tasks can be done, like downloading YouTube videos or to post a web page URL at Twitter (including the login and creating a tiny URL). iCab Mobile 2.1 comes with a few built-in modules, and there are also several modules available for download.</p>
<p>This blog post will explain, how you can write your own modules for iCab Mobile and how you (and maybe other users) can install them in iCab Mobile.</p>
<h2>Technical background</h2>
<p>Technically, modules are somehow similar to bookmarklets, but with more features and more flexibility. This means the modules are written in JavaScript code and they can do everything that can be done with JavaScript. Unlike bookmarklets, where the complete JavaScript code must be squeezed in one single line so that it can be used as a URL with &#8220;javascript&#8221; scheme, the modules can be nicely formatted, without any line limitation. Modules have a special header section where the module properties are defined. The properties include an icon that is displayed in the Modules panel of iCab Mobile, but also settings which do allow the user to configure the module in the iCab Mobile module settings panel.</p>
<h2>The module structure</h2>
<p>The module is a normal text file with JavaScript code. There&#8217;s a header section and the code section. Here&#8217;s an example, how this looks like:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig
//id=de.icab.crazy
//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...
//title=Crazy Layout
//description=The module sets random colors for the page elements
//description.de=Das Modul setzt alle Farben der Seite auf Zufallswerte
//var=confirmation;type=confirmation;default=false;
//var=language;type=language;
//endconfig

var hex = "0123456789abcdef";

function iCabMobileGoCrazyForElement(element) {
  if (element.nodeType == 1) {
    if (element.style.display != "none" &amp;&amp;
           element.nodeName.toLowerCase() != 'select') {
      element.style.backgroundColor = "#" +
                   hex.charAt(Math.random()*16) +
                   hex.charAt(Math.random()*16) +
                   hex.charAt(Math.random()*16);
      element.style.color = "#" +
                  hex.charAt(Math.random()*16) +
                  hex.charAt(Math.random()*16) +
                  hex.charAt(Math.random()*16);
      for (var i=0; i&lt;element.childNodes.length; i++) {
        iCabMobileGoCrazyForElement(element.childNodes[i]);
      }
    }
  }
}

var doAction = (confirmation == false);

if (confirmation) {
  var text = "Go crazy?";
  doAction = confirm(text);
}

if (doAction) {
  iCabMobileGoCrazyForElement(window.document.body);
}</pre>
<p>The header section defines the properties of the module. The header section starts with the line</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig</pre>
<p>and ends with the line</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//endconfig</pre>
<p>Between these two lines all the properties are defined, each individual property definition occupies exactly one line, so currently you can&#8217;t split the definition of a property into multiple lines. Also no empty lines are allowed in the header section. Each property definition has the following format:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//property=value;additional paramaters</pre>
<p>The additional parameters are optional and not always needed or required.</p>
<p>There are some properties which are required. If they are missing, iCab Mobile will ignore the module. Other properties are optional and do not need to be present. Here are the properties which are currently supported:</p>
<dl>
<dt><strong>id</strong> (required) </dt>
<dd>This property identifies the module. When updating or reinstalling a module, the value of the &#8220;id&#8221; will be used to find the old module that has to be replaced by the new one. So when updating a module, you must not change the value of the &#8220;id&#8221;. Everything else can be modified, even the name of the module. The value of the &#8220;id&#8221; property should be unique among all existing modules. The best way to find a good &#8220;id&#8221; value is to use a reverse domain name appended with the module name. If your own web page has the domain &#8220;www.your-domain.com&#8221; you should set the &#8220;id&#8221; value to &#8220;com.your-domain.moduleName&#8221;. All of your own modules will have the same reverse domain prefix and the module name as suffix. If you don&#8217;t have your own domain, you can use your name and city as a prefix and maybe some random numbers, anything which makes it unlikely that someone else uses the same id value. The &#8220;id&#8221; is not visible to the user within iCab Mobile. Its only used to identify the module.<br />
Example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//id=de.icab.crazy</pre>
<p>or if you don&#8217;t have your own domain, you may use something like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//id=de.darmstadt.clauss.alexander.crazy</pre>
</dd>
<dt><strong>title</strong> (required)</dt>
<dd>The title defines the name of the module and is displayed within the modules settings of iCab mobile. The title property is needed so that the user can enable or disable the modules in the in-app settings and also configure the modules settings.<br />
Example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title=Crazy Colors</pre>
<p>You can also define localized versions of the title. Just append the language code (for example &#8220;en&#8221; for English, &#8220;de&#8221; for German, &#8220;it&#8221; for Italian etc.) of any of the languages which are supported by the iPhone OS to the &#8220;title&#8221; keyword<br />
like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title.de=Verrückte Farben</pre>
<p>This way you can easily localize the module in many languages. The key &#8220;title&#8221; without a language code appended will be used as default language which is used when none of the defined languages does match the current language of the iPhone or iPod Touch. In general the default language should be English. If no default language is defined, then the very first language that is define will be used as default language. But it is highly recommended, that you simply use English as the default language (without language code).</p>
<p>So if the module supports English and German, the title would be defined this way:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title=Crazy Colors
//title.de=Verrückte Farben</pre>
</dd>
<dt><strong>description</strong> (optional)</dt>
<dd>The description is displayed in the settings panel of the module in iCab Mobile. The description should explain what the module is doing. Localizing is done in the same way as described above for the title. Append the language code to the &#8220;description&#8221; keyword:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//description=The module sets random colors for the page elements
//description.de=Das Modul setzt alle Farben der Seite auf Zufallswerte</pre>
</dd>
<dt><strong>icon</strong> (optional, but highly recommended)</dt>
<dd>The icon is displayed when the user opens the modules panel where the modules can be activated by tapping on their icon. The icon should be an image file in PNG or JPG format. The data of the image file must be encoded with &#8220;base64&#8243; and this &#8220;base64&#8243; encoded data can be then used as value for the &#8220;icon&#8221; property. Usually the base64 data is formatted in lines of at most 64 characters length, but for the modules, all line breaks must be removed, so the icons data can be completely included in one line. The size of the module icons (the visible part) should be approx. 41*32. You can use the following empty icon image as a template for your own icons:<br />
<a href="http://www.icab.de/img/empty.png"><img src="http://www.icab.de/img/empty.png" alt="" /></a>&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...</pre>
</dd>
<dt><strong>var</strong></dt>
<dd>The &#8220;var&#8221; property defines JavaScript variables, which are initialized by iCab Mobile. These variables can be used to allow the user to configure the modules, but also to get certain system properties, like the system language.&nbsp;</p>
<p>Variables do have the following format:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//var=NameOfVar;type=typeOfVar;default=defaultValue;title=labelForSettings;</pre>
<p>iCab Mobile will create a standard JavaScript variable declaration and initialization for all of these variable properties and adds these to the JavaScript code of the module. So your JavaScript code of the module can check and use these variables just like any other variables.</p>
<p>The value of the &#8220;var&#8221; property is the name of the variable.</p>
<p>The &#8220;title&#8221; property is required, when the variable should be presented to the user in the module settings, so the user can change the value of the variable. The value of the &#8220;title&#8221; property is used as the label in the settings panel. You can add additional localized versions of the title, just by appending the language code (as described above).</p>
<p>The &#8220;default&#8221; property can be used to define a certain default value for the variable.</p>
<p>The value of the &#8220;type&#8221; property is the type of the variable, which can be one of the following:</p>
<dl>
<dt><strong>bool</strong> or <strong>boolean</strong></dt>
<dd>The variable can have the values <strong>true</strong> or <strong>false</strong>. In the module settings this variable will be represented by a &#8220;switch&#8221; control. If the user switches it on, the variable will have the value  <strong>true</strong> otherwise the variable will have the value <strong>false</strong>. The title attribute is required for this type. The title is shown as label for the switch in the module settings. </dd>
<dt><strong>int</strong> or <strong>integer</strong></dt>
<dd>The variable can have a numerical value. In the module settings, this variable is represented with a text field where you can enter digits. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>string</strong> or <strong>text</strong></dt>
<dd>The variable can have a string value. In the module settings, it is represented as a text field. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>pass</strong> or <strong>password</strong></dt>
<dd>The variable can have a string value. In the module settings, it is represented as a text field where the input is hidden. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>lang</strong> or <strong>language</strong></dt>
<dd>The value of this variable is a string that contains the language code of the currently selected system language. This variable is not presented in the settings panel of the module. Therefore no title property is required here. </dd>
<dt><strong>confirm</strong> or <strong>confirmation</strong></dt>
<dd>The variable has a boolean value. It is represented in the module settings by a switch where the user can enable or disable a confirmation box. If enabled, iCab Mobile will ask each time the module is activated, if it should be really executed. This variable does not need a &#8220;title&#8221; property because it is automatically localized within the module settings panel. </dd>
<dt><strong>autorun</strong></dt>
<dd>The variable has a boolean value. It is represented in the module settings by a switch where the user can configure, if the module should be automatically run when the page has finished loading. This variable does not need a &#8220;title&#8221; property because it is automatically localized within the module settings panel. If there&#8217;s no variable of the type &#8220;autorun&#8221; set, the module can only be opened manually. You can set the  default value for the variable to switch on or off the &#8220;autorun&#8221; feature for the module. But the user will be always able to disable the &#8220;autorun&#8221; feature in the module settings. When the modul is executed, the variable that is defined with the type &#8220;autorun&#8221; will have the value &#8220;true&#8221; if the module was executed automatically (which means when the page has finished loading) and the value &#8220;false&#8221; if the module was opened manually by the user. So a module is able to do different thing when called automatically and when called manually (for example it can mark elements when called automatically to notify the user about something, and modify the elements when called manually, to do the real work). [This feature will be available in iCab Mobile 3.3, it's not yet available in version 3.2]</dd>
<dt><strong>select</strong></dt>
<dd>The variable of this type can be used to select one item from an array of items. This variable type uses the title property in the same way as the other types to define the label in the module settings. This variable type requires that two other properties are defined as well. The &#8220;values&#8221; property must be defined to create the array of values the user can choose from, and the &#8220;valuetitles&#8221; property must be defined to define the array of titles for these values. The &#8220;valuetitles&#8221; properties is localizable again, so append the language code as shown above for the &#8220;title&#8221; property. For the &#8220;values&#8221; and &#8220;valuetitles&#8221; property, all items must be separated by the &#8220;|&#8221; character.<br />
An example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//var=s;type=select;valuetitles=One|Two|Three;values=1|2|3;title=Number;default=2</pre>
<p>In the settings the user would be able to choose between &#8220;One&#8221;, &#8220;Two&#8221; and &#8220;Three&#8221;. If the user selects &#8220;One&#8221; the variable <strong>s</strong> would have the value <strong>1</strong>, if the user selects &#8220;Two&#8221; the variable <strong>s</strong> would have the value <strong>2</strong> etc. The default value would be <strong>2</strong> and the user would see that &#8220;Two&#8221; is preselected. The whole setting would have the label &#8220;Number&#8221;.</p>
</dd>
</dl>
<p>When the user activates a module, iCab Mobile will process the header sections and creates JavaScript variable declarations for all the variable definitions from the header section. The values for these variables will be determined by the module settings. Technically, iCab will add these variable declarations before the JavaScript code section of the module, so the module can access these variables. These variables and the JavaScript code section will be embedded in a block, so they have their own scope and do not interfere with the JavaScript code and variables which are already present in the web page.</p>
<p>This means, when the module looks like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig
//id=de.icab.module
//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...
//title=Some Module
//var=confirmation;type=confirmation;default=false;
//var=language;type=language;
//var=text;type=string;title=Text;
//endconfig

function DoSomething() {
  // here's the actual code which is doing all the work of the module
}

if (confirmation) {
  doAction = confirm("Really activate the module?");
} else {
  doAction = true;
}

if (doAction) {
  DoSomething();
}</pre>
<p>the resulting code that is actually executed in the context of the web page looks like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">{
  var confirmation = true;
  var language = "en";
  var text = "User Input";

  function DoSomething() {
    // here's the actual code which is doing all the work
  }

  if (confirmation) {
    doAction = confirm("Really activate the module?");
  } else {
    doAction = true;
  }

  if (doAction) {
    DoSomething();
  }
}</pre>
</dd>
</dl>
<h2>Special JavaScript functions</h2>
<p>Modules can also call special JavaScript functions, which are defined by iCab Mobile to do certain tasks.</p>
<p>There are the following functions available:</p>
<dl>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">startDownload(url,file)</span> </dt>
<dd>This function starts a download. The parameters are the URL and a suggestion<br />
for the filename under which the download should be saved. </dd>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">postRequest(url,content,&#8221;callBackFunction&#8221;)</span> </dt>
<dd>This function gets the data from the URL using the HTTP POST command (posting &#8220;content&#8221; to the server) and then passes the HTTP status code and the data to a function called &#8220;callBackFunction&#8221; (see getRequest() below for a description of the callback function).</dd>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">getRequest(url,&#8221;callBackFunction&#8221;)</span></dt>
<dd>This function gets the data from the URL using the HTTP GET command and then passes the HTTP status code and the data to a function called &#8220;callBackFunction&#8221; (the second parameter is a string with the name of the callback function that must be implemented by the Module to process the data):&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function callBackFunction(status, data) {
  // "status" is an integer value with the HTTP status code (200, 404, etc)
  // "data" is a string with the URL data
}</pre>
</dd>
</dl>
<h2>Installing the modules</h2>
<p>Installing the modules is done by simply downloading them from a web site. So all you need to do is to provide a web page which contains a link to your module. The link URL must use the URL scheme &#8220;icabmodule&#8221; or &#8220;javascriptmodule&#8221; instead of the usual &#8220;http&#8221; scheme. This is how iCab Mobile detects that it should download and install a module.</p>
<p>For example, if your module can be accessed with the URL <strong>http://your.webspace.com/modules/TheModule.icabmodule</strong><br />
the HTML link you would have to include in your web page would look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;a href="icabmodule://your.webspace.com/modules/TheModule.icabmodule"&gt;Download TheModule&lt;/a&gt;</pre>
<p>or alternatively look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;a href="javascriptmodule://your.webspace.com/modules/TheModule.icabmodule"&gt;Download TheModule&lt;/a&gt;</pre>
<p>If you include these links on a web page which is accessible to other people as well, other iCab Mobile users would be also able to install your module. So you can easily share your modules with other users.</p>
<p>I&#8217;ve created two URL schemes for the modules. In case other iPhone developers are interested in this module feature (candidates would be developers of other iPhone browsers),  it would be great if all these modules would be compatible. In this case the general URL scheme &#8220;javascriptmodule&#8221; could be used by all the Apps supporting the modules feature. And the other URL scheme could be used for application specific-modules (so &#8220;icabmodule&#8221; would be only accepted by iCab Mobile).<br />
Just a reminder: My <a href="http://www.icab.de/blog/2009/09/12/applink-for-the-iphone/">AppLink Proposal</a> can be also used by other iPhone Apps.</p>
<p>You can also sent the module to <a href="mailto:alexander@icab.de">me</a> so I can include it into the modules download page, which is accessible through the &#8220;Download&#8221; module that is built-in in iCab Mobile. This way the module can be immediately found by all iCab Mobile users.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/feed/</wfw:commentRss>
		<slash:comments>104</slash:comments>
		</item>
		<item>
		<title>Search and highlight text in UIWebView</title>
		<link>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/</link>
		<comments>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/#comments</comments>
		<pubDate>Mon, 11 Jan 2010 23:26:20 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=110</guid>
		<description><![CDATA[Several iPhone Apps (like my &#8220;iCab Mobile&#8221; or &#8220;NewsTap&#8221; Apps) provide a search feature which allows to search for text in the content that is currently displayed within a UIWebView. The found occurrences of the searched text are highlighted with a yellow background, so the search result can be visually located very easy. This blog [...]]]></description>
			<content:encoded><![CDATA[<p>Several iPhone Apps (like my &#8220;iCab Mobile&#8221; or &#8220;NewsTap&#8221; Apps) provide a search feature which allows to search for text in the content that is currently displayed within a UIWebView. The found occurrences of the searched text are highlighted with a yellow background, so the search result can be visually located very easy.</p>
<p>This blog post describes how this can be implemented. I&#8217;m implementing this feature as a category for the UIWebView class, so you can use the new search feature for all UIWebView objects in your Apps very easily.</p>
<p>First of all, UIWebView doesn&#8217;t allow us to access its content directly, so we have to use JavaScript again. But if you&#8217;ve read my other blog posts, you already know this approach.</p>
<p>Our goal is to implement two methods for our new UIWebView category. One method should start the search and highlights the search results. As a result this method should return the number of occurrences of the text we&#8217;ve searched. The other method should remove all the highlighted search results again to restore the original layout of the web page.</p>
<p>What we need to do is to write the main code in JavaScript and a wrapper code in Objective C which is simply calling the JavaScript code.</p>
<p>We start with the JavaScript code that is doing the real work. As I&#8217;ve already described in the blog post <a href="http://www.icab.de/blog/2009/07/27/webkit-on-the-iphone-part-1/"> WebKit on the iPhone</a>, the JavaScript code will be saved as resource file in the XCode project. This way it can be loaded from within the Objective C code from the application bundle very easily, and we don&#8217;t mix the code of multiple programming languages (JavaScript and Objective C) in the same files.</p>
<p>The following code is the the JavaScript implementation; below I&#8217;ll explain what it is doing and how it works:</p>
<p><span style="color: #993300;">SearchWebView.js:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">// We're using a global variable to store the number of occurrences
var MyApp_SearchResultCount = 0;

// helper function, recursively searches in elements and their child nodes
function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword) {
  if (element) {
    if (element.nodeType == 3) {        // Text node
      while (true) {
        var value = element.nodeValue;  // Search for keyword in text node
        var idx = value.toLowerCase().indexOf(keyword);

        if (idx &lt; 0) break;             // not found, abort

        var span = document.createElement("span");
        var text = document.createTextNode(value.substr(idx,keyword.length));
        span.appendChild(text);
        span.setAttribute("class","MyAppHighlight");
        span.style.backgroundColor="yellow";
        span.style.color="black";
        text = document.createTextNode(value.substr(idx+keyword.length));
        element.deleteData(idx, value.length - idx);
        var next = element.nextSibling;
        element.parentNode.insertBefore(span, next);
        element.parentNode.insertBefore(text, next);
        element = text;
        MyApp_SearchResultCount++;	// update the counter
      }
    } else if (element.nodeType == 1) { // Element node
      if (element.style.display != "none" &amp;&amp; element.nodeName.toLowerCase() != 'select') {
        for (var i=element.childNodes.length-1; i&gt;=0; i--) {
          MyApp_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
        }
      }
    }
  }
}

// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
  MyApp_RemoveAllHighlights();
  MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}

// helper function, recursively removes the highlights in elements and their childs
function MyApp_RemoveAllHighlightsForElement(element) {
  if (element) {
    if (element.nodeType == 1) {
      if (element.getAttribute("class") == "MyAppHighlight") {
        var text = element.removeChild(element.firstChild);
        element.parentNode.insertBefore(text,element);
        element.parentNode.removeChild(element);
        return true;
      } else {
        var normalize = false;
        for (var i=element.childNodes.length-1; i&gt;=0; i--) {
          if (MyApp_RemoveAllHighlightsForElement(element.childNodes[i])) {
            normalize = true;
          }
        }
        if (normalize) {
          element.normalize();
        }
      }
    }
  }
  return false;
}

// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
  MyApp_SearchResultCount = 0;
  MyApp_RemoveAllHighlightsForElement(document.body);
}</pre>
<p>The basic principle of searching the text and removing the highlighted search results is the same: We&#8217;re working at DOM level (Document Object Model), which means the HTML document is represented as a tree structure where each HTML element, text, comment etc. is represented as a node. All the nodes are linked together with parent and child connections. The root element of each HTML document is the element that is created by the HTML tag. This element has usually two children: The HEAD element and the BODY element. Only the content of the BODY element is visible and displayed on screen, so we only need to process this part of the document tree.</p>
<p>What we need to do is to start with the body element and traverse all of its child nodes. From within the child nodes we need to go to their child nodes as well, and so on until we reach a leaf nodes, which has no child elements. Text nodes are always leaf nodes and text nodes are the nodes which might contain the text we&#8217;re looking for.</p>
<p>Traversing the whole HTML tree searching for all text nodes can be done by a recursive algorithm called Depth-First-Search (DFS). The DFS algorithm will traverse the tree structure starting from a root element (in our case the BODY element) to the first leaf node in a branch of the tree (for example going to the first child of the root first, from there again going to the first child, etc until a leaf node is reached). Then the algorithm goes back (backtracking) to the last node where not all child nodes were traversed yet and continues with the next unvisited child nodes etc. This way all nodes of the whole tree are traversed and we are able to find all the text nodes in which we are looking for the text we are earching.</p>
<p>The functions &#8220;MyApp_HighlightAllOccurencesOfStringForElement(element,keyword)&#8221; and &#8220;MyApp_RemoveAllHighlightsForElement(element)&#8221; are both implementations of this DFS algorithm. These functions are called from MyApp_HighlightAllOccurencesOfString(keyword)&#8221; and &#8220;MyApp_RemoveAllHighlights()&#8221; which are doing the necessary initialization and provide the DFS functions with the proper root element (the BODY element). The initialization for a new search is to make sure than no highlighted text from a previous search is present, so we simple call the function to remove all text highlights.</p>
<p>When searching for a text, we check if the currently inspected node is a text node or if it is an element node. If it is an element node it can have child nodes, and these must be inspected as well. If it is a text node, we have to find out if the text of this node contains the text we&#8217;re searching. If yes, we insert the text highlight, otherwise we are finished with this node. Also if the node is neither a text node nor an element node, there aren&#8217;t any child nodes which are interesting for us, so we are finished with this node as well.</p>
<p>When the text of a text node contains the searched text, we have to split the text into three parts. Part one will contain the text up to the searched text, part two contains the searched text itself and part three contains the rest of the text. A new element will be created (a SPAN element) and the second part (the searched text) will become a child node of this new element. Now we can assign StyleSheet rules to the newly created SPAN element to create the highlight effect (setting the background color to yellow, setting the text color to black, you can even increase the font size, if you want to). Now the new element is linked with part one and three so it becomes a part of the tree strucuture of the HTML tree. And because the searched text might be found multiple times in the original text node, we continue to search for the searched text in the third part of the original text node. If we find another occurrence of the searched text, we split this third part again in three parts, otherwise we are finished. When we create a SPAN element for the highlight effect, we also assign a special value (here &#8220;MyAppHighlight&#8221;) for the  class attribute. This is important to be able to find these elements later again when we want to remove the highlight effects using the function &#8220;MyApp_RemoveAllHighlights()&#8221;. For this task we traverse the tree as well, but now we&#8217;re looking for elements whose class attribute has this special value. To restore the original state of the HTML document, we have to remove the elements we&#8217;ve inserted before (the ones with the special value of the class attribute) and we need to concatenate the text node we&#8217;ve split. JavaScript can help us to concatenate the text nodes again, because it provides the &#8220;normalize()&#8221; function which can do this for us.</p>
<p>In JavaScript we can find out the type of a node with the &#8220;nodeType&#8221; property. A value of 1 means that the node is a normal element node (like the &#8220;body&#8221; node, a &#8220;span&#8221; node etc.). A value of 3 means that the node is a text node. In this case the property nodeValue contains the text of the node. Other values for nodeType represent comment nodes (in HTML these are written as &#8220;&lt;!&#8211; Comment &#8211;&gt;&#8221;), attribute nodes (for HTML attributes like for example the &#8220;HREF&#8221; attribute for the &#8220;A&#8221; tag), document nodes and some more. In our case only the values 1 (element node) and 3 (text node) are important.</p>
<p>In the above implementation, we count the number of found occurrences in a global variable.</p>
<p><strong>Note:</strong> You&#8217;ll notice that the JavaScript function names and variables and also the value for the class attribute I&#8217;m using in the above code are very lengthy and they do also have a prefix like &#8220;MyApp_&#8221;. The reason for this is to avoid any conflicts with existing function and variable names of the web page in which we inject our JavaScript code. If you&#8217;re generating the HTML code yourself that is displayed in the UIWebView object, you can choose shorter and simpler names. But if you have to deal with HTML and JavaScript code of any web pages (like in a web browser like iCab Mobile), you should use longer names and also add the name of your app as Prefix to all function and variable names to avoid any conflicts.</p>
<p>The Cocoa/Objective C part of the implementation is very simple. We only need to declare the interface and write a simple wrapper which loads and calls the JavaScript code that is actually doing all the hard work. The interface is also simple, we only need two methods: one to start the search and which highlights the found text and one which removes all the highlights again.</p>
<p><span style="color: #993300;">SearchWebView.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str;
- (void)removeAllHighlights;

@end</pre>
<p>The typical use case would be to provide a search field where the user can enter some text. This text would be passed to the method &#8220;highlightAllOccurencesOfString:&#8221;. And when the user shakes the device, the App could call the method &#8220;removeAllHighlights&#8221; to remove all the highlighted search results again.</p>
<p>The implementation would look like this:</p>
<p><span style="color: #993300;">SearchWebView.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@implementation UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"SearchWebView" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [self stringByEvaluatingJavaScriptFromString:jsCode];

    NSString *startSearch = [NSString stringWithFormat:@"MyApp_HighlightAllOccurencesOfString('%@')",str];
    [self stringByEvaluatingJavaScriptFromString:startSearch];

    NSString *result = [self stringByEvaluatingJavaScriptFromString:@"MyApp_SearchResultCount"];
    return [result integerValue];
}

- (void)removeAllHighlights
{
    [self stringByEvaluatingJavaScriptFromString:@"MyApp_RemoveAllHighlights()"];
}

@end</pre>
<p>The first thing we&#8217;re doing in the method &#8220;highlightAllOccurencesOfString:&#8221; is to load the JavaScript file we&#8217;ve written above from the application bundle and inject it into the web page that is currently displayed in UIWebView. Because we&#8217;re implementing this as a category for UIWebView, we can use &#8220;self&#8221; to call the method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; of the UIWebView instances.</p>
<p>After we&#8217;ve injected the JavaScript code we simply call the JavaScript function we&#8217;ve defined above to do the search.</p>
<p>And finally we access the variable we&#8217;ve defined in the JavaScript code, which represents the number of occurrences of the string that were found, and we return its integer value as the result of the method.</p>
<p>In the method &#8220;removeAllHighlights&#8221; we only need to call the  corresponding JavaScript function we&#8217;ve defined in the JavaScript code from above. Loading the external JavaScript file and injecting it into the Web page is not necessary here. If we&#8217;ve started a search before, the code is already injected and we don&#8217;t need to do this again. And if we haven&#8217;t started a search before, we just don&#8217;t need the JavaScript code because there are no highlights which have to be removed.</p>
<p>As you can see, the Objective C code for the UIWebView category is just a simple wrapper code for the JavaScript code. Now, when you have a UIWebView object in your App, you can simply search for text in its content by calling &#8220;highlightAllOccurencesOfString:&#8221;, like in this example where we&#8217;re searching for the text &#8220;keyword&#8221;:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">  // webView is an instance of UIWebView
  [webView highlightAllOccurencesOfString:@"keyword"];</pre>
<p>Additional notes: In case your App has to deal with web pages which can have frames, you have to add some additional code that looks for frames. You have to traverse all the documents of all the frames to find all the text nodes in all frames. The code from above isn&#8217;t doing this to keep it as simple as possible.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/feed/</wfw:commentRss>
		<slash:comments>254</slash:comments>
		</item>
		<item>
		<title>Moving objects within an NSMutableArray</title>
		<link>http://www.icab.de/blog/2009/11/15/moving-objects-within-an-nsmutablearray/</link>
		<comments>http://www.icab.de/blog/2009/11/15/moving-objects-within-an-nsmutablearray/#comments</comments>
		<pubDate>Sun, 15 Nov 2009 17:17:28 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=105</guid>
		<description><![CDATA[In this blog post I&#8217;d like to address a common task in iPhone Apps: letting the user reorder entries of a list that is managed by a UITableView. In many cases this would internally simply result in moving an object within an array from one index to another index. The standard array classes of Cocoa don&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>In this blog post I&#8217;d like to address a common task in iPhone Apps: letting the user reorder entries of a list that is managed by a UITableView. In many cases this would internally simply result in moving an object within an array from one index to another index. The standard array classes of Cocoa don&#8217;t have a method for this task, so we simply implement our own. But there are a few &#8220;traps&#8221; and concepts we should be aware of (like &#8220;abstract classes&#8221;, &#8220;categories&#8221;), and which I like to mention here as well.</p>
<p>When developing Apps for the iPhone and iPod Touch, it&#8217;s likely that you&#8217;re using UITableView objects to display the content of your App. UITableView is one of the most important Views of the iPhone OS and used for all kinds of lists and data that can be provided as list. In most cases you would store your data in an NSArray or NSMutableArray object and each row of the table represents an item of this array. In case your app allows the user to reorder the table entries, you would have to implement the following delegate method of the UITableView class:</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">- (void)tableView:(UITableView *)tableView
         moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
                toIndexPath:(NSIndexPath *)toIndexPath</pre>
<p>This delegate method is called whenever the user has enabled the &#8220;edit&#8221; mode of the table and has moved a table entry to another location. Within this method, you have to reorder the items of your own data storage (the array) so that the new representation of the table matches the order of the items within your array again. But if you look at the methods of the NSMutableArray class, you won&#8217;t find a method to do this directly. There&#8217;s no method to move an object of the array from one index to another. But because this is a task which we need very often (almost all iPhone Apps do have tables and in many cases it makes sense that the users of our App should be able to reorder the table entries), we will add a new method to the NSMutableArray class which is doing this, so we don&#8217;t have to implement the same code over and over again.</p>
<p>Subclassing NSMutableArray to add the new method would be possible, but is not the way I would choose here. NSMutableArray is an <strong>abstract class</strong> and a subclass would not inherit the data storage of the parent class. This means when subclassing, we would have to create and maintain our own data storage and would also have to implement all of the basic methods to set and get array items. Many of the basic data storage classes of the Cocoa Frameworks are abstract classes, so be aware when you plan to subclass these classes. Remember that you have to provide your own data storage if you do so.</p>
<p>But Objective-C provides other ways to add methods to an existing class, so subclassing is not necessary. We can define a <strong>category</strong> for the new method. A category can be used to add new methods to existing classes without subclassing them. This has the benefit that these new methods are even available for objects which are created by code which we can&#8217;t control, like objects created and returned by the system frameworks. But the downside is that a category can not add any additional member variables or additional data storage to the class. The reason is simple, the memory that is occupied by an object of the base class must remain the same. The other frameworks and code which doesn&#8217;t know about the new methods are still allocating only the memory for the base class when they create a new object. So a category can only add new behavior (methods), but no new data (member variables).</p>
<p>For our task, a category is just what we need: we don&#8217;t need additional variables or data storage, we just need to implement a new behavior (method) to move an array object from one index to another index. We name this new method &#8220;moveObjectFromIndex:toIndex:&#8221;</p>
<p>The implementation of the category would look like this:</p>
<p><span style="color: #993300;">MoveArray.h:</span></p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">@interface NSMutableArray (MoveArray)

- (void)moveObjectFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;

@end</pre>
<p><span style="color: #993300;">MoveArray.m:</span></p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">#import "MoveArray.h"

@implementation NSMutableArray (MoveArray)

- (void)moveObjectFromIndex:(NSUInteger)from toIndex:(NSUInteger)to
{
    if (to != from) {
        id obj = [self objectAtIndex:from];
        [obj retain];
        [self removeObjectAtIndex:from];
        if (to &gt;= [self count]) {
            [self addObject:obj];
        } else {
            [self insertObject:obj atIndex:to];
        }
        [obj release];
    }
}
@end</pre>
<p>The code is very simple: we first remove the object from its original location and insert it at the new location or add it to the end of the array when the new location is beyond the array bounds. As you can see, though we do not subclass NSMutableArray, we can still use the &#8220;self&#8221; keyword just like we would have done this in a subclass. Also note when defining a category there&#8217;s no block where you can define member variables (within &#8220;{&#8230;}&#8221; like you can do this when subclassing), but instead you can only give the category a name (in this case &#8220;(MoveArray)&#8221;). This is because you can&#8217;t change the memory allocation for the class (see above).</p>
<p>Now, whenever we need to move an array object from one index to another (in a NSMutableArray object), we can simply call</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">[array moveObjectFromIndex:from toIndex:to];</pre>
<p>As you can see, though we haven&#8217;t sub-classed NSMutableArray, the new method is called exactly in the same way as any other methods of the NSMutableArray class. This makes categories a very powerful and flexible feature.</p>
<p>For our initial UITableView  example, implementing the UITableView delegate method &#8220;tableView:moveRowAtIndexPath:toIndexPath:&#8221; would be very simple now. In most cases we could just do the following:</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">- (void)tableView:(UITableView *)table
               moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
                      toIndexPath:(NSIndexPath *)destinationIndexPath
{
    [array moveObjectFromIndex:[sourceIndexPath row]
                       toIndex:[destinationIndexPath row]];
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/11/15/moving-objects-within-an-nsmutablearray/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>Adding any kinds of UI elements into a UINavigationBar</title>
		<link>http://www.icab.de/blog/2009/10/13/adding-any-kinds-of-ui-elements-into-a-uinavigationbar/</link>
		<comments>http://www.icab.de/blog/2009/10/13/adding-any-kinds-of-ui-elements-into-a-uinavigationbar/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 15:30:46 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=79</guid>
		<description><![CDATA[In iCab Mobile and many other iPhone apps you can see a toolbar with buttons, text fields and other UI elements at the top of the screen. And this toolbar looks like a UINavigationBar object. But if you try to add UI elements into a UINavigationBar object in Interface Builder, you&#8217;ll notice that you can&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>In iCab Mobile and many other iPhone apps you can see a toolbar with buttons, text fields and other UI elements at the top of the screen. And this toolbar looks like a UINavigationBar object. But if you try to add UI elements into a UINavigationBar object in Interface Builder, you&#8217;ll notice that you can&#8217;t add all UI elements in any location within the UINavigationBar. More concrete: you can add up to three elements in concrete spots (left, middle, right), and only certain UI elements.  A UINavigationBar object is meant to be used for navigation purposes and therefore is usually automatically populated by a UINavigationController. And the UINavigationController only uses the three spots (left to go back to the previous navigation level, the center area to display the title and the right to add an &#8220;edit&#8221; button for example).</p>
<p>So how are iCab Mobile and other apps able to populate a UINavigationBar object with any UI elements in other than the three available spots?</p>
<p>First of all, not everything which looks like a UINavigationBar is a UINavigationBar. Up to iCab Mobile 1.7 the toolbar at the top of the screen is not a UINavigationBar object, it&#8217;s a UIImageView object with an image that looks like a UINavigationBar would look like. But since iCab Mobile 2.0 it will be a UINavigationBar. iCab Mobile 2.0 will allow to customize the colors of the toolbars, and unlike images, the UINavigationBar object can have any color.</p>
<p>But even for UIImageView objects, you can&#8217;t add any subviews in Interface Builder. So the main question is the same: how to add any available UI elements in any location as subview of these objects (it doesn&#8217;t matter if these objects are UIImageView or UINavigationBar objects)?</p>
<p>The answer is simple: you have to do this programatically. UIImageView and UINavigationBar are both subclasses of UIView. And UIView objects can have subviews. You only need to call the method <span style="color: #003300; font-family:courier; font-size:0.9em">addSubview:</span> to add a subview. When using UINavigationBar as the root for our toolbar, we only have to make sure that it is not managed by a UINavigationController. But because we need to create all the objects programmatically, this isn&#8217;t a problem at all.</p>
<p>One place to create the toolbar can be the <span style="color: #003300; font-family:courier; font-size:0.9em">viewDidLoad</span> method of the UIViewController which serves as the controller for the view that holds the toolbar. The following code is an example how this would look like. All the code fragments should go into the <span style="color: #003300; font-family:courier; font-size:0.9em">viewDidLoad</span> method, I&#8217;ve split the code into fragments only because it&#8217;s easier to explain what I&#8217;m doing this way.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">- (void)viewDidLoad
{
  [super viewDidLoad];

  CGFloat width = self.view.frame.size.width;</pre>
<p>Here we first get the width of the view of our UIViewController. It is used to calculate the widths of all of the toolbar elements.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">  UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:
                                           CGRectMake(0,0,width,52)];
  navBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  [self.view addSubview:navBar];
  [navBar release];</pre>
<p>This fragment creates the UINavigationBar element and we add it as subview to the view object of our UIViewController. If the application should support the autorotation feature, we have to make sure that the width is flexible and set the autoresizing mask accordingly.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">  UILabel *label = [[UILabel alloc] initWithFrame:
                                   CGRectMake(10,2,width-20,14)];
  label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  label.text = @"some text for the title...";
  label.backgroundColor = [UIColor clearColor];
  label.font = [UIFont systemFontOfSize:12];
  label.textAlignment = UITextAlignmentCenter;
  [navBar addSubview:label];
  [label release];</pre>
<p>Then we create the title line of our toolbar. We use a UILabel element. The width must be flexible as well when the autorotation feature is supported. The label is added as a subview of the UINavigationBar object so that there is a small margin at the top, left and right side. It is important to set the background color to &#8220;clearColor&#8221; to make the background completely transparent. The default background color would be white.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">  UITextField *textField = [[UITextField alloc] initWithFrame:
                                CGRectMake(10,19,width-80,26)];
  textField.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  textField.borderStyle = UITextBorderStyleRoundedRect;
  textField.font = [UIFont systemFontOfSize:17];
  [navBar addSubview:textField];
  [textField release];</pre>
<p>This fragment will add a text field to enter some text into the toolbar. This is similar to what we&#8217;ve done to add the label.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">  UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  [button setFrame:CGRectMake(width-60,19,50,26)];
  [button setTitle:@"Go" forState:UIControlStateNormal];
  button.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  [navBar addSubview:button];
}</pre>
<p>And finally we&#8217;ve added a button to the toolbar. Nothing special here. This time the autoresizing mask is set to have a flexible left margin, because the button should always have the same width. It only should be placed on the right side of the toolbar, so the right margin must be fixed, while the left must be flexible.</p>
<p>This is more or less all you need to do to add UI elements in UINavigationBar objects. Of course you also need to set the delegates for some of the UI elements in order to process clicks on the button or  text field editing.</p>
<p>This is how the toolbar from above will look like:</p>
<p><img src="/dev/toolbar.jpg" alt="" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/10/13/adding-any-kinds-of-ui-elements-into-a-uinavigationbar/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Using UIActionSheet in landscape mode on iPhone</title>
		<link>http://www.icab.de/blog/2009/09/24/using-uiactionsheet-in-landscape-mode-on-iphone/</link>
		<comments>http://www.icab.de/blog/2009/09/24/using-uiactionsheet-in-landscape-mode-on-iphone/#comments</comments>
		<pubDate>Thu, 24 Sep 2009 14:12:37 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=68</guid>
		<description><![CDATA[The UIActionSheet class of the iPhone OS can be used to present the user a modal view with a descriptive text and some buttons the user can choose from. This class is usually used when the user has to confirm a certain action (like deleting something) or as some kind of contextual menu (for example [...]]]></description>
			<content:encoded><![CDATA[<p>The UIActionSheet class of the iPhone OS can be used to present the user a modal view with a descriptive text and some buttons the user can choose from. This class is usually used when the user has to confirm a certain action (like deleting something) or as some kind of contextual menu (for example when you tap for a longer time on a link in Safari or iCab Mobile). A UIActionSheet object would look like this:</p>
<p align="center"><img src="/dev/sheet_pm.jpg" alt="" /></p>
<p>But there is a problem: the screen area of the small iPhone screen is limited and only a limited number of buttons will fit on the screen. In portrait mode (like in the screenshot from above), about 6-7 buttons will fit on the screen, which is usually more than enough for most tasks. But in landscape mode the number of buttons can be an issue. Because of the reduced height of the screen in landscape mode, less buttons will fit on the screen. Under iPhone OS 2.x the top most buttons might be no longer accessible because they are located outside of the screen. Since iPhone OS 3.0, Apple has solved this issue by converting the buttons into a scrollable list, if the available space isn&#8217;t enough for all buttons.</p>
<p>Here&#8217;s how this looks like under iPhone OS 2.x:</p>
<p align="center"><img src="/dev/sheet_lm.jpg" alt="" /></p>
<p>Here&#8217;s how this looks linke under iPhone OS 3.x:</p>
<p align="center"><img src="/dev/sheet_lm_3.jpg" alt="" /></p>
<p>If you write an app which requires at least OS 3.0, you can just ignore this problem. But if your app should still support OS 2.x (especially iPod Touch users are slow in upgrading, because the OS 3.x update is not free for iPod Touch users), you have to take care about this problem.</p>
<p>Reducing the number of buttons in landscape mode is usually not an option, and moving half of the buttons into a second UIActionSheet object which will open when you click a button labeled &#8220;More options&#8230;&#8221; can be an easy solution, but it makes navigation through all the options less comfortable to the user.</p>
<p>A simple solution for this problem would be to open the UIActionSheet in portrait mode orientation under OS 2.x while the app itself remains in landscape mode. This way the user would always get the full list of buttons, no special reduced button set for different orientations would be needed, and also no need to introduce multi UIActionSheet objects which are linked together with some buttons.</p>
<p>This can look like this in iPhone OS 2.x (Note: in the background you can see that the app is still in landscape mode):</p>
<p align="center"><img src="/dev/sheet_lm_2.jpg" alt="" /></p>
<p>How can this be done? When you open a UIActionSheet, you can do the following:</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">  UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Some text..."
                           delegate:self
                  cancelButtonTitle:@"Cancel"
             destructiveButtonTitle:@"Delete"
                  otherButtonTitles:@"Button 1",@"Button 2",nil];

  if ([[[UIDevice currentDevice] systemVersion] floatValue] &gt;= 3.0 ||
      UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
    [sheet showInView:self.view];
  } else {
    [sheet showInView:self.view.window];
  }
  [sheet release];</pre>
<p>This code fragment will first create the UIActionSheet object (as usual). Then we check if we&#8217;re running under OS 3.x or newer, or if the current orientation is the portrait mode. If this is the case we just show the UIActionSheet within the view of the current view controller (this is what we would have done normally). But when running under OS 2.x and the device is currently in landscape mode, we do not show the UIActionSheet in the current view, but instead it is shown in the window object (which is also a subclass of UIView). Unlike the normal &#8220;UIView&#8221; objects where the coordinate system will be automatically transformed according to the current device orientation so that the top left corner will have the coordinate (0,0), the coordinate system of the window object (the root object of the view hierarchy) won&#8217;t be transformed. It will always remain the same and is based on the standard portrait orientation. So when we open the UIActionSheet here, it will be shown as if the device would be in portrait mode.</p>
<p>This way we can use UIActionSheets with more than 3-4 buttons in landscape mode safely under OS 3.x and also under OS 2.x without risking that some buttons are no longer accessible.</p>
<p>Here you can download an archive of an example project which demonstrates this technique: <a href="http://www.icab.de/dev/ActionSheetTest.zip">ActionSheetTest.zip (12 KB)</a></p>
<p>Let the example app run under OS 2.x and tap on the two buttons of the main view to find out how the UIActionSheet will look like in landscape and portrait mode with and without the technique described above. Also compare this when running the app under OS 3.x.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/09/24/using-uiactionsheet-in-landscape-mode-on-iphone/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>&#8220;AppLink&#8221; for the iPhone.</title>
		<link>http://www.icab.de/blog/2009/09/12/applink-for-the-iphone/</link>
		<comments>http://www.icab.de/blog/2009/09/12/applink-for-the-iphone/#comments</comments>
		<pubDate>Fri, 11 Sep 2009 22:07:46 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=60</guid>
		<description><![CDATA[I&#8217;ve now updated the proposal I&#8217;ve introduced with the blog post &#8220;Calling alternate browsers on the iPhone&#8221; a few weeks ago. The new implementation is now named &#8220;AppLink&#8221; (thanks to Robert Chin for the name &#8220;AppLink&#8221;) and is much more powerful. The main purpose of AppLink is to make it possible for (internet-aware) iPhone Apps [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve now updated the proposal I&#8217;ve introduced with the blog post &#8220;<a href="http://www.icab.de/blog/2009/08/04/calling-alternate-browsers-on-the-iphone/">Calling alternate browsers on the iPhone</a>&#8221; a few weeks ago. The new implementation is now named &#8220;<strong>AppLink</strong>&#8221; (thanks to Robert Chin for the name &#8220;AppLink&#8221;) and is much more powerful.</p>
<p>The main purpose of AppLink is to make it possible for (internet-aware) iPhone Apps to call each other to open certain URLs. For example by default, all apps are able to let the iPhone OS open a URL, and the iPhone OS will launch Safari or Mail to open these URLs. Unfortunately the iPhone OS will never call other apps (like an alternate Web Browser) for the standard URL schemes, so you can&#8217;t change the default browser or the default Mail app.  If Apps support non-standard URL schemes then the iPhone OS is able to call these Apps from within other Apps, but the problem is, that the other Apps don&#8217;t know about these non-standard URL schemes. So calling these apps is difficult.</p>
<p>AppLink will try to solve these issues. But there are limits here as well. AppLink-aware Apps can detect each other, so they can call each other to open URLs. But for calling other Apps which are not AppLink-aware, nothing has changed. So the goal should be that as many (internet-aware) Apps as possible do support AppLink to get the best user experience.</p>
<p>The current implementation of AppLink assumes that a Web Browser (like iCab Mobile), an RSS reader and a Mail App are the most important Apps for accessing the internet, the center of your internet activities. Technically this is because the standard URL schemes of the internet are &#8220;http&#8221;/&#8221;https&#8221; (Web Browser), &#8220;feed&#8221;/&#8221;feeds&#8221; (RSS reader) and &#8220;mailto&#8221; (Mail App) and these are all already supported by the iPhone OS and its default Apps (Safari and Mail).</p>
<p>An example:</p>
<p>There&#8217;s an AppLink-aware Web Browser (like iCab Mobile) and an AppLink-aware Facebook App installed on the iPhone. The Facebook App can open links in an external browser and the user can decide if this browser is iCab Mobile or Safari or maybe another browser. And if the user is currently surfing in the web using iCab Mobile and opens a link to a new interesting facebook profile, iCab Mobile would automatically show or enable a special button. If the user taps on this button, the native facebook app is called with the current facebook URL, so the user can directly add and manage the new facebook profile within the facebook app where he also manages all the other facebook profiles and contacts. The user doesn&#8217;t have to quit and lauch apps manually, the user doesn&#8217;t need to copy and paste URLs etc. All this can be done by AppLink automatically.</p>
<p>Wikipedia Apps, Twitter Apps and many other Apps can be called the same way if they support AppLink. The AppLink-aware browser will know that a Wikipedia App is interested in wikipedia.org URLs and the a Twitter app is interested in &#8220;twitter.com&#8221; URLs, so it can automatically offer to open such URLs in these native Apps.</p>
<p>But also very simple tasks like opening the FAQ page of an App in a web browser or sending an email to the developer of an App  can be done using AppLink (many Apps in the AppStore do have something like this build in, though only calling Safari or Mail). The AppLink solution will call the AppLink-aware browser or AppLink-aware Mail App if available. If these are not available on the device, the default Apps (Safari and Apple Mail) are called. And calling the AppLink-aware App instead of the default one is most likely what the user wants, otherwise he wouldn&#8217;t have installed the alternate apps.</p>
<p>The curent beta version of  &#8221;<strong>iCab Mobile</strong>&#8221; does already support AppLink with all its features. Though there are currently no other AppLink-aware Apps available, the AppLink features can be already used with some of the build-in apps like the native <strong>YouTube</strong> app. When opening a YouTube video page a new button will appear next to the URL field. Pressing this button will offer to open the video in the native YouTube app. And what can be done for YouTube URLs would also work for other URLs (like Facebook, Twitter, Wikipedia etc.). Here&#8217;re some screenshots, how this can look like:</p>
<div>
<p><img src="http://www.icab.de/dev/feed.jpg" alt="" /><br />
At the top you&#8217;ll see the normal URL bar of iCab Mobile, without the special button to the right of the URL field.<br />
To the left you&#8217;ll see an RSS feed that is displayed in iCab Mobile. To the right of the URL bar there&#8217;s a new button. If you tap on this button, iCab Mobile will ask if you want to open the RSS feed in either Safari or in the &#8220;URLTest (RSS)&#8221; App (see the picture to the right). In this example there are two Apps installed, which can open the feed URL and the user can choose in which App to open the RSS feed.</div>
<div>
<p><img src="http://www.icab.de/dev/youtube.jpg" alt="" /><br />
To the left you&#8217;ll see a video page at youtube.com. iCab Mobile knows that the native YouTube app can open the video, so it adds the special button to the right of the URL field again. Tap the button and iCab will ask if you want to open the video in the &#8220;YouTube&#8221; app.</div>
<p>You can download the AppLink class here: <a href="http://www.icab.de/dev/AppLink.zip">AppLink.zip</a> (32 KB)</p>
<p>There&#8217;s also a small web site about AppLink: <a href="http://www.icab.de/dev/AppLink.html">http://www.icab.de/dev/AppLink.html</a></p>
<p>Please spread the word about this AppLink proposal. Hopefully many iPhone App developers will find this useful and will support AppLink in their Apps.</p>
<p>I&#8217;m also looking for feedback, suggestions and critics. So please tell me if you like the idea, if you have suggestions, if you need help etc. Please use the email address &#8220;alexander@icab.de&#8221; or write a comment here. Developers who want to add AppLink support in their Apps and need a counterpart App to test with (like for example iCab Mobile), please contact me as well. Maybe I can provide you with a beta version of iCab Mobile (but there&#8217;re limits in the number of beta versions I can give away).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/09/12/applink-for-the-iphone/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Calling iCab Mobile from within Safari on the iPhone</title>
		<link>http://www.icab.de/blog/2009/09/07/calling-icab-mobile-from-within-safari-on-the-iphone/</link>
		<comments>http://www.icab.de/blog/2009/09/07/calling-icab-mobile-from-within-safari-on-the-iphone/#comments</comments>
		<pubDate>Mon, 07 Sep 2009 21:03:20 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Safari]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=53</guid>
		<description><![CDATA[Almost all apps on the iPhone can easily open a URL in Safari. But normally you can&#8217;t open a URL from within Safari in other apps, for example in iCab Mobile (V 1.7). But with a &#8220;bookmarklet&#8221; (a special bookmark which is based on JavaScript code) you can do this as well. Here&#8217;s how the [...]]]></description>
			<content:encoded><![CDATA[<p>Almost all apps on the iPhone can easily open a URL in Safari. But normally you can&#8217;t open a URL from within Safari in other apps, for example in iCab Mobile (V 1.7).</p>
<p>But with a &#8220;bookmarklet&#8221; (a special bookmark which is based on JavaScript code) you can do this as well. Here&#8217;s how the bookmark URL for the bookmarklet should look like:</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">    javascript:location.href='web'+location.href.substring(4);</pre>
<p>Because you can&#8217;t create Bookmarks from scratch in Safari on the iPhone, you should just create a bookmark of a random page and change its URL to the above line and the title to &#8220;Open in iCab Mobile&#8221;.</p>
<p>If you open this bookmarklet from within the Safari bookmarks, the currently displayed web page is passed to iCab Mobile and opened there.</p>
<p>The bookmarklet simply changes the URL scheme from &#8220;http&#8221; to &#8220;web&#8221; and from &#8220;https&#8221; to &#8220;webs&#8221;. And because iCab Mobile supports the &#8220;web&#8221; and &#8220;webs&#8221; schemes as a replacement for &#8220;http&#8221; and &#8220;https&#8221;, the iPhone OS will pass these modified URLs to iCab Mobile.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/09/07/calling-icab-mobile-from-within-safari-on-the-iphone/feed/</wfw:commentRss>
		<slash:comments>40</slash:comments>
		</item>
		<item>
		<title>URL filtering for UIWebView on the iPhone</title>
		<link>http://www.icab.de/blog/2009/08/18/url-filtering-with-uiwebview-on-the-iphone/</link>
		<comments>http://www.icab.de/blog/2009/08/18/url-filtering-with-uiwebview-on-the-iphone/#comments</comments>
		<pubDate>Tue, 18 Aug 2009 21:10:10 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=35</guid>
		<description><![CDATA[iCab Mobile provides a filter manager which allows to filter out advertising banners and other stuff from web pages. It has a list of simple URL-based filter rules (which is even editable by the user) and when a web page contains resources (image files, JavaScript files, stylesheets etc.) whose URLs match one of these rules, [...]]]></description>
			<content:encoded><![CDATA[<p>iCab Mobile provides a filter manager which allows to filter out advertising banners and other stuff from web pages. It has a list of simple URL-based filter rules (which is even editable by the user) and when a web page contains resources (image files, JavaScript files, stylesheets etc.) whose URLs match one of these rules, the resources won&#8217;t be loaded.</p>
<p>But implementing filters seems to be impossible. When you look at the public API of the UIWebView class, you won&#8217;t see anything which would allow to find out which resources the UIWebView object is loading, and even worse, nothing is available which can be used to force the UIWebView not to load these resources when you want to filter them out.</p>
<p>But of course there is a solution, otherwise this blog post wouldn&#8217;t make much sense <img src='http://www.icab.de/blog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> .</p>
<p>To implement filters we don&#8217;t have to look at UIWebView. As I mentioned above, nothing in the UIWebView API would allow to implement filtering.</p>
<p>To find a hook where we can intercept all the HTTP requests which are done by UIWebView we have to know a little bit about the URL loading system of Cocoa because UIWebView is using the URL loading system to get all the data from the web. One part of the URL loading system is the NSURLCache class, and this is our hook we&#8217;re looking for. Though the iPhone OS doesn&#8217;t cache any data on &#8220;disk&#8221; at the moment (this can be different in later iPhone OS release) and therefore the cache that is managed by the NSURLCache class is usually empty, UIWebView nevertheless checks if the requested resources are in the cache. So all we need to do is to subclass NSURLCache and overwrite the method</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">- (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request</pre>
<p>This method is called for all resources the UIWebView is requesting. So all we need to do is to check if the URL of the request matches one of the filters. If it does, we create a fake response with no content, otherwise we just call the super class.  This is basically all we need to do.</p>
<p>Here&#8217;re some more details:</p>
<p><strong>1. Subclassing NSURLCache:</strong><br />
In the Header file there&#8217;s almost nothing to do:</p>
<p><span style="color: #993300;"> FilteredWebCache.h:</span></p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">@interface FilteredWebCache : NSURLCache
{
}
@end</pre>
<p>Now the main code for the subclass:</p>
<p><span style="color: #993300;"> FilteredWebCache.m:</span></p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">#import "FilteredWebCache.h"
#import "FilterManager.h"

@implementation FilteredWebCache

- (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request
{
    NSURL *url = [request URL];
    BOOL blockURL = [[FilterMgr sharedFilterMgr] shouldBlockURL:url];
    if (blockURL) {
        NSURLResponse *response =
              [[NSURLResponse alloc] initWithURL:url
                                        MIMEType:@"text/plain"
                           expectedContentLength:1
                                textEncodingName:nil];

        NSCachedURLResponse *cachedResponse =
              [[NSCachedURLResponse alloc] initWithResponse:response
                             data:[NSData dataWithBytes:" " length:1]];

        [super storeCachedResponse:cachedResponse forRequest:request];

        [cachedResponse release];
        [response release];
    }
    return [super cachedResponseForRequest:request];
}
@end</pre>
<p>The code first checks if the URL should be blocked (the FilterManager class is doing all these checks, this class isn&#8217;t shown here). If yes, it creates a new response object with no content and stores this in the cache. One could assume that it should be possible to just return the fake response object and we don&#8217;t need to store it in the cache. But if we do this, the app would crash very soon because our fake response object is over-released by the iPhone OS. I don&#8217;t know why exactly this happens, this might be a bug in the iPhone OS (and also in MacOSX 10.5.x where the same thing happens. This works fine in 10.4.x and all older MacOSX releases) or caused by some undocumented internal dependencies between the different classes of the URL loading system. So we just store our fake response in the Cache. This makes sure that all response objects we return are really stored in the Cache and this is what the iPhone OS expects and then it won&#8217;t crash.<br />
<br /><b>Update:</b> It seems that it is also necessary that the &#8220;fake&#8221; response is initialized with a NSData object which has a size larger than zero. </p>
<p><strong>2. Creating a new Cache:</strong><br />
We also need to create a new cache and tell the iPhone OS that it has to use this new cache instead of the default one so we really get called when the URL loading system checks the cache for a resource. This should be done before any of the UIWebView objects are starting to load web pages, very early within the launching process of the app.</p>
<pre style="padding-left:0.7em; border-left: 1px solid #030; color: #003300; font-size:0.9em">NSString *path = ...// the path to the cache file
NSUInteger discCapacity = 10*1024*1024;
NSUInteger memoryCapacity = 512*1024;

FilteredWebCache *cache =
      [[FilteredWebCache alloc] initWithMemoryCapacity: memoryCapacity
                             diskCapacity: discCapacity diskPath:path];
[NSURLCache setSharedURLCache:cache];
[cache release];</pre>
<p>We have to provide a path where the cache file is stored. The cache file is automatically created by the NSURLCache objects, so we don&#8217;t need to create the file, we only have to define where the file should be saved (this must be somewhere in the &#8220;sandbox&#8221; of our application, for example in the &#8220;tmp&#8221; folder or in the &#8220;Documents&#8221; folder).</p>
<p>This is all the magic to implement URL-based filters for UIWebView on the iPhone. You see, it&#8217;s not that complicated.</p>
<p>Note: If the filters can change while your app is running, you may need to remove the fake responses from the cache again. The NSURLCache class provides a method for this, so this isn&#8217;t a problem. If your filters are static, then you don&#8217;t need to care about this.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2009/08/18/url-filtering-with-uiwebview-on-the-iphone/feed/</wfw:commentRss>
		<slash:comments>65</slash:comments>
		</item>
	</channel>
</rss>

