iPad for iPhone Developers 101 in iOS 6: Custom Input View Tutorial
This is the third part and final part of a three-part series to help get iPhone Developers up-to-speed with iPad development by focusing on three of the most useful classes: UISplitView, UIPopoverController, and Custom Input Views. By Ellen Shapiro.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iPad for iPhone Developers 101 in iOS 6: Custom Input View Tutorial
20 mins
Update 3/7/2013: Fully updated for iOS 6 (original post by Ray Wenderlich, update by Ellen Shapiro).
This is the third part and final part of a three-part series to help get iPhone Developers up-to-speed with iPad development by focusing on three of the most useful classes: UISplitView, UIPopoverController, and Custom Input Views.
In the first part of the series, you made an app with a split view that displays a list of monsters on the left side, details on the selected monster on the right side.
In the second part of the series, you added a popover view to allow changing the color of the label displaying the monster’s name.
In this part, you’re going to add a custom input view (or “keyboard”) to allow selecting a new weapon for each monster.
You’ll start out with where you left off the project after Part 2, so grab a copy if you don’t have it already.
Before You Get Started: A Quick Refactor
In this tutorial, you’ll need to use the Monster object’s Weapon property for a lot more than it was originally intended do. In fact, you’re going to be using it for so much that it makes more sense to break it out into its own separate object.
Never be afraid to refactor (ie, rewrite your code), especially when something that was originally much smaller mutates into something much larger – it can sometimes be a little bit of a pain up front, but it makes your code far easier to read, understand, and maintain.
To get started, go to File\New\File and choose the iOS\CocoaTouch\Objective-C class template. Name the class Weapon, make it a subclass of NSObject, click Next, and then Create.
First, you’re going to want to move a couple of things from the Monster files over to the Weapon files. You’ll move the enum that’s currently named Weapon over to Weapon.h and rename it to WeaponType in order to prevent compiler errors. You’ll also create an assign property to hold the Weapon object’s WeaponType.
Then you’ll move the weaponImage method declaration to Weapon.h and the implementation to Weapon.m, and update it to use the WeaponType property instead of the old Weapon property when it was part of Monster.h. Finally, you’ll add a simple factory method to create a new weapon.
To do all this, replace Weapon.h with this:
#import <Foundation/Foundation.h>
typedef enum {
Blowgun = 0,
NinjaStar,
Fire,
Sword,
Smoke,
} WeaponType;
@interface Weapon : NSObject
@property (nonatomic, assign) WeaponType weaponType;
//Factory method to make a new weapon object with a particular type.
+(Weapon *)newWeaponOfType:(WeaponType)weaponType;
//Convenience instance method to get the UIImage representing the weapon.
-(UIImage *)weaponImage;
@end
Replace Weapon.m with this:
#import "Weapon.h"
@implementation Weapon
+(Weapon *)newWeaponOfType:(WeaponType)weaponType
{
Weapon *weapon = [[Weapon alloc] init];
weapon.weaponType = weaponType;
return weapon;
}
-(UIImage *)weaponImage
{
switch (self.weaponType) {
case Blowgun:
return [UIImage imageNamed:@"blowgun.png"];
break;
case Fire:
return [UIImage imageNamed:@"fire.png"];
break;
case NinjaStar:
return [UIImage imageNamed:@"ninjastar.png"];
break;
case Smoke:
return [UIImage imageNamed:@"smoke.png"];
break;
case Sword:
return [UIImage imageNamed:@"sword.png"];
default:
//Anything not named in the enum.
return nil;
break;
}
}
@end
Now update Monster.h with this, to take out the methods you’ve now moved to the Weapon class:
#import <Foundation/Foundation.h>
@class Weapon;
@interface Monster : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *description;
@property (nonatomic, strong) NSString *iconName;
@property (nonatomic, strong) Weapon *weapon;
//Factory class method to create new monsters
+(Monster *)newMonsterWithName:(NSString *)name description:(NSString *)description
iconName:(NSString *)iconName weapon:(Weapon *)weapon;
@end
Update Monster.m as well, removing the implementations:
#import "Monster.h"
#import "Weapon.h"
@implementation Monster
+(Monster *)newMonsterWithName:(NSString *)name description:(NSString *)description
iconName:(NSString *)iconName weapon:(Weapon *)weapon
{
Monster *monster = [[Monster alloc] init];
monster.name = name;
monster.description = description;
monster.iconName = iconName;
monster.weapon = weapon;
return monster;
}
@end
Add this import to the top of LeftViewController.m:
#import "Weapon.h"
And then update the portion of initWithCoder: adding Monster objects to the array to the following so you can take advantage of the new Weapon object:
//Create monster objects then add them to the array.
[_monsters addObject:[Monster newMonsterWithName:@"Cat-Bot" description:@"MEE-OW"
iconName:@"meetcatbot.png" weapon:[Weapon newWeaponOfType:Sword]]];
[_monsters addObject:[Monster newMonsterWithName:@"Dog-Bot" description:@"BOW-WOW"
iconName:@"meetdogbot.png" weapon:[Weapon newWeaponOfType:Blowgun]]];
[_monsters addObject:[Monster newMonsterWithName:@"Explode-Bot" description:@"Tick, tick, BOOM!"
iconName:@"meetexplodebot.png" weapon:[Weapon newWeaponOfType:Smoke]]];
[_monsters addObject:[Monster newMonsterWithName:@"Fire-Bot" description:@"Will Make You Steamed"
iconName:@"meetfirebot.png" weapon:[Weapon newWeaponOfType:NinjaStar]]];
[_monsters addObject:[Monster newMonsterWithName:@"Ice-Bot" description:@"Has A Chilling Effect"
iconName:@"meeticebot.png" weapon:[Weapon newWeaponOfType:Fire]]];
[_monsters addObject:[Monster newMonsterWithName:@"Mini-Tomato-Bot" description:@"Extremely Handsome"
iconName:@"meetminitomatobot.png" weapon:[Weapon newWeaponOfType:NinjaStar]]];
Finally, in RightViewController.m, add an import to the top of the file:
#import "Weapon.h"
And update how you’re setting the WeaponImageView’s image.
_weaponImageView.image = [_monster.weapon weaponImage];
Hit run – your application should look exactly as it did before, just with some differences under the hood that will make it easier to implement your Custom Input View for selecting a new weapon for your monster.
Custom Input View Overview
To display a custom input view, what you need to depends if you’re using a UITextView or UITextField, or something else.
If you’re using a UITextView or UITextField, you’re in luck. All you need to do is return a custom view for the “inputView” property.
However, if you’re using something else (like you are in this case with your UIImageView), you’ll need to make a custom subclass of the view you’re using so that you can override the inputView getter and return your own custom view.
So you have two more classes to write: a custom UIImageView, and your view controller for your custom input view. Let’s start with your custom input view controller.
Creating a Custom Input View Controller
Go to File\New\File… and choose the iOS\CocoaTouch\Objective-C class template. Name the class WeaponInputViewController and make it a subclass of UIViewController. Be sure to check both Targeted for iPad and With XIB for user interface. Since this view is not part of the navigation hierarchy, it’s going to be responsible for its own .xib instead of the Storyboard being responsible for it. Click Next and then Create.
Open WeaponInputViewController.xib. By default it makes the view the size of the iPad screen, but you want something much smaller in height. Under Simulated Metrics in the Attributes Inspector (4th tab), set the Size to “None” to allow you to resize the view, and set the Status Bar to “None” to remove the simulated status bar.
Now, either by dragging the view to resize or by using the Size Inspector (5th tab), set the main view’s width to 768 and height to 110.
Create 5 70×70 buttons with their titles removed along the left side of the view as follows, and a “Close” button at the end:
For auto-layout purposes, you should pin the leftmost button to the top and left of the superview, then pin the top alignments of each subsequent large button so they’re all the same distance from the top of the superview. You should also make sure to pin the widths and heights of each of the square buttons. Then, pin the vertical centers of the large buttons to the vertical center of the close button, so that the close button is in the middle of those other buttons.
Once you’re done with AutoLayout, change the background color of the main view to Light Gray Color to make the background stand out a bit. Then change the type of each Button from “Rounded Rect” to “Custom” and set the image for each button to the image of one of the weapons, as shown:
Now let’s fill in the class definition. Replace WeaponInputViewController.h with the following:
#import <UIKit/UIKit.h>
#import "Weapon.h" //Provides access to the WeaponType enum.
@protocol WeaponInputDelegate <NSObject>
@required
-(void)selectedWeaponType:(WeaponType)weaponType;
-(void)closeTapped;
@end
@interface WeaponInputViewController : UIViewController
@property (nonatomic, weak) IBOutlet UIButton *blowgunButton;
@property (nonatomic, weak) IBOutlet UIButton *fireButton;
@property (nonatomic, weak) IBOutlet UIButton *ninjaStarButton;
@property (nonatomic, weak) IBOutlet UIButton *smokeButton;
@property (nonatomic, weak) IBOutlet UIButton *swordButton;
@property (nonatomic, weak) id<WeaponInputDelegate> delegate;
-(IBAction)weaponButtonTapped:(UIButton *)sender;
-(IBAction)closeTapped;
@end
This should all look familiar – you set up a protocol so you can notify a listener when a weapon type is selected (or the close button), setup IBOutlet properties for each of the buttons, an IBAction which will work with any of the weapon buttons (and which takes a sender parameter so it can tell which UIButton was tapped), and then an IBAction which only works with the close button.
So let’s hook up those action methods now. Go back to WeaponInputViewController.xib and control click on “File’s Owner” to open up the list of outlets and actions. First, drag from each of the IBOutlets for the weapon buttons to the actual buttons themselves to hook these outlets up.
Then, control-drag from each weapon button to “File’s Owner” and hook the button up to the weaponButtonTapped method. Control-drag from the Close button to the closeTapped method as well. When you’re done, you should have five buttons hooked up to weaponButtonTapped: and one to closeTapped like so:
Then wrap it up by implementing the IBActions you’ve declared in WeaponInputViewController.m with the following, just before @end:
#pragma mark - IBActions
-(IBAction)closeTapped
{
//Notify the delegate if it exists.
if (_delegate != nil) {
[_delegate closeTapped];
}
}
-(IBAction)weaponButtonTapped:(UIButton *)sender
{
//Create a variable to hold the selected weapon type.
WeaponType selectedWeaponType;
//Set the selected weapon based on the button that was pressed.
if (sender == _blowgunButton) {
selectedWeaponType = Blowgun;
} else if (sender == _fireButton) {
selectedWeaponType = Fire;
} else if (sender == _ninjaStarButton) {
selectedWeaponType = NinjaStar;
} else if (sender == _smokeButton) {
selectedWeaponType = Smoke;
} else if (sender == _swordButton) {
selectedWeaponType = Sword;
} else {
NSLog(@"Oops! Unhandled button click.");
}
//Notify the delegate of the selection, if it exists.
if (_delegate != nil) {
[_delegate selectedWeaponType:selectedWeaponType];
}
}
As you can see, nothing too exciting or fancy here. All you do is notify your delegate when the various buttons get tapped. This ViewController has no knowledge that its view is actually being used as a custom input controller – you have to tell it to be one.