iOS Designs to iPhone · process255/Dev-Denver Wiki
This tutorial focuses on taking a photoshop comp of an iPhone app and building a working iPhone app that matches the comps (as much as possible). The designs intentionally stay within the constraints of a "normal" iPhone app for ease of demonstration.
The tutorial will focus on two designer friendly aspects of iPhone app development:
- Use of Xcode's relatively new storyboard feature.
- Use of iOS 5's appearance customization apis (many of the visual customizations that are now trivial were a bit of a pain pre iOS 5).
PhotoshopLets go through the process of taking a designer's photoshop comps and making a functional iPhone app from them. Here are screenshots showing the startup screen and the 3 views of our application.
Startup Screen
Speakers
Speaker Detail
Sponsors
In order to speed the process up I've sliced up the images we're going to use in the app and included them in "Sliced-Images"
Sliced Images
Ok, lets get started and make an iPhone app. Images are cool and all but we want an app.- Open Xcode.
- Create a new project in Xcode. File > New > Project...
- Select iOS Application > Single View Application > Next
- Product Name is "Dev-Denver"
- Company Identifier is your domain i.e. "com.process255"
- Class Prefix "DD" (Develop Denver)
- Device Family "iPhone"
- Check "User Storyboards" and "Use Automatic Reference Counting"
- Leave "Include Unit Tests" unchecked (you should use these in real life).
- Click Next
Project Options
- Choose a folder to save the project in.
- Click Create
- Make sure the Supported Device Orientations has Portrait selected and the other deselected.
- Drag app-icon.png from the "Sliced-Images" directory to the first App Icons box.
- Drag app-icon@2x.png from the "Sliced-Images" directory to the Retina Display App Icons box.
- Check "Prerendered" in the Display Icons section.
- Drag Default.png from the "Sliced-Images" directory to the first Launch Images box.
- Drag efault@2x.png">Default@2x.png the from "Sliced-Images" directory to the Retina Display Launch Images box.
These steps added our application's icon and launch image files for both non-retina (pre iPhone 4) and retina screens. You'll notice that the retina images always have the same name as the regular non-retina file name with the additional suffix of @2x. This is Apple's convention for retina bitmap assets. The sdk will load and display the appropriate image based on the device. If no @2x version of a file is found the non-retina image will be used.
Project Options
Now we're going to rename "DDViewController" to "DDSpeakerViewController".
- Select "DDViewController.h". Then highlight the text "DDViewController" next to "@interface".
- Right+click or control+click the selection and choose Refactor > Rename…
- Change the name to "DDSpeakerViewController" and click Preview.
- Click Save (Xcode will ask if you want to take automatic snapshots, click Disable.
DDSpeakerViewController.h should now look like this (I deleted the copyright crap up top):
#import <UIKit/UIKit.h>@interface DDSpeakerViewController : UIViewController@end
Now that we've handled some of the initial busywork of creating a new project and configuring it lets dive into storyboards.
StoryboardsIntroduced in Xcode 4.2 with the release of iOS 5, storyboards are Apple's recommended method for laying out Views, View Controllers and the flow of interactivity in Mac and iOS apps. Storyboards expand on Apple's previous user interface files known as "nibs" which were originally .nib and recently .xib files. Think of storyboards as a bundle of user interface files with additional interaction and flow information. While its possible to create iOS apps without storyboards, a great deal of additional code is required to do so.
Select the "MainStoryboard.storyboard" file, you'll see this:
MainStoryboard.storyboard
We're going to modify this file now and plan out our app.
Based on our photoshop files we are going to need at least 3 view controllers. We'll soon see that there are some additional non-visual controllers that will need to be added to "MainStoryboard.storyboard".
1) Select 'Speaker View Controller' on the canvas and delete it by pressing the delete key.
2) Search for "UITabBarController" in the Object Library. Physically drag the Tab Bar Controller object onto the canvas.
3) Select "View Controller - Item 1" and delete it by pressing the delete key.
4) Search for "UITableViewController" in the Object Library. Physically drag the Table View Controller object onto the canvas and place it above 'View Controller - Item 2'.
5) With 'Table View Controller' still selected, from the 'Editor Menu' select Embed In > Navigation Controller. This will give us a Navigation Bar at the top of what will become the speakers list (a table view controller).
6) Select 'Tab Bar Controller', right+click or control+click and drag towards 'Navigation Controller'. This will draw a line that will allow you connect the two in the same manner as 'Tab Bar Controller' and 'View Controller - Item 2' are currently connected. When you let go you'll see a menu titled 'Storyboard Segues'. Select "Relationship - View Controllers".
7) Select 'View Controller - Item 2', from the 'Editor Menu' select Embed In > Navigation Controller. This will give us a Navigation Bar at the top of what will become the sponsors view.
8) Getting close! We need one more controller on the screen. Zoom out a bit by clicking on the "-" magnifying glass icon, we need some more space. Search for "UIViewController" in the Object Library. Physically drag the View Controller object onto the canvas and place it to the right of the 'Table View Controller'.
9) We have all our view controllers now. A bit more storyboarding and then we'll switch over to code for a bit. Zoom in on 'Table View'. Select the white row at the top underneath the "Prototype Cells". You may need to expand the Table View Controller > Table View in the "Table View Controller Scene" to select the "Table View Cell". Once the cell is selected right+click or control+click and drag the arrow to the right. Let go on top of the new 'View Controller' created in step 8. When you deselect you'll see the now familiar "Storyboard Segues". Choose "Push".
10) Zoom in on the far left view controller "Tab Bar Controller". Our process has our tabs out of order. We need to swap them. Select "Item 2" and drag it to the right. Now we have "item" and "item 2". Great.
Lets go write some code! (after we import some images)But first lets check out our app. We can publish now and see the app in iOS simulator. It doesn't do much but it we have a real app with a couple tabs. From the "Product" menu select "Run". Your app will build and you should see something like this.
Adding imagesOk, back to Xcode. Now that we've got the basic structure of our storyboard we can import our images into the project and begin to create the classes that will back our storyboard view controllers. Amazingly we will only need one additional class in the project. Right now we have two classes (that we get to much around with much at any rate):DDAppDelegate and DDSpeakerViewController. Time to add some images and create our new class.
1) Select "Dev-Denver" in the Project Navigator, now go to the File menu > New > Group. Name your new group "Resources".
2) Open Finder and select all the images inside the "Sliced-Images" folder (except for Default.png, efault@2x.png">Default@2x.png, app-icon.png, app-icon@2x.png since we already have those in the project). Make sure you can view Xcode. With the files selected, drag them to your newly created "Resources" group. 3) You'll see a dialog confirming your action. Make sure to check "Copy items into destination group's folder (if needed)." Click "Finish".
Now you have a bunch of images in your project. They will be bundled with your app. We are going to use these assets to customize the appearance of the app.
One more classSelect the "Dev-Denver" group in the Project Navigator.
- From the 'File' menu select 'New > File..'. In the iOS section choose 'Cocoa Touch > Objective-C class'.
- Click Next.
- Name the file 'DDSpeakersTableViewController'.
- Select 'UITableViewController' as the Subclass of.
- Make sure 'Targeted for iPad' and 'With XIB for user interface' are unchecked.
- Click Next.
- On the following screen click "Create".
Modifying the codeThis next little bit has a lot of code, make sure to read through it, is heavily commented. DDSpeakersTableViewController is the Table View Controllerthat will contain the list of speakers.
Replace the contents of DDSpeakersTableViewController.h with:
#import <UIKit/UIKit.h>@interface DDSpeakersTableViewController : UITableViewController@end
Replace the contents of DDSpeakersTableViewController.m with:
#import "DDSpeakersTableViewController.h"#import "DDSpeakerViewController.h"@interface DDSpeakersTableViewController ()@property(nonatomic, retain) NSArray *speakers;@end@implementation DDSpeakersTableViewController@synthesize speakers = _speakers;- (void)viewDidLoad { [super viewDidLoad]; // set up the datasource for our table, normally this would be something dynamic // conecting to a server or using a persistent store of some type on the device self.speakers = [NSArray arrayWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: @"Drew Dahlman", @"name", @"Drew divides his time between designing and developing. He is a jack of all trades and as you can see bears an uncanny likeness to a famous actor… Rodney Dangerfield.", @"description", @"Develop Denver", @"organization", @"drew-thumb.png", @"thumb", @"drew.jpg", @"image", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Pete Larson", @"name", @"94cb18183f2b1b15400490dff2d167b8660f7861quot;When he is not making and eating peanutbutter sandwiches, Pete Larson divides his time between drinking heavliy (pictured) and occasionally coding.94cb18183f2b1b15400490dff2d167b8660f7861quot; -Eric Wedum", @"description", @"Jiffy Media", @"organization", @"pete-thumb.png", @"thumb", @"pete.jpg", @"image", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Sean Dougherty", @"name", @"As a former art major, Sean Doughery understands the importance of visual impact. Check out the guns ladies, LOOK but don't touch, he is spoken for (sorry).", @"description", @"process255", @"organization", @"sean-thumb.png", @"thumb", @"sean.jpg", @"image", nil], [NSDictionary dictionaryWithObjectsAndKeys: @"Sean & Matt", @"name", @"These dudes work it with their legs.", @"description", @"Legwork", @"organization", @"sean-matt-thumb.png", @"thumb", @"sean-matt.jpg", @"image", nil], nil];}#pragma mark - Table view data source// return the number of sections our table should contain- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return 1;}// return the number of rows our table should contain- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // return the number of speakers return [self.speakers count];}// return the height of table cells- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 93;}// called when a new table cell is created- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"SpeakerCell"; // grab a queued cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // create a new cell if one is not in the queue if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // grab the speaker dictionary NSDictionary *speaker = [self.speakers objectAtIndex:indexPath.row]; // grab a reference to the thumbnail image view in the cell UIImageView *thumb = (UIImageView *)[cell viewWithTag:1]; // set the thumbnail imageview's image in cell's image thumb.image = [UIImage imageNamed:[speaker objectForKey:@"thumb"]]; // grab a reference to the name label in the cell UILabel *nameLabel = (UILabel *)[cell viewWithTag:2]; // set the name in the cell's name label nameLabel.text = [speaker objectForKey:@"name"]; return cell;}// Do some customisation of our new view when a table item has been selected- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Make sure we're referring to the correct segue if ([[segue identifier] isEqualToString:@"ShowSpeaker"]) { // Get reference to the destination view controller DDSpeakerViewController *vc = [segue destinationViewController]; // get the selected index NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row]; // grab the speaker dictionary NSDictionary *speaker = [self.speakers objectAtIndex:selectedIndex]; // Pass the name to our view controller vc.name = [speaker objectForKey:@"name"]; // Pass the description to our view controller vc.description = [speaker objectForKey:@"description"]; // Pass the imagePath to our view controller vc.imagePath = [speaker objectForKey:@"image"]; // Pass the organizagtion to our view controller vc.organization = [speaker objectForKey:@"organization"]; }}@end
Now lets turn our attention to the DDSpeakerViewController class. This is the view controller for the detail view of a speaker that is shown when a speaker is tapped in the speaker list (DDSpeakersTableViewController).
Replace the contents of DDSpeakerViewController.h with:
#import <UIKit/UIKit.h>@interface DDSpeakerViewController : UIViewController@property(nonatomic,copy) NSString *name;@property(nonatomic,copy) NSString *organization;@property(nonatomic,copy) NSString *imagePath;@property(nonatomic,copy) NSString *description;@end
Replace the contents of DDSpeakerViewController.m with:
#import "DDSpeakerViewController.h"// private properties@interface DDSpeakerViewController ()@property(nonatomic, retain) IBOutlet UILabel *organizationLabel;@property(nonatomic, retain) IBOutlet UIImageView *imageView;@property(nonatomic, retain) IBOutlet UITextView *descriptionTextView;@end@implementation DDSpeakerViewController@synthesize organizationLabel = _organizationLabel;@synthesize name = _name;@synthesize organization = _organization;@synthesize imagePath = _imagePath;@synthesize description = _description;@synthesize imageView = _imageView;@synthesize descriptionTextView = _descriptionTextView;- (void)viewDidLoad { [super viewDidLoad]; // set the name self.organizationLabel.text = self.organization; // set the nav bar title self.title = self.name; // set the image self.imageView.image = [UIImage imageNamed:self.imagePath]; // set the description self.descriptionTextView.text = self.description; }@end
Onto the last bit of code. We now take a look at the App Delegate (DDAppDelegate). DDAppDelegate is the starting point for our app. It is here that we have an opportunity to customize the "theme" of our app. This code is also heavily commented, make sure to read through it. This class makes use of the new appearance api available in iOS 5 mentioned at the beginning of the tutorial.
Replace the contents of DDAppDelegate.h with:
#import <UIKit/UIKit.h>@interface DDAppDelegate : UIResponder <UIApplicationDelegate>@property (strong, nonatomic) UIWindow *window;@end
Replace the contents of DDAppDelegate.m with:
#import "DDAppDelegate.h"@implementation DDAppDelegate@synthesize window = _window;// run through some common components and make them look nifty- (void)customizeChrome { // customize nav bar background UIImage *navBarImage = [UIImage imageNamed:@"navbar.png"]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault]; // customize nav bar back button UIImage *backButton = [[UIImage imageNamed:@"back-button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 14, 0, 4)]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; // customize tab bar background UIImage* tabBarBackground = [UIImage imageNamed:@"tabbar.png"]; [[UITabBar appearance] setBackgroundImage:tabBarBackground]; // customize tab bar selection item image [[UITabBar appearance] setSelectionIndicatorImage:[UIImage imageNamed:@"tabbar-item.png"]]; // customize tab bar item label colors // Normal [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor colorWithRed:23.0/255.0 green:55.0/255.0 blue:55.0/255.0 alpha:1.0], UITextAttributeTextColor, [UIColor colorWithRed:52.0/255.0 green:132.0/255.0 blue:147.0/255.0 alpha:1.0], UITextAttributeTextShadowColor, [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset, nil] forState:UIControlStateNormal]; // Selected [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], UITextAttributeTextColor, [NSValue valueWithUIOffset:UIOffsetMake(0, 0)], UITextAttributeTextShadowOffset, nil] forState:UIControlStateSelected]; // grab a reference to the tab bar controller so we can set the images and prevent them // from being tinted, this is sort of a lame hack UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController; // grab a reference to the first view controller inside the tab bar controller UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0]; // setup the speakers tab icon UIImage* speakersIconNormal = [UIImage imageNamed:@"speakers-tab-bar-item.png"]; UIImage* speakersIconSelected = [UIImage imageNamed:@"speakers-tab-bar-selected-item.png"]; UITabBarItem *speakersItem = [[UITabBarItem alloc] initWithTitle:@"Speakers" image:speakersIconNormal tag:0]; [speakersItem setFinishedSelectedImage:speakersIconSelected withFinishedUnselectedImage:speakersIconNormal]; [navigationController setTabBarItem:speakersItem]; // setup the sponsors tab icon UIImage* sponsorsIconNormal = [UIImage imageNamed:@"sponsors-tab-bar-item.png"]; UIImage* sponsorsIconSelected = [UIImage imageNamed:@"sponsors-tab-bar-selected-item.png"]; UITabBarItem *sponsorsItem = [[UITabBarItem alloc] initWithTitle:@"Sponsors" image:sponsorsIconNormal tag:1]; [sponsorsItem setFinishedSelectedImage:sponsorsIconSelected withFinishedUnselectedImage:sponsorsIconNormal]; UIViewController* sponsorsController = [[tabBarController viewControllers] objectAtIndex:1]; [sponsorsController setTabBarItem:sponsorsItem];}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // set the status bar to black [application setStatusBarStyle:UIStatusBarStyleBlackOpaque]; [self customizeChrome]; return YES;}@end
Publishing now will show you the "theme" applied to our app. What remains is for us to go back to our storyboard and connect our user interface to the code we've just added above.
Select MainStoryboard.storyboard and select the table view. In the Identity Inspector change the class to `DDSpeakersTableViewController'.
Select the "Table View Cell" in the table view (now the 'Speakers Table View Controller Scene').
- Select the Attributes Inspector.
- Set the Identifier to "SpeakerCell".
Click the Size Inspector, check Custom and change the Row Height to 93.
Lets add a background to our cell. Search for "UIImageView" in the Object Library. Physically drag the Image View object onto the Table View Cell. Set it's frame to X = 0, Y = 0, Width = 320, Height = 93.
Change the Image to "cell-background.png" and Highlighted to "cell-background-selected.png" in the Attributes Inspector.
Now we'll add the speaker thumbnail. Drag another UIImageView from the Object Library onto the Table View Cell. Set it's frame to X = 9, Y = 6, Width = 76, Height = 79.
For visual reference change the Image to "drew-thumb.jpg" in the Attributes Inspector (these images are dynamically assigned to each row but this gives us a nice preview in our storyboard). Set the tag to 1 (important, we reference this in our code).
The label for our cell is a UILabel. Search for "UILabel" in the Object Library. Physically drag the Label object onto the Table View Cell. Set it's frame to X = 94, Y = 36, Width = 152, Height = 25.
In the Attributes Inspector change:
- Text: "Drew Dahlman" (again for reference only, this is dynamically set by our code)
- Font: Helvetica Neue Bold 10.0
- Tag: 2 (important, we reference this in our code)
- uncheck "Autoshrink"
Select the to navigation bar in the Table View. Set the Title in the Attributes Inspector to "Speakers".
Whew! We're done with this cell and table. A quick change and then we'll publish to check our progress. Select the "Item" text underneath the question mark graphic in the top Navigation Controller and change the Title to "Speakers" and the Image to "speakers-tab-bar-item.png".
Do the same thing with the "Item 2" text underneath the question mark graphic in the bottom Navigation Controller and change the Title to "Sponsors" and the Image to "sponsors-tab-bar-item.png".
Save your storyboard and publish (command+r). We have speakers!
Speaker DetailSelect the top right view controller in your story board. Change it's Class to "DDSpeakerViewController" in the Identity Inspector.
We have 4 user interface controls to add to this view. With all of your storyboarding skills acquired above we'll handle these with one screenshot. Here are the controls in order of how you should put them on the view and their properties:
Background (UIImageView)
- Set it's frame to X = 0, Y = 0, Width = 320, Height = 412. (Size Inspector)
- Image: speaker-detail-bg.jpg (Attributes Inspector)
Image (UIImageView)
- Set it's frame to X = 34, Y = 25, Width = 250, Height = 165. (Size Inspector)
Organization Label (UILabel)
- Set it's frame to X = 29, Y = 227, Width = 172, Height = 21. (Size Inspector)
- Text: "Organization" (Attributes Inspector)
- Font: "Helvetica Neue Bold 17.0" (Attributes Inspector)
- uncheck "Autoshrink"
- Text Color: Use the rgb sliders Red 217, Green 41, Blue 8.
Description (UITextView)
- Set it's frame to X = 22, Y = 244, Width = 264, Height = 97. (Size Inspector)
- Text: "Description" (Attributes Inspector)
- Font: "Helvetica Neue 14.0" (Attributes Inspector)
- Text Color: Use the rgb sliders Red 52, Green 52, Blue 52.
- Behavior Editable is unchecked.
- Scrollers: all unchecked
- Background Color clear
- Bounces unchecked
This is what your View Controller should look like:
Two more steps to complete the Speaker Detail View wiring.
Right+Click or Control+Click on the battery in the Speaker View Controller. While pressing down, drag down to the UIImageView placeholder. You will see a line stretch down. When the press is released a menu will appear. Select "imageView".
Repeat this again except this time drag from the battery to the Organization Label. Select "organizationLabel".
Repeat this one last time dragging from the battery to the Description Text View. Select "descriptionTextView".
Select the grey circle Segue on the arrow between the Speakers Table View Controller and the Speakers View Controller and set it's identifier to "ShowSpeaker".
We now have details!Publish your app and check it out in the simulator. Tapping a speaker shows their detail.
Home Stretch. A very simple sponsors screen.Select the last lonely View Controller in the lower right. This one is so simple we don't even bother with a class for it. As with the Speakers Table View Controller we need to select the Navigation Item and add the title "Sponsors".
We're going to add one last component, a UIImageView. This is old hat by now.
- Drag a UIImageView from the Object Library.
- Set it's frame to X = 86, Y = 152, Width = 148, Height = 64. (Size Inspector)
- Image: desk.png (Attributes Inspector)
Last step... so close. to. done.
Select the view and set the Background to black.
Publish and enjoy.