Mar 30

Recently there have been some interesting developer news related to working with images on the iPhone.

  • First there is Chris Greening’s open source project simple-iphone-image-processing, that provides a set of common image processing tasks .
  • Today I listened to the Mobile Orchard’s podcast Interview with Paul Cantrell, and the discussion was about UIKit, views, layers, etc. This was the most enlightening information I’ve come across on this topic ever. Highly recommended.

So, I thought I’d contribute a few UIImage routines that I’ve found useful.

Combine two UIImages

To add two UIImages together you need to make use of Graphics Context.

- (UIImage *)addImage:(UIImage *)image1 toImage:(UIImage *)image2 {
	UIGraphicsBeginImageContext(image1.size);

	// Draw image1
	[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];

	// Draw image2
	[image2 drawInRect:CGRectMake(0, 0, image2.size.width, image2.size.height)];

	UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();

	UIGraphicsEndImageContext();

	return resultingImage;
}

Create a UIImage from a part of another UIImage

This requires a round-trip to Core Graphics land:

- (UIImage *)imageFromImage:(UIImage *)image inRect:(CGRect)rect {
	CGImageRef sourceImageRef = [image CGImage];
	CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, rect);
	UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
	CGImageRelease(newImageRef);
	return newImage;
}

Save UIImage to Photo Album

This is just a one-liner:

UIImageWriteToSavedPhotosAlbum(image, self, @selector(imageSavedToPhotosAlbum: didFinishSavingWithError: contextInfo:), context);

And to know if the save was successful:

- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
	NSString *message;
	NSString *title;
	if (!error) {
		title = NSLocalizedString(@"SaveSuccessTitle", @"");
		message = NSLocalizedString(@"SaveSuccessMessage", @"");
	} else {
		title = NSLocalizedString(@"SaveFailedTitle", @"");
		message = [error description];
	}
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
													message:message
												   delegate:nil
										  cancelButtonTitle:NSLocalizedString(@"ButtonOK", @"")
										  otherButtonTitles:nil];
	[alert show];
	[alert release];
}

written by Nick \\ tags: , , , , ,

Jan 27

Have you every been frustrated with the small circular touch area when setting the accessory type of a UITableViewCell to be UITableViewCellAccessoryDisclosureIndicator? Unless you have child fingers, it’s very difficult to select consistently.

Here’s one way to improve the user experience and create the same effect of a larger touch pad like you see on the YouTube application. To do this, you need to implement a custom table view cell. You can either create your own subclass, or you can add subviews to the cell’s contextView. This example shows how to do the latter.

When setting up my UITableViewCells I like to create methods that provide the elements I wish to use to add to the cell’s contentView. Here I am creating a UIButton of type UIButtonTypeDetailDisclosure to be added to each cell in the table. The button possesses an area of 44 x 44 pixels and occupies the right most area of the table view cell that will be 320 pixels wide and 44 pixels tall. Note here that you define a target and action of your own rather than use the callbacks for the accessory type defined in the UITableViewDelegate protocol.

- (UIButton *) getDetailDiscolosureIndicatorForIndexPath: (NSIndexPath *) indexPath 
{
	UIButton *button = [UIButton buttonWithType: UIButtonTypeDetailDisclosure];
	button.frame = CGRectMake(320.0 - 44.0, 0.0, 44.0, 44.0);
	[button addTarget:self action:@selector(detailDiscolosureIndicatorSelected:) forControlEvents:UIControlEventTouchUpInside];
	return button;
}  

The following shows how to add the button to the cell in the cellForRowAtIndexPath method in your table view controller class. As you add subviews to the contentView, be sure your detailed disclosure indicator is rendered on top. Also, ensure you are properly handling the reuse of your cells.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
...
[cell.contentView addSubview: [self getDetailDiscolosureIndicatorForIndexPath: indexPath]];
...
} 

Finally to process the selection event you need the following code:

- (void) detailDiscolosureIndicatorSelected: (UIButton *) sender 
{	
	//
	// Obtain a reference to the selected cell
	//
	UITableViewCell *cell = [self.tableView cellForRowAtIndexPath: [self.tableView indexPathForSelectedRow]];

	//
	// Do something like render a detailed view
	//		
	...
} 

written by Jess \\ tags: , ,

Dec 12

Among the most popular posts on this blog have been those about UIWebView. (The fact that it’s the #1 search result on Google for the term UIWebView might help. 🙂

Here’s I’d like to revisit the problem of displaying local images in a UIWebView. In this post I presented a little hack to embed small images into the HTML. While this may work in some cases, it’s far from an optimal solution. For example it does not address other types of external files you might want to refer to from your HTML.

Using relative paths or file: paths to refer to images does not work with UIWebView. Instead you have to load the HTML into the view with the correct baseURL: 

NSString *path = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:path];
[webView loadHTMLString:htmlString baseURL:baseURL];

You can then refer to your images like this:

<img src="myimage.png">

Or from within CSS like this:

background-image: url(loading.gif)

It’s important to note that images inside your application bundle are at the root of the bundle, even if you place them in an Images directory in your project.

 

written by Nick \\ tags: , ,

Apr 16

Many of the built-in applications on the iPhone have this nice blue pattern background.

iPhone Standard Screen Background Color

It’s easy to get the same effect in your applications. Just set the background to the built-in color called groupTableViewBackgroundColor. (Note that the documentation erroneously refers to this color as groupTableBackgroundColor.)

UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
view.backgroundColor = [UIColor groupTableViewBackgroundColor];

written by Nick \\ tags: ,

Apr 09

Say you want each customer to accept your Terms of Service, or some other legalese, before they can use your iPhone application.

Display the screen upon startup

The standard AppDelegate code generated by Xcode creates and displays your first application screen. Here’s a way that you can use the hidden property of UIView to display your new screen first without disrupting the flow of the rest of the application.

// Create window
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
[window setBackgroundColor:[UIColor whiteColor]];
    
// Set up main view navigation controller
MainMenuViewController *navController = [[MainMenuViewController alloc] init];
	
// Create a navigation controller using the new controller
navigationController = [[UINavigationController alloc] initWithRootViewController:navController];
navigationController.navigationBarStyle = UIBarStyleDefault;
	
[navController release];

// Create Terms of Service screen	
tosController = [[TermsOfServiceViewController alloc] init];
[window addSubview:[tosController view]];
navigationController.view.hidden = YES;

// Add the navigation controller's view to the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];

Most of this is boilerplate code, more or less generated by Xcode. What’s new starts at line 14. Instantiate the new TermsOfServiceViewController and add it to the view. Standard stuff. The important line is #17, which hides the navigationController.

Dismiss the new screen

Now that the Terms of Service screen is displayed and the Main Menu is hidden we need a way to dismiss the ToS screen and return to the normal application flow.

In your AppDelegate class create a new method:

- (void)termsOfServiceAccepted
{
	tosController.view.hidden = YES;
	navigationController.view.hidden = NO;
}

In the TermsOfServiceController you would have a button that the user has to tap:

UIButton *acceptButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
acceptButton.frame = CGRectMake(kLeftMargin, 
						applicationFrame.size.height - kBottomMargin - kButtonHeight, 
						applicationFrame.size.width - kLeftMargin - kRightMargin, 
						kButtonHeight);
[acceptButton setTitle:NSLocalizedString(@"ButtonAcceptTermsOfService", @"") forStates:UIControlStateNormal];
[acceptButton addTarget:self action:@selector(termsOfServiceAccepted:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:acceptButton];

When the button is tapped, the selector is called which in turn calls the above method in the AppDelegate.

- (void)termsOfServiceAccepted:(id)sender
{
	id applicationDelegate = [[UIApplication sharedApplication] delegate];
	[applicationDelegate termsOfServiceAccepted];
}

Only show the screen once

With this code the ToS screen is displayed every time the application is started. That will quickly get annoying. So let’s add some code to only show it once by using a boolean stored in NSUserDefaults.

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults boolForKey:TERMS_OF_USE_ACCEPTED]) {
	tosController = [[TermsOfServiceViewController alloc] init];
	[window addSubview:[tosController view]];
	navigationController.view.hidden = YES;
}

And in the termsOfServiceAccepted method set the boolean in NSUserDefaults.

- (void)termsOfServiceAccepted
{
	tosController.view.hidden = YES;
	navigationController.view.hidden = NO;

	// Store acceptance in UserDefaults
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	[userDefaults setBool:YES forKey:TERMS_OF_USE_ACCEPTED];
}

Now the ToS screen is only shown until the user taps the accept button. Your customers will be happy, as will your lawyers.

written by Nick \\ tags: , ,

Apr 03

A UIWebView is great for displaying rich text; you just format your text as HTML. But how do you display images in a UIWebView?

Since the iPhone is almost always connected you could just use a regular image tag referencing an image on a server somewhere:

<img src="http://example.com/img/foo.png" />

But that is less than optimal. What you really want to do is reference an image on the iPhone. One way to do this is to embed the image in the HTML using the data: URI scheme. Here’s an example:

<img src="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP
C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA
AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J
REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq
ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0
vr4MkhoXe0rZigAAAABJRU5ErkJggg==" alt="Red dot" />

Here’s a great tool for converting any small image to the base64 encoding required by data:image:
www.abluestar.com/utilities/encode_base64/index.php

Adding images this way will allow you to create great looking, self-contained help files and about us pages for your iPhone applications.

Update: See this post for an alternate way to display images: UIWebView – Loading External Images and CSS

written by Nick \\ tags: , ,

Mar 31

Beta 2 of the iPhone SDK was launched last week and as advertised it now includes Interface Builder. Unfortunately somebody forgot to tell the documentation department at Apple about this. There is no documentation about Interface Builder related to the iPhone to be found anywhere. Zip. Nada. Nothing.

Of course there’s the general Introduction to Interface Builder User Guide, but does not deal with any of the specifics for building an iPhone interface.

Online forums are filled with messages from bewildered iPhone developers. Some brave bloggers have posted tutorials, which in many cases feel like putting a square peg into a round hole just to get the darn thing working.

I’ve found one tutorial that stands out from the crowd and actually makes sense from a Model-View-Controller (MVC) perspective. Check out Scott’s Reason #2: Interface Builder is buggy and has no documentation on his curiously named “I Hate the iPhone SDK” blog.

written by Nick \\ tags:

Mar 27

If you’re using a UIWebView to display rich text from a string of HTML (as I described yesterday) you can even include HTML links to external URLs.

However when the external web page is displayed it is done using the same scale as your local HTML page. Unless the two pages are about equal in size, the visual transition is less than pleasing.

You can use the scalesPageToFit property of UIWebView to fix this. See line 13:

- (void)loadView
{
  // Create a custom view hierarchy.
  CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
  UIView *view = [[UIView alloc] initWithFrame:appFrame];
  view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
  self.view = view;
  [view release];

  CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
  webView = [[UIWebView alloc] initWithFrame:webFrame];
  webView.backgroundColor = [UIColor whiteColor];
  webView.scalesPageToFit = YES;
  [self.view addSubview:webView];
}

But this causes another problem: your internal HTML page probably looks a lot smaller than you would like. To fix this we’ll use a trick that web app developers use to optimize web sites for the iPhone.

To the HTML code add a meta viewport tag:

<meta name="viewport" content="width=320"/>

The complete code looks like this:

NSString *html = @"<html><head><title>The Meaning of Life</title><meta name=\"viewport\" content=\"width=320\"/></head><body><p>...really is <b>42</b>!</p></body></html>";
[webView loadHTMLString:html baseURL:nil];

written by Nick \\ tags: , ,

Mar 26

A UITextView is great for displaying multiple lines of simple text. But when you need something more fancy, like headlines or highlighting text, then you should look at UIWebView. It’s not just capable of displaying web pages from a URL, but you can also specify the HTML you want to display as a string.

Create a UIWebView in a controller:

- (void)loadView
{
  // Create a custom view hierarchy.
  CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
  UIView *view = [[UIView alloc] initWithFrame:appFrame];
  view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
  self.view = view;
  [view release];

  CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
  webView = [[UIWebView alloc] initWithFrame:webFrame];
  webView.backgroundColor = [UIColor whiteColor];
  [self.view addSubview:webView];
}

Then you can add your rich text to the view like this:

NSString *html = @"<html><head><title>The Meaning of Life</title></head><body><p>...really is <b>42</b>!</p></body></html>";
[webView loadHTMLString:html baseURL:nil];

The documentation is not very clear on what the baseURL is used for in the context of HTML that is loaded from a string.

written by Nick \\ tags: ,