原文地址: http://www.raywenderlich.com/1063/ipad-for-iphone-developers-101-custom-input-view-tutorial
This is the third part of a three part series to help get iPhone Developers up-to-speed with iPad development by focusing on three of the most interesting new classes (at least to me): UISplitView, UIPopoverController, and Custom Input Views.
In the first part of the series, we 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, we added a popover view to allow changing of colors.
In this part, we’re going to add a custom input view (or “keyboard”) to allow selecting your favorite way to kill these monsters.
We’ll start out with where we left off the project last time, so grab a copy if you don’t have it already.
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 assign a custom view to the “inputView” property.
However, if you’re using something else (like we are in this case with our 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 we have two classes to write: a custom UIImageView, and our view controller for our custom input view. Let’s start with our custom input view.
Go to “File/New File…”, choose “UIViewController subclass”, make sure “Targeted for iPad” and “With XIB for user interface” are checked but “UITableViewController subclass” is NOT checked, and click Next. Name the class “WeaponInputViewController”, and click Finish.
Open up WeaponInputViewController.xib. By default it makes the view the size of the iPad screen, but we want something much smaller in height. Interface Builder won’t let us resize the default view, so delete the default view and add a new one, with width 768 and height 110. Also don’t forget to control-drag from “File’s Owner” to View to reconnect the default view for the owner.
Update:: Jerry Beers from the comments section below pointed out that instead of deleting/re-adding, you can just turn off the simulated UI elements, and then you can resize at will. Thanks Jerry!
Create 5 70×70 buttons along the left side of the view as follows:
Then set tye type of each Button from “Rounded Rect” to “Custom” and set the image for each button to a weapon, as shown:
Now let’s fill in the class definition. Replace WeaponInputViewController.h with the following:
#import <UIKit/UIKit.h> #import "Monster.h" @protocol WeaponInputControllerDelegate - (void)weaponTapped:(Weapon)weapon; - (void)doneTapped; @end @interface WeaponInputViewController : UIViewController { id<WeaponInputControllerDelegate> _delegate; } @property (nonatomic, assign) id<WeaponInputControllerDelegate> delegate; - (IBAction)blowgunTapped:(id)sender; - (IBAction)fireTapped:(id)sender; - (IBAction)ninjastarTapped:(id)sender; - (IBAction)smokeTapped:(id)sender; - (IBAction)swordTapped:(id)sender; - (IBAction)doneTapped:(id)sender; @end |
This should all look familiar – we just set up a protocol so we can notify a listener when a weapon is selected (or the done button), and set up a bunch of action methods.
So let’s hook up those action methods now. Go back to WeaponInputViewController.xib and control-drag from each button to “File’s Owner and hook the button up to the appropriate action method.
Then wrap it up by replacing WeaponInputViewController.m with the following:
#import "WeaponInputViewController.h" @implementation WeaponInputViewController @synthesize delegate = _delegate; - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { return YES; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void)viewDidUnload { [super viewDidUnload]; } - (void)dealloc { self.delegate = nil; [super dealloc]; } - (IBAction)blowgunTapped:(id)sender { if (_delegate != nil) { [_delegate weaponTapped:Blowgun]; } } - (IBAction)fireTapped:(id)sender { if (_delegate != nil) { [_delegate weaponTapped:Fire]; } } - (IBAction)ninjastarTapped:(id)sender { if (_delegate != nil) { [_delegate weaponTapped:NinjaStar]; } } - (IBAction)smokeTapped:(id)sender { if (_delegate != nil) { [_delegate weaponTapped:Smoke]; } } - (IBAction)swordTapped:(id)sender { if (_delegate != nil) { [_delegate weaponTapped:Sword]; } } - (IBAction)doneTapped:(id)sender { if (_delegate != nil) { [_delegate doneTapped]; } } @end |
As you can see, nothing too exciting or fancy here. All we do is notify our delegate when the various buttons get tapped. In fact, you could say that this view has no knowledge that it’s even being used as a custom input view controller!
We want it to work so that when you tap the image, it brings up the input controller we just made. So like we discussed above, we’ll have to make a subclass of UIImageView so we can return our input view to display.
While we’re at it, we’ll have to make a few oher tweaks to the image view. By default, UIImageViews don’t accept input and don’t become responders to input events, so we’ll have to enable support for that.
So let’s get started. Go to “File/New File…”, choose “Objective-C subclass”, make sure “Subclass of NSObject” is selected, and click Next. Name the class “WeaponSelector”, and click Finish.
#import <Foundation/Foundation.h> #import "WeaponInputViewController.h" @protocol WeaponSelectorDelegate - (void)weaponChanged:(Weapon)weapon; @end @interface WeaponSelector : UIImageView <WeaponInputControllerDelegate> { WeaponInputViewController *_weaponInputView; Weapon _weapon; id<WeaponSelectorDelegate> _delegate; } @property (nonatomic, retain) WeaponInputViewController *weaponInputView; @property (nonatomic, assign) Weapon weapon; @property (nonatomic, assign) IBOutlet id<WeaponSelectorDelegate> delegate; - (UIView *)inputView; @end |
Ok let’s explain this a bit. First, we create a delegate so that we can notify a listener when the weapon is changed. Note we could have possibly re-used the WeaponInputControllerDelegate for this, but it isn’t as clean of a design because it muddles the dependencies.
Next, we declare this class as a WeaponInputControllerDelegate since we want to know when the user selects a button in the input view. We store the current selected weapon, and a pointer to the delegate to notify when it changes. Note we mark the delegate as an IBOutlet so we can assign it from Interface Builder later.
Finally, we declare a prototype for the override for the getter for the inputView that this class will contain.
Switch over to WeaponSelector.m and add in the code below. We’re going to split it into sections so we can explain it part by part.
#import "WeaponSelector.h" @implementation WeaponSelector @synthesize weaponInputView = _weaponInputView; @synthesize weapon = _weapon; @synthesize delegate = _delegate; |
We start out by adding our synthesize statement like normal.
- (void)setWeapon:(Weapon)weapon { _weapon = _weapon; switch (weapon) { case Blowgun: self.image = [UIImage imageNamed:@"blowgun.jpg"]; break; case Fire: self.image = [UIImage imageNamed:@"fire.jpg"]; break; case NinjaStar: self.image = [UIImage imageNamed:@"ninjastar.jpg"]; break; case Smoke: self.image = [UIImage imageNamed:@"smoke.jpg"]; break; case Sword: self.image = [UIImage imageNamed:@"sword.jpg"]; break; default: break; } } |
We then override the weapon property’s setter so that we can set the image appropriately based on the weapon choice.
- (UIView *)inputView { if (_weaponInputView == nil) { self.weaponInputView = [[[WeaponInputViewController alloc] initWithNibName:@"WeaponInputViewController" bundle:[NSBundle mainBundle]] autorelease]; _weaponInputView.delegate = self; } return _weaponInputView.view; } |
Here’s the most important part – we override inputView to return our own custom view. We just allocate the view controller if we haven’t already, set ourselves as the delegate, and return the view.
- (BOOL)canBecomeFirstResponder { return YES; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self becomeFirstResponder]; } |
We override the method to say that yes we CAN become the first responder to input events, and upon a touch we set ourselves as the first responder. This has the effect of bringing up our input view.
- (void)weaponTapped:(Weapon)weapon { self.weapon = weapon; [self resignFirstResponder]; [_delegate weaponChanged:weapon]; } - (void)doneTapped { [self resignFirstResponder]; } |
Here we implement the WeaponInputViewControllerDelegate methods. In the case where a weapon is tapped, we set our weapon to the new weapon (through our property, hence calling our setter override and setting the image as well). We then resign as the first responder, which has the effect of removing our custom input view. Finally we notify our delegate that the weapon has changed so it can take appropriate action.
If done is tapped, we just call resignFirstResponder to remove the input view.
- (void) dealloc { self.weaponInputView = nil; [super dealloc]; } @end |
At the end we add our cleanup code as usual.
Phew! We’ve done a lot here. Don’t worry we’re almost done – all we have to do now is replace the plain vanilla input view with our custom view, and handle the weaponChanged callback!
Double click RightViewController.xib, and select the UIImage next to “Preferred way to kill”. Go to the fourth tab in the inspector and change the class from UIImageView to “WeaponSelector”. Also, in the first tab in the inspector check “User Interaction Enabled.”
Finally, control drag from WeaponSelector to “File’s Owner”, and set it as the delegate.
Now make the following changes to RightViewController.h:
// In import section #import "WeaponSelector.h" // Modify class declaration @interface RightViewController : UIViewController <MonsterSelectionDelegate, UISplitViewControllerDelegate, ColorPickerDelegate, WeaponSelectorDelegate> { // Modify type of weaponView variable WeaponSelector *_weaponView; // Modify type of weaponView property @property (nonatomic, retain) IBOutlet WeaponSelector *weaponView; |
And make the following changes to RightViewController.m:
// Modify refresh as the following - (void)refresh { _nameLabel.text = _monster.name; _iconView.image = [UIImage imageNamed:_monster.iconName]; _descrLabel.text = _monster.descr; _weaponView.weapon = _monster.preferredWayToKill; } // Add the following new method - (void)weaponChanged:(Weapon)weapon { if (_popover != nil) { [_popover dismissPopoverAnimated:YES]; } _monster.preferredWayToKill = weapon; } |
Compile and run, and if all works well, you shoudl be able to tap the icon to bring up a custom image view!
Here’s a copy of all of the code we’ve developed so far.
That’s all for the iPad for iPhone Developers 101 series for now. However soon I will write an article about how to port iPhone apps to the iPad!