The Jungle, Part 2: More UI Elements


This week we’re going to continue from Part 1 and explore additional UI elements. Before we begin though, a bit of (old) news—iOS 5.0 was released on Wednesday, and with it Xcode 4.2. As such, from now on I will be working with Xcode 4.2 and the 5.0 SDK. Once we get the basics down we can discuss compatibility with older versions and how to check the system version, but we’ll use the newest and greatest for now.

As usual, Xcode 4.2 is available on the App Store. The Xcode 4.2 build is 4D199 (you’d be surprised how difficult it was to find that anywhere else—important if you’re upgrading from a Developer Preview), which you can get either from the Welcome screen or by going to Xcode > About Xcode. There is an installation issue floating around in which you may be constantly installing 4.1. In that case, I’d recommend uninstalling Xcode first, and then running the “Install Xcode.app” that the App Store downloads. That fixed the problem for me.

Once Xcode is installed, open up the project from last time, and dive into RootViewController.xib.

Interface First

With Xcode 4′s integration of Interface Builder and the code, it breaks down the boundaries of code versus interface and allows you to easily jump back and forth as ideas come into your head. This time, we’ll put together the interface first and then hook it up to code—even to code that doesn’t exist yet.

First, start by moving the text field up a bit so we have some more vertical room below. Drag it up, following the blue guidelines, until it snaps into place a bit below the button.

Segmented Control Settings

Segmented Control Properties


Drag out a Segmented Control and place it near the middle of the screen. Extend the width until you get to the blue guidelines at the left and right of the screen. Go to the Attributes Inspector, and use the Segment pop-up menu to select “Segment 0 – First”. Underneath, change the Title to “Colors” and watch the change propagate to the UI as well. Change the title of the second segment to “Progress” using the pop-up to change the segment.

The segmented control works similar to the tabs you see across OS X, especially in System Preferences—users expect them to “swap” view content to something else, and they are usually placed above the content that you expect to swap. For simpler views it may be easy enough to place views overlapping each other and hide or show them as necessary (UIView has a hidden property that can be set to YES or NO). However, this is quite cumbersome and doesn’t scale very well. A better alternative is to create a plain UIView and set that to individual views as necessary. Therefore, drag out a basic UIView, align it underneath the segmented control, make it as wide, and drag it down until the blue guidelines appear at the bottom. It should look like this:

Main View

New Main View

Now select the view, and copy (Command-C) it. Click outside of the main view (on the main canvas area) and paste (Command-V). You should get a blank view the size of the original, floating around on the canvas. Click on the canvas again, and paste another view. They will be the views you’ll swap in with the segmented control.

Set the background of both views to Group Table View Background Color. It may be easier to set their color to transparent, but this has a major impact on drawing performance—it is much faster to set a specific opaque color.

Drag out UI elements from the Library to build two views like you see here. The exact positioning doesn’t matter, but make sure to get the correct elements. The first view contains a standard UIView at the top with the Background color set to 50% gray (click on the left part of the Background control in the inspector to get the standard OS X color picker). Underneath are four labels, right-aligned with a standard shadow (most UI text in iOS is drawn with a shadow) and four sliders next to the labels. Here, don’t worry about the edge guidelines—go right up to the edge of the view. You’ve already accounted for the edge guides in the main view.

Sliders View

Sliders View

The second view contains a few more elements—a bold-font label, a regular-font label, a stepper control (new in iOS 5), another bold-font label, a switch, a progress view, and a button. None of the views except the labels have been customized away from their default appearance. The regular label should have its text set to 0%, rather than the standard “Label”.

Progress View

Progress View

We have to set some properties on the stepper for it to work with us. Select it then open the Utilities Inspector. The Minimum value should stay at 0 but the Maximum should go up to 100. Increase the Step to 10 to have it change by 10, from 0 to to 100. Also, select the progress view and set the Progress to 0.

Making Connections

Our interface design is now done, so let’s make the connections. Open the Assistant editor (The second button in the “Editor” group in the toolbar). Make sure the file in the right pane is RootViewController.h (you can use the jump bar). Control-drag from the blank view in the main view to a blank line in your header. You’ll get a gray rectangle that says “Insert Outlet or Outlet Collection”.

New Connection

New Connection

Let go, and you get a little popup that lets you configure the outlet name and type. Call the outlet sectionView. Click Connect, and a new property will appear with a blue flash. To connect the other smaller views, you’ll have to Control-drag from the icons on the left edge of the IB view to the code. See screenshot below:

Connection from Sidebar

Connection from Sidebar

The view with the sliders should be called colorsView; the other should be called progressView.

New Outlet Popup

New Outlet Popup

Then, connect all five elements in the colorsView to the header as well—displayColor, redSlider, greenSlider, blueSlider, and alphaSlider. The progressView isn’t quite as straight-forward, but it’s not difficult. Connect the label next to “Fill:” as amountLabel, the stepper as amountStepper, the switch as animatedSwitch, and the progress view as progressIndicator.

Now we’ll connect the actions. Start off with one of the sliders—right click on one of them, and scroll down until you find the Value Changed Event.

Control Events

Control Events

Click and drag from the circle to the right until you come to a new line underneath the actions—you’ll get a blue line you’ll be familiar with now. You’ll get the gray rectangle that says “Insert Action” this time. In the pop-up, call the method colorSliderChanged. Next to Arguments, select “None” from the menu. Click Connect. Now do the same with the other sliders, but rather than dragging to a new line, drag until the colorSliderChanged method becomes highlighted. You’ll connect to that method, rather than creating a new one.

We’ll use a slightly different way to connect the button’s action. Control-drag from the “Reset” button to a new line underneath the actions. The default option will connect an outlet, but we want an action. From the Connections popup menu, choose Action instead. Change the name to resetContent.

New Action Popup

New Action Popup

Note that the event is “Touch Up Inside”, the default for buttons. Arguments should again be None.

Connect the stepper control in the same way. Name should be fillAmountChanged; Arguments should be None. Note the default event, “Value Changed” for the segmented control. Arguments should be None.

Finally, we’ll have to connect the segmented control as well. Follow the same process as before. The name should be sectionChanged. Arguments should be Sender.

Writing the Code

Open RootViewController.m and scroll through the file. You’ll note a bunch of synthesized properties, ivars added to the dealloc method, and blank methods for all the actions you just connected. This saves us a lot of boilerplate code and lets us get right down to work. Put in a blank line in the colorSliderChanged method after the opening brace. This method will change the fill color of that gray rectangle based on the slider values, converting to an RGB-A color. Note though that this isn’t necessarily a recommended way to do it—it can be slow on older devices.

We’ll create four float variables to hold the values of the sliders, then create a UIColor instance based off the floats. Then we assign that as the background color of the blank view. The code for the method:

- (IBAction)colorSliderChanged {
	float redColor = self.redSlider.value;
	float greenColor = self.greenSlider.value;
	float blueColor = self.blueSlider.value;
	float alphaValue = self.alphaSlider.value;
	UIColor *newBackground = [UIColor colorWithRed:redColor green:greenColor blue:blueColor alpha:alphaValue];
	self.displayColor.backgroundColor = newBackground;
}

Next, let’s handle the case where the stepper’s value is changed. First we need to update the label, which we’ve already connected. We grab the stepper’s value, and create a string out of that. Then we assign the string to the label’s text property. Next, we’ll update the progress bar. We use the switch’s on property to determine if we should animate, and use the stepper’s value to determine where to fill to.

- (IBAction)fillAmountChanged {
	NSInteger amount = (NSInteger)self.amountStepper.value;
	// You need two percents to "escape" it and actually display a percent sign
	NSString *amountString = [NSString stringWithFormat:@"%d%%", amount];
	self.amountLabel.text = amountString;
	[self.progressIndicator setProgress:((float)amount / 100.0) animated:self.animatedSwitch.on];
}

The reset method is easy. We’re just assigning some properties.

- (IBAction)resetContent:(id)sender {
	self.amountStepper.value = 0.0;
	self.amountLabel.text = @"0%";
	[self.animatedSwitch setOn:YES animated:YES];
	[self.progressIndicator setProgress:0.0 animated:YES];
}

There’s one last bit that we have to handle—view swapping. Right now if you run the project you’ll just get a white rectangle on screen and you’ll not get any of our new code. A simple method like this will fix the issue:

- (IBAction)sectionChanged:(id)sender {
	UISegmentedControl *sectionControl = (UISegmentedControl *)sender;
	if (sectionControl.selectedSegmentIndex == 0)		// Colors section
		[self.sectionView addSubview:self.colorsView];
	else if (sectionControl.selectedSegmentIndex == 1)	// Progress section
		[self.sectionView addSubview:self.progressView];
}

In this method, we’re simply adding the correct view to our main placeholder view as necessary.

Now the code is complete. But if you run it, you’ll see an obvious problem—the first time you run, you’ll get a blank white rectangle. It’s only when you select a segment in the segmented control that you get a view. Let’s fix that by adding this one line to viewDidLoad:

[self.sectionView addSubview:self.colorsView];

We know that the first view to load will be the colors view, so automatically we’ll add that view. If you build and run now, you’ll get the fully working app.

UIKit From Here

As you’ve seen, using UIKit controls is rarely very difficult or involved. 85% of the time you’re just setting and getting properties on the controls. Some of them have a custom setter that allows you to animate the change. There are some other controls that we haven’t really covered, but they’re not difficult. We’ll get into putting a tab bar into our application (and its corresponding controller) next time. For now though, if you would like to experiment around, feel free. Also check out Apple’s documentation. You can download this version of the sample project here.

About these ads

The Jungle, Part 1: Basic UI Elements


Recently, we’ve been discussing concepts. This week, we’re going to take a break from that and start working on an app that will cover many of the high-level topics in the iOS SDK and allow you to build many interfaces with Interface Builder. We’ll start from a bare-bones project template and work our way up from there.

I’m running Xcode 4.1 on Mac OS X 10.7 Lion. Xcode 4 is available on the Mac App Store for both Snow Leopard and Lion. You can use Xcode 3, but some things (especially Interface Builder) will be different, so you may have to “translate” some of the screenshots. Nevertheless, the procedure in Xcode 3 is still similar, just in different places. Xcode 4 is the future, and despite the criticisms, does have some really nice features. And it includes the latest features, many of which make coding much easier. So fire up Xcode, and let’s get started.

Setting Up the Project

When you first start Xcode 4, you’ll be presented with the Welcome to Xcode window. As you create more projects, the list to the right will get populated with those projects. For now, read the texts if you wish, but click on “Create a new Xcode project” to get started.

Xcode 4 Welcome Window

Xcode 4.1 Welcome Window

A new window will appear, and a modal sheet will slide down. From the left column, under iOS, select “Application”. Then, in the top-right pane select “Window-Based Application” (it may be called “Empty Application”). Click Next.

For Product Name:, call it “SDK Demo”. You can call it something else, but you’ll have to make adjustments down the road. You can ignore the other options; we’ll talk about them in later posts. Click Next and find a place to save your project.

Xcode Main Project Window

Xcode Main Project Window

Xcode should now present the main project window, and your project has been set up. At this point you can click on the circular Run button in the top-left of the main Xcode window, which will launch your app in the iPhone Simulator.

All you’ll see at the moment is a blank white screen—that’s because we haven’t done anything yet. Let’s do that now.

The Root View Controller

All iOS applications should have a root view controller. There are books and tutorials online that tell you to do your whole interface in the app delegate (I’ll talk about this below)—which you can, but it is not recommended for anything but the simplest project. Sometime in the future, applications will be expected to have a root view controller—they will no longer be optional. It’s just a good idea. So let’s create one.

In Xcode, go to File > New > New File… (Command-N). On the left, select Cocoa Touch under iOS and select the second option, UIViewController subclass. Click Next. For Class, call it RootViewController. Make sure it’s a subclass of UIViewController, and that it comes with a XIB for the user interface. Click Next, and accept the default save location. Click Create.

New File View

New File Picker

New File Name

Name new file

In the File Navigator, click on AppDelegate.h. There is some existing code provided by the template; leave that there. Note that the provided code may differ across versions of Xcode, but the general meaning and end results are the same. Add the following lines to the existing code to set up the header file.

#import <UIKit/UIKit.h> 
@class RootViewController;    // Add this line
@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) RootViewController *rootViewController;    // Add this line too 
@end

Switch to the .m file, either by selecting the file in the File Navigator, or pressing Command-Control-↑. In this case, there’s a lot of comments and sample code that we aren’t going to use now. You can keep it if you wish, or replace the file with this code:

#import "AppDelegate.h"
#import "RootViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize rootViewController = _rootViewController;

- (void)dealloc {
	[_window release];
	[_rootViewController release];
    [super dealloc];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
	_rootViewController = [[RootViewController alloc] init];
	self.window.rootViewController = self.rootViewController;
    [self.window makeKeyAndVisible];
    return YES;
}

@end

The code of interest is in the application:DidFinishLaunchingWithOptions: method. Every app starts off as a window, and that’s what the first line does—it creates a window that is the size of the screen, and autoreleases it. That line, and the next, which sets the window’s background color to white, is provided by the template. The next two lines are what’s important here—you’re initializing the RootViewController instance, and assigning it to the window’s rootViewController property, which was new in iOS 4.0. In iOS 3.x and earlier, you couldn’t directly set the view controller; you had to set the view instead.

[_window addSubview:self.rootViewController.view];

The last two lines actually display the window, and return YES. If you return NO, the app will then be quit. The app setup is now done. Running the app, you won’t notice any visual difference, but the setup is indeed complete.

The App Delegate

Now is a good place to take a detour and talk about the app delegate. This class, created with every project, is the interface between the iOS system and your app. When the system has finished loading the app, the application:didFinishLaunchingWithOptions: method is called. In many cases, you can ignore the arguments to the method. Generally, here you perform some basic setup, including creating the main window and the root view controller as you saw above. In a game, you may also choose to initialize timers or graphics engines here, but make sure you don’t do something too time-intensive, or your app will appear to hang.

Other situations in which app delegate methods may be called include times when your app is being quit, in which you have a few seconds to do some last-minute processing or to save state, or when your app goes into or comes from the background. “The background” is only present in a multi-tasking environment (iOS 4.0 or higher on some iOS devices), when your app may be dismissed without being quit. In those cases, you may want to save state, or pause game timers, or close network connections.

Do note that in all these cases, you have limited time to do your processing, so you shouldn’t be calculating pi to 15 million digits here. Taking too long to do any processing will make your app appear to hang, and the system will try to avoid that.

A technical note: how does the system know which class the app delegate is? If you want to know, open up the Supporting Files group in the File Navigator. Select the main.m file. Inside, you’ll find this code:

UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

That fourth argument to the UIApplicationMain() function is the name of the app delegate class, passed in as an NSString. NSStringFromClass() takes a Class object (found by invoking the +class method on any class) and returns the string with its name. That’s how the system finds the app delegate.

Creating the Root View Controller

In this initial revision of our app, we’ll add in some basic functionality that shows off some basic UIKit elements. We will also be exploring Xcode 4′s new Interface Builder, included within Xcode’s main window.

Open up RootViewController.h and put in the following code.

#import <UIKit/UIKit.h>
@interface RootViewController : UIViewController {
	int buttonPressCount;
}

@property (nonatomic, retain) IBOutlet UILabel *buttonPressLabel;
@property (nonatomic, retain) IBOutlet UILabel *echoLabel;

- (IBAction)simpleButtonPressed:(id)sender;
- (IBAction)textFieldTextDidChange:(id)sender;
@end

You add two label outlets and two action methods. The IBOutlet and IBAction qualifiers don’t affect your code at all, but they are a “hint” to Interface Builder as connect points.

Interestingly, the action methods take one argument, of a generic type. This sender object is the object that triggered the event. You could have written the method without the argument, but then you would not know which object triggered the method. In some cases, you can determine what course of action you want to take based on the sender object (you can hook up one action method to multiple objects), or access some property of the sender. We will use the second reason. By doing so, we can avoid declaring two additional outlets, only to access their properties. As you gain more experience, you’ll get a feel for which properties you need to declare and hook up, and which you can obtain from method arguments, cleaning up your code.

Setting Up the Interface

Blank XIB

Blank window with Utilities

UIView Attributes

UIView Attributes

With your declarations done, switch over to RootViewController.xib. Open up the Utilities pane by clicking on the third button in the View group in the toolbar, or by pressing Command-Option-4. Click in the main view to select it and bring up the Attributes.

The white background is a bit garish, so click on the double arrows at the right end of the Background popup. If you click in the middle, you’ll get the standard color picker. If you click on the double arrows, you get a list of recent colors, and some options that you can’t get in the color picker.

Color Choices

Color choices

Select the “Group Table View Background Color”. The main view will take on a blue-and-white pinstripe appearance. That’s better.

The bottom part of the Utilities pane has four small icons. Select the third one to bring up a list of UI elements you can use. The search bar at the bottom allows you to filter the results. Drag out a button into the main view.

UI Elements Library

UI elements library

As you drag, you’ll see blue guidelines appear—those are Apple’s recommendations for spacing, keeping your UI elements away from the edges of the screen, where they may be hard to hit, and away from each other, to prevent the wrong element from being touched. A guideline will appear at the middle of the screen. Align the button with the top guideline and down the middle.

Blue Guidelines

Blue guidelines

Using the horizontal drag handles, make the button about 160 pixels wide (there will be an overlay telling you the size; don’t worry about the exact value), and bring it back to the center using the guides. Double click in the center of the button to bring up a text-editing field. Type in “Press Me!” or something similar. Click off the main view to deselect the button.

Finalized Button

Finalized button

Drag out a label and center it. Move it up toward the button until the guide appears underneath the button, and leave it there. Again, make it 128 pixels wide and center it. Using the Attributes inspector, set the following properties:

Label Attributes

Label attributes

  • Set Alignment to centered
  • Make the Text Color Black (or Default)
  • Make the Shadow color White
  • Make the Shadow Offset 0 Horizontal, 1 Vertical (the default is -1)
  • Clear out the text, so that the label isn’t displaying “Label”

Drag out a text field. Make it 160 pixels wide, centered horizontally. The vertical position doesn’t really matter too much. Keep the default attributes for now; feel free to play around with them later. Now hold down the Option key. Click and drag the original label to a position underneath the text field. You’ll make a copy of the original label, including all the attributes.

Making Connections

Connections Inspector

Connections inspector

Now that we have the UI elements laid out, we’re going to connect it to our outlets and actions. There are many ways to do that; for now, because our inspectors are open, we’re going to use those. We’ll cover other methods in future posts.

Open the Connections inspector (the sixth option in the Utilities sidebar) or press Command-Option-6. Select the first object along the bar to the left—it looks like a light orange cube and when you mouse over it, the pop-up says “File’s Owner.” This is a proxy object (I’ll talk about that in a future post when we explore Xcode) that references the owner of the XIB—in this case, RootViewController.h. Selecting that, some outlets show up in the connections inspector. First we’ll wire up the outlets.

Click in the little circle to the right of the buttonPressLabelentry. Drag the cursor over to the label below the button, and let go. As you drag, you’ll see a blue line appear, starting from that little circle.

Visual connection—blue line

Blue line shows connection

The connection will show up in the connection inspector. Repeat the process for the echoLabel, connecting it to the label beneath the text field.

Shows Connection

Visual connection

Under the Received Actions section, drag from simpleButtonProssed: to the button in your interface. This time, you’re connecting an action; when you release, you’ll get a list of eventsthat the button can respond to. You can have different methods trigger based on what type of touch input the button receives.

UIControl Events List

Events list

Select “Touch Up Inside”, which means that the action will only be triggered if a finger touches down and up—what you would naturally do—all within the borders of the button. This is the most commonly-used choice for buttons. Connect textFieldTextDidChange: to the text field, but this time select Editing Changed for the event. This will have the method be called every time the text inside the text field changes—it’ll get called every time the user types a letter or hits the backspace.

Having wired up the entire interface, return to the code—open RootViewController.m.

Coding the Actions

You can replace the existing code with the following:

#import "RootViewController.h"

@implementation RootViewController
@synthesize buttonPressLabel = _buttonPressLabel;
@synthesize echoLabel = _echoLabel;

#pragma mark - Memory Management
- (void)dealloc {
	[_buttonPressLabel release];
	[_echoLabel release];
}

#pragma mark - View lifecycle
- (void)viewDidLoad {
	buttonPressCount = 0;
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view to relieve memory usage
	self.buttonPressLabel = nil;
	self.echoLabel = nil;
}

#pragma mark - Action Methods
- (IBAction)simpleButtonPressed:(id)sender {
	buttonPressCount++;
	NSString *pluralHandlerString = (buttonPressCount == 1) ? @"time" : @"times";
	NSString *displayString = [[NSString alloc] initWithFormat:@"Button pressed %d %@", buttonPressCount, pluralHandlerString];
	self.buttonPressLabel.text = displayString;
	[displayString release];
}

- (IBAction)textFieldTextDidChange:(id)sender {
	UITextField *textField = (UITextField *)sender;
	NSString *textFieldText = textField.text;
	self.echoLabel.text = textFieldText;
}
@end

The beginning isn’t of much interest—we’re importing files, synthesizing properties, and dealing with memory management. The viewDidLoad method is where you usually do some setup—initialize instance variables, or set some properties on UI elements. viewDidUnload is where you release your retained subviews (and any other properties that you initialize or create in viewDidLoad) to free up that memory, when your view disappears. The action methods warrant some discussion.

The first action method simply keeps track of the number of times the button is pressed and displays that. It uses the buttonPressCount instance variable, incrementing it each time the button is pressed. There is initially a string that uses the conditional operator to determine whether or not to use the plural form of “time.” Then that string is plugged into the final string, along with the run count, to form the displayString. This is then assigned to the text property of the corresponding label, which causes the text to be displayed. The string is released according to memory management rules.

The second action method uses the sender argument, first casting it to a UITextField. It grabs the current text value from the text field—this method is called every time the text changes, so the text value will be different every time, and then this text is assigned to the label’s text property as before. The text field manages its text property, so you don’t have to release it.

The Finished Product

Finished Project Version 1 Screenshot

Finished product

At this point, the first version of our demo app is done. Hit Run in the toolbar, and it should bring up the iPhone Simulator. Play around with the button presses and the text field. If you have build issues or warnings, post them in the comments or email me, and I’ll get back to you as soon as possible.

One More Thing

Once the keyboard shows up, you can’t get rid of it without quitting the app. Let’s fix that.

Add an action method called dismissKeyboard: to the header, which takes one sender. The sender will be the text field. Connect the method in IB to the text field’s Did End on Exit event. The implementation of the method should be like this:

- (IBAction)dismissKeyboard:(id)sender { 
    [(UITextField *)sender resignFirstResponder];
}

Now, if you hit the Return key on the keypad, the keyboard will slide away. The important part is the resignFirstResponder method call, which in the case of a text field will also dismiss the keyboard.

And that’s all there is to it.

Project Download

You can download the source for this project here.

  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 224 other followers

  • Back to the Past

    April 2014
    S M T W T F S
    « Aug    
     12345
    6789101112
    13141516171819
    20212223242526
    27282930  
  • Time Machine

  • You count!

    • 589,778 views
  • Worldwide Stats

    free counters
Follow

Get every new post delivered to your Inbox.

Join 224 other followers

%d bloggers like this: