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.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Custom View and Custom Input Controller

You want the app to work so that when you tap the image, it brings up the input controller you just made. So as discussed above, you’ll have to make a subclass of UIImageView so you can return your input view to display.

While you’re at it, you’ll have to make a few other tweaks to the image view. By default, UIImageViews don’t accept input and don’t become responders to input events, so you’ll have to enable support for that.
So let’s get started.

Go to File\New\File… and choose the iOS\Cocoa Touch\Objective-C class template. Name the class WeaponSelectorImageView, make it a subclass of UIImageView, click Next and then Create.

Update WeaponSelectorImageView.h follows:

#import <UIKit/UIKit.h>
#import "WeaponInputViewController.h" //Has Input delegate as well.
@class Weapon;

@protocol WeaponSelectorDelegate <NSObject>
-(void)selectedWeapon:(Weapon *)weapon;
@end

@interface WeaponSelectorImageView : UIImageView <WeaponInputDelegate>

@property (nonatomic, strong) Weapon *weapon;
@property (nonatomic, strong) WeaponInputViewController *weaponInputController;
@property (nonatomic, strong) IBOutlet id<WeaponSelectorDelegate> delegate;

@end 

Let’s explain this a bit. First, you create a delegate so that you can notify a listener when the Weapon is changed. You’re going to be receiving updates about what WeaponType the user wants to use, but you need to be able to notify object of what Weapon object you want to use.

Next, you declare this class as a WeaponInputControllerDelegate since you want to know when the user selects a button in the input view. You store the current selected weapon, and a pointer to your own WeaponSelectorDelegate to notify when it changes. Note you mark your delegate as an IBOutlet so you can assign it from Interface Builder later.

Switch over to WeaponSelectorImageView.m and add in the code below. You’re going to split it into sections so you can explain it part by part. Start by overriding the weapon property’s setter so that you can set the image appropriately based on the weapon choice:

#import "WeaponSelectorImageView.h"
#import "Weapon.h"

@implementation WeaponSelectorImageView

#pragma mark - Overridden setters
-(void)setWeapon:(Weapon *)weapon
{
    //First make sure you're actually changing the weapon
    if (_weapon != weapon) {
        _weapon = weapon;
        
        //Update your image to use the weapon's image.
        self.image = [_weapon weaponImage];
    }
}

This will work both when the .weapon is set from RightViewController.m’s refreshUI method and when the weapon is set from the delegate method you’ll implement in a minute.

Next, you override several superclass methods from UIResponder to allow for showing a custom input view:

#pragma mark - Superclass overrides
-(BOOL)canBecomeFirstResponder
{
    //Says that this view can show an input view.
    return YES;
}

-(UIView *)inputView
{
    //Make sure the weaponInputController exists, and if not, create it.
    if (_weaponInputController == nil) {
        _weaponInputController = [[WeaponInputViewController alloc] initWithNibName:nil bundle:nil];
        _weaponInputController.delegate = self;
    }
    
    //Return the WeaponInputController's view as the input view.
    return _weaponInputController.view;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //Show the input view as soon as the imageView is touched, 
	//if it is not already showing.
    if (![self isFirstResponder]) {
        [self becomeFirstResponder];
    }
}

The inputView method is the most important – this tells the system what view should be used as a custom input view when the view becomes the firstResponder. You’re basically saying, “Instead of showing a keyboard when I become a first responder, use this custom view I made.”

Most inputViews that are provided by Apple are just keyboards, but you can return any kind of view you want by overriding this method in any direct or indirect UIResponder subclasses. More details on this technique can be found in Apple’s developer documentation on Custom Views for Data Input.

Finally, implement the WeaponInputDelegate methods to handle Weapon Type selection and the close command:

#pragma mark - WeaponInputDelegate methods
-(void)closeTapped
{
    //Dismiss the input view.
    [self resignFirstResponder];
}

-(void)selectedWeaponType:(WeaponType)weaponType
{
    //Set the instance variable.
    [self setWeapon:[Weapon newWeaponOfType:weaponType]];
    
    //Dismiss the input view.
    [self resignFirstResponder];
    
    //Notify the delegate of the change if it exists.
    if (_delegate != nil) {
        [_delegate selectedWeapon:_weapon];
    }
}

@end

Note that whenever you call resignFirstResponder, this automatically dismisses the inputView the same way you dismiss the keyboard when a UITextView or UITextField resigns the first responder.

Phew! You’ve done a lot here. Don’t worry, you’re almost done – all you have to do now is replace the plain vanilla UIImageView with your custom view, and handle the weaponChanged callback.

Integrating into the RightViewController

Open MainStoryboard_iPad.storyboard, and in the Right View Controller select the UIImageView currently named weaponImageView next to “Preferred way to kill”. Go to the Identity Inspector (3rd tab) in the inspector and change the class from UIImageView to “WeaponSelectorImageView”.

Also, in the Attributes Inspector (4th tab) make sure you check the box for “User Interaction Enabled” – if this box is not checked, touchesBegan:withEvent: will never be called and your custom input view will not show up.

Finally, control drag from the WeaponSelectorImageView to the RightViewController object, and set the RightViewController as the weapon selector’s delegate.

Now go to RightViewController.h so you can update a few items. First, add an import to the import section:

#import "WeaponSelectorImageView.h" //Also has WeaponSelectorDelegate protocol

Then update the class declaration to show that the class conforms to the WeaponSelectorDelegate protocol:

@interface RightViewController : UIViewController <MonsterSelectionDelegate, UISplitViewControllerDelegate, ColorPickerDelegate, WeaponSelectorDelegate>

Then update the class of the weaponImageView from UIImageView to your custom subclass, WeaponSelectorImageView:

@property (nonatomic, weak) IBOutlet WeaponSelectorImageView *weaponImageView;

In RightViewController.m, start by updating the refreshUI method:

-(void)refreshUI
{
    _nameLabel.text = _monster.name;
    _iconImageView.image = [UIImage imageNamed:_monster.iconName];
    _descriptionLabel.text = _monster.description;
    
    //Setting the weapon on your custom imageview will automatically set
    //the image.
    _weaponImageView.weapon = _monster.weapon;
}

And finally, add the WeaponSelectorDelegate method to update the Monster’s weapon:

#pragma mark - WeaponSelectorDelegate method
-(void)selectedWeapon:(Weapon *)weapon
{
    //Check to make sure the weapon is changing.
    if (_monster.weapon != weapon) {
        //Update the weapon
        _monster.weapon = weapon; 
    }
}

By adding this method, when you change the monster’s weapon, you update the object so that it remembers your choice for the life of the application.

Re-run your application, and if all works well, you should be able to tap the icon to bring up a custom input view:

Finished Custom Input View

Note that a custom input view will always want to be the same width as the keyboard. In fact, the Apple documentation warns: “Although the height of these views can be what you’d like, they should be the same width as the system keyboard.” In almost all cases, this is as wide as the entire screen rather than just the view controller launching the input view.

Remember to take this into account when using AutoLayout to build your custom input views – otherwise you might wind up with a portion of your input view that isn’t visible when the iPad is rotated in a certain direction.

Contributors

Over 300 content creators. Join our team.