First App:Bull's Eyes
From RayWenderlich's tutorial: The iOS Apprentice: Getting Started
Alert View
1. View controllers
These three files together — the .xib, .h and .m — form the implementation of a view controller. The job of a view controller is to manage a single screen from your app. Simply put, the xib file contains the design of the view controller’s user interface, while the .h and .m files contain its functionality.
One of the design principles of iOS is that each screen in your app gets its own view controller.
2. Making connections
That is how you make buttons and other controls do things: you define an action in the view controller’s .h file and then make the connection in Interface Builder. From now on, whenever the button is tapped, the showAlert action will be performed.
File’s Owner
A xib, the user interface of a view controller, is owned by that view controller. File’s Owner, therefore, refers to the view
controller object.
By dragging from the button to File’s Owner, you tell Interface Builder that you want to connect the button to an action from the view controller.
3. Acting on the button
- (IBAction)showAlert { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hello, World" message:@"This is my first app!" delegate:nil cancelButtonTitle:@"Awesome" otherButtonTitles:nil]; [alertView show]; }
How does an app work?
When the user taps the Hit Me button in our app, for example, that UIButton object sends a message to our view
controller that in turn may message more objects.
On iOS, apps are event-driven, which means that the objects listen for certain events to occur and then process them.
Your part in this scheme is that you write the source code that will be performed when your objects receive the messages for such events.
Frameworks
iOS offers a lot of building blocks in the form of frameworks or “kits”. The UIKit framework provides the user interface controls such as buttons, labels and navigation bars. It manages the view controllers and generally takes care of anything else that deals with your app’s user interface.
Instead, you can build your app on top of the system-provided frameworks and take advantage of all the work the Apple engineers
have already done for you.
Any object you see whose name starts with UI, such as UIButton, comes from UIKit. When you’re writing iOS apps, UIKit is the framework you’ll spend most of your time with but there are others as well. Foundation is the framework that provides many of the basic building blocks for writing Objective-C programs (its prefix is NS, as in NSString).
Examples of other frameworks are Core Graphics, for drawing basic shapes such as lines, rectangles, gradients and images on the screen; Core Audio for playing sounds; CFNetwork for doing networking; and many others. The complete set of frameworks for iOS is known collectively as Cocoa Touch.
Portrait vs landscape (orientation)
the iPhone is tilted on its side and the screen is wider but less tall. This is called landscape mode.
So what is a point? On the iPhone 3GS and earlier models, as well as the corresponding iPod touch models and the iPad 1 and 2, one point corresponds to one pixel, which is the smallest element that a screen is made up of. Images are produced by changing the color values of these pixels.
However, on the iPhone 4 and new iPad with their high-resolution Retina display, one point actually corresponds to two pixels horizontally and vertically, so four pixels in total.
Converting the app to landscape pp44
To convert our app from portrait into landscape, we have to do three things:
1. Make the view from BullsEyeViewController.xib landscape instead of portrait.
2. Change one line of code in BullsEyeViewController.m that will allow the view
controller to autorotate to landscape mode.
3. Change the “Supported Device Orientations” settings of our app.
.2 » Open BullsEyeViewController.m and find the bit of code that says:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)ê interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); }
This was automatically put into the view controller by Xcode when it created the project from the Single View Application template. (If you’re using Xcode 4.3, it does not include the green text.) This particular piece of functionality is queried by UIKit when the user rotates his iPhone. UIKit essentially asks the view controller: “Do you want to rotate to this new orientation?”
If your view controller says “yes”, then the screen will rotate with the device from portrait to landscape or the other way around.
» Our app should allow landscape only, so change the code to the following:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)ê interfaceOrientation { return UIInterfaceOrientationIsLandscape(interfaceOrientation); }
When the app starts up, UIKit asks the view controller, “Do you want to run in portrait
orientation?” because that is probably how you’re holding the iPhone. Thanks to our
modification, BullsEyeViewController says: “Nope.” Then UIKit asks again, this time
for landscape orientation. Our view controller confirms and the screen is flipped to landscape.
Even if the user rotates his device back to portrait later, the app stays in landscape.
Objects
Even our small starter app already contains several different objects. The one we have spent the most time with is BullsEyeViewController. The Hit Me button is also an object, as is the alert view. Our project also has an object named BullsEyeAppDelegate, even though we’re going to ignore that for this lesson. And the texts that we put on the alert view — “Hello, World” and “This is my first app!” — are also objects. These object thingies are everywhere!
An object can have both data and functionality:
• An example of data is the Hit Me button that we added to the view controller’s
screen. When we dragged the button into the xib, it actually became part of the
view controller’s data. Data contains something. In this case, the view controller
contains the button.
• An example of functionality is the showAlert action that we added to respond to
that button. Functionality does something. The thing that provides functionality to an object is commonly called a method.
Xib or nib
Xib files are also known as “nib” files. Say what? Technically speaking, a xib file is compiled into a nib
file that is put into your application bundle.
The slider
Remember how we added an action to the view controller in order to recognize when
the user tapped the button? We can do the same thing for the slider. This action will be
performed whenever the user drags the slider’s knob. The steps for adding this action are
largely the same as what we did before.
» First, go to BullsEyeViewController.h and add a declaration for the action, on a line
between showAlert and @end:
BullsEyeViewController.h
- (IBAction)sliderMoved:(UISlider *)slider;
» Go back to the nib (same thing as xib, remember?) and Ctrl-drag from the slider to
File’s Owner. Let go of the mouse button and select sliderMoved from the popup. Done! linkage
» Go to BullsEyeViewController.m and add the following at the bottom, just above the
@end line:
BullsEyeViewController.m
- (IBAction)sliderMoved:(UISlider *)slider { NSLog(@"The value of the slider is now: %f", slider.value); }
» Run the app and drag the slider.
As soon as you start dragging, the Xcode window opens a new pane at the bottom, the
so-called Debug Area, which shows a list of messages:
Altogether
NSString
Variables pp58
When we read the slider’s value in sliderMoved, that piece of data disappears when the
action method ends. It would be handy if we could remember this value until the user
taps the Hit Me button. Fortunately, Objective-C has a building block exactly for this
purpose: the variable.
» Open BullsEyeViewController.m and change the @implementation line at the top to:
BullsEyeViewController.m
@implementation BullsEyeViewController { int currentValue; }
» Change the contents of the sliderMoved action to the following:
- (IBAction)sliderMoved:(UISlider *)slider { currentValue = lroundf(slider.value); }
We use the function lroundf() to round the decimal number to the nearest whole number
and we then store that rounded-off number into the currentValue variable.
» Now change the showAlert method to the following:
- (IBAction)showAlert { NSString *message = [NSString stringWithFormat: @"The value of the slider is: %d", currentValue]; UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hello, World!" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; }
As before, we create and show a UIAlertView, except this time its message says: “The
value of the slider is: X”, where X is replaced by the contents of the currentValue variable
(a whole number between 1 and 100).
%i, %d, %f NSInteger int float
The difference is that %d is used for integer values and %f
is used for decimals (also known as “floating point” in programmer-speak, hence the f).
however, we do not wish to print the result but show it in the alert view.
The default value for instance variables in Objective-C is 0, and that is what we are seeing here.
Our first bug
Think of a reason why the value would be 0 in this particular situation (start the app, don’t move the slider, press the button).
BullsEyeViewController.m
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. }
» Change the viewDidLoad method to the following:
- (void)viewDidLoad { [super viewDidLoad]; currentValue = 50; }
The viewDidLoad message is sent by UIKit as soon as the view controller loads its user
interface from the nib file. At this point, the view controller isn’t visible yet, so it’s a good
place to set instance variables to their proper initial values. In our case, we’ll simply set
currentValue to 50, which should be the same value as the slider’s initial position.
Properties and outlets
- (IBAction)sliderMoved:(UISlider *)slider { currentValue = lroundf(slider.value); }
In this case, when the sliderMoved action is performed, slider contains a reference to
the actual slider object that was moved. The UISlider object basically said, “Hey view
controller, I’m a slider object and I just got moved. By the way, here’s my phone number
so you can get in touch with me.”
» Add the following line to BullsEyeViewController.h:
@property (nonatomic, strong) IBOutlet UISlider *slider;
This tells Interface Builder that we now have an “outlet” named slider that can be connected to a UISlider object. Just as Interface Builder likes to call methods “actions”, it calls these properties outlets.
link
» Open the nib. Hold Ctrl and click on the slider. Let go of the mouse button and a menu
pops up that shows all the connections for this slider. (You can also right-click once.)
This popup menu works exactly the same as the Connections Inspector. I just wanted to
show you that it exists as an alternative.
» Click on the open circle next to “New Referencing Outlet” and drag to File’s Owner:
» In the popup menu that appears, select “slider”.
This is the outlet property that we just added to the class. You have now connected the
slider object from the nib to the view controller’s slider property.
These three steps are necessary for just about any property you add to the view controller
if that property refers to a view in the nib:
1. add @property to the .h file,
2. connect the outlet in Interface Builder,
3. @synthesize in the .m file.
Tip: If you’re using Xcode 4.4 or better, then you no longer need to add @synthesize statements.
The designers of Objective-C decided that the compiler could figure out for itself that the properties
need to be synthesized. So from now on when I say, “synthesize this property”, you can simply
ignore that and your app will still work. How cool is that! Unfortunately, users of Xcode 4.2 and 4.3
will still have to type in the @synthesize statements, so that’s yet another reason to upgrade to
the latest version of Xcode.
Now that we have done all this setup work, we can refer to the slider object from anywhere
inside the view controller using the slider property.
» Change viewDidLoad to:
- (void)viewDidLoad { [super viewDidLoad]; currentValue = self.slider.value; }
The only change here is that we added the word self in front of slider. That is how you
refer to a property. The “self” keyword allows the view controller to refer to itself.
Here, self refers to the view controller. The construct self.slider refers to the slider
property inside the view controller. Finally, self.slider.value refers to the slider’s
value, which also happens to be a property (on the UISlider object).
By the way, Xcode might now be complaining about the sliderMoved method. For me,
it says: “Warning: local declaration of ‘slider’ hides instance variable”.
We get that warning because adding a property named slider also implicitly makes an
instance variable named slider.
I used the new name sender, which is really the standard term for the parameter of an
action method.
Properties vs instance variables
You can tell the difference between the two because properties are always accessed using self:
// This uses the property: self.slider.value = 50; // This uses the backing instance variable directly: slider.value = 50;
So what is the added benefit over using a property versus an instance variable?
There are several reasons, but mainly ivars are supposed to be used only by the insides of an object. Other objects aren’t
intended to see them or use them. Properties, however, can be accessed by other objects.
That’s what we did with Interface Builder: we hooked up the slider object to the view controller’s slider
property. However, you can’t connect things from within Interface Builder to the view controller’s private
parts, the instance variables.
Generating the random number
targetValue = 1 + (arc4random() % 100);
We’re also calling the function arc4random() to deliver an arbitrary integer (whole number)
between 0 and about 4 billion. Our slider doesn’t go quite that far so we want to limit
the random number to be between 1 and 100. To accomplish that, we first use the modulo
operator (%) to restrict the result of arc4random() to a number between 0 and 99. Then
we add 1 to this number to get it +in the range 1 - 100.
The viewDidLoad method is only called once when the view controller is created during app startup.
Rounds
..» With that in mind, add the following new method above viewDidLoad:
BullsEyeViewController.m
- (void)startNewRound { targetValue = 1 + (arc4random() % 100); currentValue = 50; //reset the slider bar self.slider.value = currentValue; }
» And change viewDidLoad to:
- (void)viewDidLoad { [super viewDidLoad]; [self startNewRound]; }
» Make the following changes to showAlert:
BullsEyeViewController.m
- (IBAction)showAlert
{
. . .
[alertView show];
[self startNewRound];
}
This is doing something we haven’t seen before: [self startNewRound]. Earlier we
have used self to refer to one of the view controller’s properties, self.slider. Now
we’re using self to call one of the view controller’s methods.
When you use self, you are sending a message from one method in the object to another
method in that same object. Think of this as telling yourself to do something: “Oh wait,
I have to buy some ice cream first.”
Putting the target value in the label
We already did something similar with the slider. Recall that we created a @property so
we could reference the slider anywhere from within the view controller, and that we could
ask the slider for its value through self.slider.value. We’ll do the same thing for the
label.
When we made the nib, we already added a label for the target value (top-right corner).
Now the trick is to put the value from the targetValue variable into this label. To do that,
we need to accomplish two things:
1. Create a reference to the label so we can send it messages. prpperty
2. Give the label new text to display.
BullsEyeViewController.h
@property (nonatomic, strong) IBOutlet UILabel *targetLabel;
» In BullsEyeViewController.xib, click to select the label. Go to the Connections Inspector
and drag from “New Referencing Outlet” to File’s Owner. Select “targetLabel” from
the popup, and the connection is made. pp82
BullsEyeViewController.m
@synthesize targetLabel;
compiler LLVM 3.0
@property
declares a property on your class with whatever atomicity and setter semantics you provide.
With Xcode 4.4, autosynthesis is available wherein you are provided with a backing ivar from your property without declaring it in @synthesize
. This ivar has the form of _propertyName
where your property name ispropertyName
.
Today, the only reason to use @synthesize
is if you want the ivar to have a non-standard name (never do that).
To be even more clear: object.something
does not mean "return the ivar named _something
from object
." It means "return the result of[object something]
, whatever that does." It is common for that to return the value of an ivar.
You should declare all of your state (internal and external) using@property
declarations, and you should avoid directly declaring ivars. You should also always access your properties via their accessors (self.something
), except in the init
and dealloc
methods. Ininit
and dealloc
, you should directly use the ivar (_something
).
Add the following method above startNewRound: BullsEyeViewController.m
- (void)updateLabels { self.targetLabel.text = [NSString stringWithFormat:@"%d", targetValue]; }
updateLabels is a regular method — it is not attached to any UI controls as an action —
so it won’t do anything until we actually call it.
- (void)viewDidLoad { [super viewDidLoad]; [self startNewRound]; [self updateLabels]; }
- (IBAction)showAlert
{
. . .
[self startNewRound];
[self updateLabels];
}
Action methods vs. normal methods
So what is the difference between an action method and a regular method? Nothing. An action method
is really just the same as any other method. The only special thing is the (IBAction) specifier. This
allows Interface Builder to see the method so you can hook it up to your buttons, sliders, and so on.
..
posted on 2013-01-28 01:37 Chansonyan 阅读(179) 评论(0) 收藏 举报