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.
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 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.
Creating a 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:
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!
Custom View and Custom Input 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!
Integrating into the Right View
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!
Show Me the Code!
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!
转载自:www.raywenderlich.com