Tutorial: All About Images (Part 1) — UImage & UIImageView

UIViewContentModeScaleAspectFill

Figure 1: Screenshot of AllAboutImages Tutorial 1

Over the next few days I will be posting tutorials about using images in iOS. In this, the first in the series, you will learn how to load an image from your app’s bundle and display it in a UIImageView. A screenshot of the completed tutorial is shown in Figure 1. You’ll add each of the tutorials as a tab in a tab bar application to get some experience with UITabBarController.

Subscribe to me on YouTube To get started create a create a new tab bar application and call it AllAboutImages . Take a moment to familiarize your self with the files XCode has generated. You have two view controllers, one for each of the tabs in the tab view controller. You can compile and run the project now and you will see the placeholder views XCode has created.

Declaring an IBOutlet and an IBAction.

The UI for the first view controller will consist of a UIImageView to display the image and a UISegmentedControl to modify some of the options of the imageView. You’ll begin by adding an IBOutlet for the image view and an IBActionFirstViewController.h and add the highlighted code.

@interface FirstViewController : UIViewController {
    UIImageView* mImageView;
}

@property (nonatomic, retain) IBOutlet UIImageView* imageView;

- (IBAction)contentModeChanged:(UISegmentedControl*)segmentedControl;

@end

Line 2 defines an ivar to store the image view, line 5 defines a property you will use to access it and line 7 declares an action that will be take when the value of the UISegmentedControl changes.

Open FirstViewContoller.m and add your synthesis code:

@implementation FirstViewController
@synthesize imageView = mImageView;

If you’ve done another tutorial from this site you’ll know whats coming next… Since you have added a property with the retain attribute, before going any further you should add your cleanup code to viewDidUnload and dealloc:

- (void)viewDidUnload
{
    self.imageView = nil;
    [super viewDidUnload];
}
- (void)dealloc
{
    [mImageView release];
    [super dealloc];
}

Layout out the User Interface

Figure 2: The UI Layout

From the object library drag a UIImageView and a UISegmentedControl onto your view and resize them to look something like Figure 2.

Select the segmented control and in the attributes inspector increase the number of segments to 3 and name the segments “Fill”, “AspectFit” and “AspectFill”.

With the segmented control still selected switch to the connections inspector and drag from the “Value Changed” action to the File’s Owner object. With this connection in place UIKit will call your contentModeChanged: method any time the user changes the value of the segmented control.

Finally click on the File’s Owner icon and connect the imageView outlet to the image view. This completes the layout. Now is a good time to save your work.

Adding an Image to your App’s Bundle

To add an image to your app’s bundle simply locate an image on your computer and drag it into the “Supporting Files” folder of your project in XCode. If you need an image you can download the one I am using KinkakuJi.png

Loading an Image from your App’s Bundle

Now open FirstViewController.m and modify the commented out viewDidLoad: method as shown below.

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSAssert(self.imageView, @"self.imageView is nil. Check your IBOutlet connections");
    UIImage* image = [UIImage imageNamed:@"KinkakuJi"];
    NSAssert(image, @"image is nil. Check that you added the image to your bundle and that the filename above matches the name of you image.");
    self.imageView.backgroundColor = [UIColor blackColor];
    self.imageView.clipsToBounds = YES;
    self.imageView.image = image;
}

Let’s look at that line by line. Line 4 is an error check to ensure that the IBOutlet for your image view is correctly connected. Line 5 loads the image from your app’s bundle. Notice you can omit the file’s suffix. Line 6 is an error check to ensure the file loaded correctly. Line 7 sets the background color of the image view so that you can see if the image is not filling the image view. By default UIImageView will draw outside its bounds. Line 8 changes the this behavior, telling the image view to crop (or clip) the image to its bounds. Finally, line 9 sets the image to display.

You’re almost ready to run your project. The final task you need to complete is responding to the UISegmentedControl

Responding to Changes in Value of a UISegmentedControl

By default, UIImageView will stretch or shrink your image so that it completely fills the view’s bounds. This is not always what you want. If the aspect ratio, that is the ratio of the width to the height, of the view does not match the aspect ratio of the image then the image will be distorted. You can change this by setting the contentMode property of the image view. The default mode is UIViewContentModeScaleToFill. The mode UIViewContentModeScaleAspectFit ensures the complete image is displayed, without distorting the aspect ratio, leaving black space around the image as needed. This is similar to watching a movie in letter box format for example. The mode UIViewContentModeScaleAspectFill fills the bounds of the image view without distorting the image cropping the image as needed. This all makes a lot more sense when you can see it with your eyes. So add the following code to FirstViewController.m

- (IBAction)contentModeChanged:(UISegmentedControl *)segmentedControl
{
    switch(segmentedControl.selectedSegmentIndex)
    {
        case 0:
            self.imageView.contentMode = UIViewContentModeScaleToFill;
            break;
        case 1:
            self.imageView.contentMode = UIViewContentModeScaleAspectFit;
            break;
        case 2:
            self.imageView.contentMode = UIViewContentModeScaleAspectFill;
            break;
    }
}

This code uses the selectedSegmentIndex property of the segmented control to set the contentMode of the image view.

Save you work and compile and run the project. If all goes well you should see something similar to Figure 1. If your code does not work and you cannot diagnose the problem you may want to download the completed code from the link at the end of the article.

In the next tutorial I’ll look at how you can put the image view into a scroll view to allow panning and zooming like Photos app. It should be up in the next day or two, so check back soon.

Download the Source Code

You can download the source code for the completed tutorial here: AllAboutImages1.zip

I know ads in blogs are annoying, but they help us keep this site up and running. Please consider visiting one of our sponsors, like the one below.


Posted in Tutorial | Tagged , , , , | 2 Comments

Snippet: Playing a System Sound

If you are using ARC see: “Playing a System Sound (ARC Version)”.

Today I needed to add a simple beep sound to an app. The code snippet below shows all the relevant calls.

// Required import... Also need AudioToolbox.framework
#import <AudioToolbox/AudioToolbox.h>

// ivar 
SystemSoundID mBeep;

// Create the sound ID
NSString* path = [[NSBundle mainBundle] 
                     pathForResource:@"Beep" ofType:@"aiff"];
NSURL* url = [NSURL fileURLWithPath:path];
AudioServicesCreateSystemSoundID((CFURLRef)url, &mBeep);

// Play the sound
AudioServicesPlaySystemSound(mBeep);

// Dispose of the sound
AudioServicesDisposeSystemSoundID(mBeep);

If I had found a page with a snippet like this on it it would have saved me some time. Maybe this page will save you some!

Posted in Code Snippets | Tagged , , , | 3 Comments

Tutorial: Building a Web Browser with UIWebView (Part 3)

Screenshot of the Complete WebBrowser Tutorial

Figure 1: Screenshot of the Completed WebBrowser Tutorial

This tutorial is part three of a three part tutorial on creating a web browser using UIWebView (see Part 1 & Part 2). A screenshot of the completed project is shown in Figure 1.

If you completed part two of the tutorial and it seems to be working well you can continue with your current project, or if you prefer you can download the starting point here: WebBrowser2.zip

Outstanding Issues

In this tutorial, you will fix the issues noted at the end of part two, I have reordered them slightly so that dependent issues are dealt with in order:

  • the keyboard displayed is not the URL keyboard so it is awkward to type a URL,
  • the keyboard auto-capitalization mode is inappropriately set,
  • there is no clear button in the address field,
  • if the user omits the http:// the page will not load,
  • the address field can get out of sync with the displayed page,
  • if an error occurs, the user is never informed.

Fixing the Keyboard Configuration

Currently the keyboard type that appears is not really suitable for entering URLs. Much of the punctuation needed requires switching to another keyboard.

The keyboard type that appears when any subclass of UIResponder becomes first responder is controlled by the keyboardType of the object. Possible values are:

  • UIKeyboardTypeDefault
  • UIKeyboardTypeASCIICapable
  • UIKeyboardTypeNumbersAndPunctuation
  • UIKeyboardTypeURL
  • UIKeyboardTypeNumberPad
  • UIKeyboardTypePhonePad
  • UIKeyboardTypeNamePhonePad
  • UIKeyboardTypeEmailAddress

In your case you can make the appropriate keyboard appear by inserting the following line in viewDidLoad in WebBrowserViewController.m.

    address.font = [UIFont systemFontOfSize:17];
    address.keyboardType = UIKeyboardTypeURL;
    [address addTarget:self 
                action:@selector(loadAddress:event:) 
      forControlEvents:UIControlEventEditingDidEndOnExit];
    [navBar addSubview:address];

If you compile and run your code now you should find that the keyboard that appears has ‘/’ and ‘.com’ keys.

There are still problems with the keyboard. When you use backspace to erase the currently displayed URL the keyboard automatically switches to uppercase.

The keyboard auto-capitalization behavior is controlled by the autocapitalizationType property of the first responder. Possible values are:

  • UITextAutocapitalizationTypeNone
  • UITextAutocapitalizationTypeWords
  • UITextAutocapitalizationTypeSentences
  • UITextAutocapitalizationTypeAllCharacters

Add this line of code after the one you just added:

    address.autocapitalizationType = 
                UITextAutocapitalizationTypeNone;

Compile and run your project and confirm that the auto-capitalization behavior is now correct.

Adding a Clear Button to the Address Field

As it stands, if the address field is already displaying an address you have to backspace until you have erased the entire address. This is quite tedious. In Safari when you edit an address an ‘X’ in a grey circle appears on the right hand side of the address field. Touching this icon erases the complete field. Clearly this behavior is more desirable.

The clearButtonMode property of UITextField controls when this “clear button” is displayed. Possible values are:

  • UITextFieldViewModeNever
  • UITextFieldViewModeWhileEditing
  • UITextFieldViewModeUnlessEditing
  • UITextFieldViewModeAlways

In your case, the closest match to the behavior exhibited by Safari is UITextFieldViewModeWhileEditing.

Just after the last line of code you added add:

address.clearButtonMode = 
             UITextFieldViewModeWhileEditing;

Save everything, compile and run your project now. You should see the clear button appear when you start editing a URL.

Correcting User Input

Most modern web browsers allow the user to drop the “http://” prefix on URLs. At this point, if a user omits this prefix in your browser the page will not load. (In fact, an error will be generated but will have pushed off dealing with errors until the end of the tutorial.)

It is fairly simple to fix this. Edit your loadAddress method as follows.

- (void)loadAddress:(id)sender event:(UIEvent *)event
{
    NSString* urlString = self.addressField.text;
    NSURL* url = [NSURL URLWithString:urlString];
    if(!url.scheme)
    {
        NSString* modifiedURLString = [NSString stringWithFormat:@"http://%@", urlString];
        url = [NSURL URLWithString:modifiedURLString];
    }
    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}

This solution uses the scheme property of the NSURL class to determine whether the user set a scheme (the part of the URL before the colon). If not it prepends “http://” this will correct entries like www.yahoo.com to http://www.yahoo.com.

Compile and run your your code. Enter “www.yahoo.com” and verify that the correct web page is loaded.

Keeping the Address Field in Sync

If you use the back or forward buttons, or follow a link, the address bar is not in sync with the displayed page. It turns out there is more to solving this problem than meets the eye. So first define a method to update the address bar.

Add the following definition to WebBrowserViewController.h:

- (void)updateAddress:(NSURLRequest*)request

The implementation of this method looks like this. Add this to your WebBrowserViewController.m.

- (void)updateAddress:(NSURLRequest*)request
{
    NSURL* url = [request mainDocumentURL];
    NSString* absoluteString = [url absoluteString];
    self.addressField.text = absoluteString;
}

The key point about this code is that instead of requesting the URL from NSRequest you are requesting the mainDocumentURL which is more appropriate to show in the address field.

So this code solves how to display an updated URL in the address field, but not how and when you will be informed.

On the face of it, it should be simple. According to Apple documentation the delegate method webView:shouldStartLoadWithRequest:navigationType: is called before the web view starts loading content.

So the following code modification should ensure that your address field is always in sync with the currently displayed page.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    [self updateAddress:request];
    return YES;
}

It turns out that this works most of the time, except when you use the “back” and “forward” buttons. It seems that sometimes calling goBack or goForward does not call shouldStartLoadWithRequest.

A reasonable workaround for this problem is to add the following code to your webViewDidFinishLoad:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self updateButtons];
    [self updateTitle:webView];
    NSURLRequest* request = [webView request];
    [self updateAddress:request];
}

Reporting Errors to the User

When UIWebView encounters an error it invokes the didFailLoadWithError method of its delegate.

You could handle the error right in the delegate method, but as your programs get bigger having a central error handling routine will be useful (a more general solution to this will be the topic of a future tutorial). So just add the highlighted code to your didFailLoadWithError.

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self updateButtons];
    [self informError:error];
}

Since this method is not yet declared you should add a declaration in your header file.

- (void)informError:(NSError*)error;  

The UIAlertView class provides a convenient way of alerting users of errors.
Add the following code to WebBrowserViewController.m.

- (void)informError:(NSError *)error
{
    NSString* localizedDescription = [error localizedDescription];
    UIAlertView* alertView = [[UIAlertView alloc] 
                              initWithTitle:@"Error" 
                                    message:localizedDescription delegate:nil 
                          cancelButtonTitle:@"OK" 
                          otherButtonTitles:nil];
    [alertView show];
    [alertView release];
}

The NSError class offers a variety of messages, but for your simple error handler the localizedDescription is the most appropriate. As regards the UIAlertView since there is no other sensible action that the user can do other than acknowledge the error a simple “OK” button will suffice, that is, “retry” or other options probably don’t make sense here.

Once you’ve made this change you can compile and run your project. You should now have a fully functional, albeit at a basic level, web browser.

Conclusion

Over the the three tutorials of this project you have learned a lot. You have used Interface Builder to layout a basic UI that contained a UIToolbar, a UIWebView and a number of UIBarButtonItems. You augmented this UI programmatically with a UINavigationBar, UILabel and a UITextField. To keep your UI in sync with the UIWebVIew your UIViewController subclass implemented the UIWebViewDelegate protocol and, when thing went wrong, you used UIAlertView to inform the user. Not bad for a few hours work!

Download the Source Code

You download the source code for the completed tutorial here: WebBrowser3.zip

This web site is ad supported. If you would like to see other great tutorials like this one please visit one of our sponsors.


Posted in Tutorial | Tagged , , , , , , | 12 Comments