Coding under the Hammer
CocoaTouch
Simple UIView based Animations on the iPhone
Jul 18th
Although for complex animation sequences on the iPhone you need to use either the OpenGL or Apple’s very own Core Animation Framework, a lot of simple animations can be achieved with the methods found in the UIView class. All these animations are actually built upon Core Animation, but they have been wrapped up for you to use with very little code.
All animations triggered by the UIView class happen within a animation block.
To start an animation block you use the UIView Class method:
+ (void)beginAnimations:(NSString *)animationID context:(void *)context;
The animation ID is used in delegate call backs for such things as the beginning and end of animation blocks. The context is an additional piece of information that can be passed through.
As both of these parameters are optional, you comenly see:
[UIView beginAnimations:@"" context:NULL];
To commit these animations, and therefore end the animation block, you need to use the UIView class method:
+ (void)commitAnimations
So what types of animations can you do ? well quite a few.
You can animate views by:
- Changing their alpha
- Changing there size
- Changing there location
In a single animation block you can animate multiple views, and you are also able to nest animation blocks.
So lets do a common example. When a UITableViewCell is being edited you often want to make a UILabel’s (label) alpha change to 0 so it is hidden:
[UIView beginAnimations:@"" context:NULL];
[label setAlpha:(editing ? 0.0 : 1.0)];
[UIView commitAnimations];
For those new to C programming:
(editing ? 0.0 : 1.0)
Is equivlant to:
if(editing)
{
0.0;
}else{
1.0;
}
So for the second example we also have a UILabel (label), that we want to grow when the animation code is run. We also want this animation to last 2 seconds, so the user can admire our work. In addition to this, we want the animation to “ease in”. This is known as the animation curve. The animation curves that are available are
- ease in (slow then fast)
- ease out (fast then slow)
- ease in and out (slow, fast, slow)
- linear (constant speed)
We would do this animation using the following animation block:
[UIView beginAnimations:@"" context:NULL];
//The new frame size
[label setFrame: CGRectMake(0,0,320,100)];
//The animation duration
[UIView setAnimationDuration:2.0];
[UIView setAnimationDelay: UIViewAnimationCurveEaseIn];
[UIView commitAnimations];
This is just a brief overview of the animations you can do using the UIView class, but it should be enough to get you started. The UIView methods also include delegate call backs for when an animation starts and end. For more information see Apple’s Documentation.
If the UIView class does not have what you need, you will probably need to use the Core Animation framework. While using this framework is not trivial, it is not as hard as using OpenGL (which is used commonly for 3D games), and you can build some fantastic animations using it.
NSUserDefaults (Preferences)
Jun 28th
Not everybody likes their applications to behave in the same way, and this is why the majority of Mac and iPhone applications have preferences. The default way to handle these preferences is using the class NSUserDefaults.
NSUserDefaults are stored on the file system as .plist, which is simply an XML document. The way you access NSUserDefaults programatically is very much like accessing an NSMutableDictionary, that is by using keys.
Setting User Defaults
NSUserDefaults can be a bool, float, integer or an object. So if you wanted to set the autosave option (which is a BOOL) for your application to be YES, you would write:
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"AutoSave"];
Moreover if you wanted to set the person’s name to ObjColumnist you would write:
[[NSUserDefaults standardUserDefaults] setObject:@"ObjColumnist" forKey:@"PersonName"];
Reading User Defaults
Reading the user defaults is just as easy as setting them. If we wanted to retrieve the 2 values we stored above, you would simply write:
NSString *name = [ [NSUserDefaults standardUserDefaults] stringForKey:@"PersonName"];
BOOL autoSave = [ [NSUserDefaults standardUserDefaults] boolForKey:@"AutoSave"];
You should also notice that there is a convenience method for retrieving a string for a key, even though we set it as an object.
It is also important to be aware that if you attempt to retrieve a numeric value such as an integer, integerForKey: for a non existent key, you will get the value 0. This is obviously an issue if 0 would trigger a certain preference in you application.
Setting the default user defaults
So you can now set and read the user defaults, but how do you assign their default values ?
The answer is registerDefaults:
This method is usually called in the initialize (class) method of a given application’s AppController. The parameter for this method is an NSDictionary. The code below sets the default preference for the AutoSave option to YES, and the person’s name to @”unknown”.
NSMutableDictionary *defaults=[NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:@"AutoSave"];
[defaults setObject:@"unknown" forKey:@"PersonName"];
[[NSUserDefaults standardUserDefaults] registerDefaults: defaults];
One thing that Cocoa does for you automatically without needing any extra code, is that it only saves to the .plist the values that are different to the defaults values, that were registered using registerDefaults: . This means that if the user does not change any of their default preference settings, there will no be a preference file created.
Note: The code above just stores the preference for the AutoSave option, you still have to create the code to manage the saving of data yourself.
Helper Objects (Delegate and DataSource)
Jun 14th
Helper Objects are used throughout Cocoa and CocoaTouch, and usually take the form of a delegate or dataSource. They are commonly used to add functionality to an existing class without having to subclass it.
In software engineering, the delegation pattern is a technique where an object outwardly expresses certain behaviour but in reality delegates responsibility for implementing that behavior to an associated object in an Inversion of Responsibility. The delegation pattern is the fundamental abstraction that underpins composition (also referred to as aggregation), mixins and aspects.
http://en.wikipedia.org/wiki/Delegation_pattern
The most common use of helper objects in iPhone development is when using UITableViews. When you instantiate a UITableViewController this class is automatically assigned to be the delegate and dataSource of the table view it holds.
self.tableView.delegate=self;
self.tableView.dataSource=self;
The UITableView then calls the appropriate delegate method when it needs information, such as:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
This ask the delegate, what is the height for the row at a given index path, so in your UITableViewController (the delegate) you would write the following, if you wanted the row to be 44 pixels high (the default).
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44;
}
Writing your own delegate.
Most delegates are declared as a protocol, so the compiler knows that the delegate implements the required methods.
Example:
You have created a subclass of UITextView (e.g. OBCTextView), that you use in your application to enter both Tweets and SMS. You want to use the same class for both, BUT tweets have a character limit of 140 and SMS have a character limit of 160 (we will pretend that we are still in the 90s).
So we need to create a delegate that asks for the character limit.
- (NSInteger) charachterLimitForTextView: (OBCTextView *)textView;
As mentioned before this should be declared as a protocol
@protocol OBCTextViewDelegate
- (NSInteger) charachterLimitForTextView: (OBCTextView *)textView;
@end
The OBCTextView would call this method like so:
-(void)askDelegateForCharachterLimit{
NSInterger charachterLimit = [delegate charachterLimitForTextView:self];
//Do something with character limit
}
In our SMS view controller we would declare that we implement the delegate protocol.
@interface OBCSMSViewController : UIViewController < OBCTextViewDelegate > {
}
@end
And then in the implementation file you would implement the following method
- (NSInteger) charchterLimitForTextView: (OBCTextView *)textView{
return 160;
}
This method is required for OBCTextView to work so we should also add the key word @required to the protocol
@protocol OBCTextViewDelegate
@required
- (NSInteger)charchterLimitForTextView:(OBCTextView *)textView;
@end
Optional methods
In your delegate, you may also want to implement optional methods. So for this example we will ask for the text color. You can specify that a method is optional using @optional.
@protocol OBCTextViewDelegate
@required
- (NSInteger)charachterLimitForTextView:(OBCTextView *)textView;
@optional
- (UIColor *)textViewColorForTextView:(OBCTextView *)textView;
@end
As it is optional the delegate class (helper object), does not have to implement it. If it does not implement it, and we attempt to call it, the application will crash. So how do we know if we should call the method or not ? The answer is respondsToSelector.
respondsToSelector allows you to ask an object at run time, if it implements a given selector. So for our example:
-(void)getTextColor{
if( [delegate respondsToSelector: @selector(textViewColorForTextView:)] ){
UIColor *textColor = [delegate textViewColorForTextView:self];
//Do something with the text color
}
}
if your class wants to return a textview color, then you simply implement the method.
- (NSInteger)textViewColorForTextView:(OBCTextView *)textView{
return [UIColor redColor];
}
So that finishes off this post on helper objects, and how you can create your own. As always this example was a simple one to make it easy to follow. You could probably implement the above using setter methods.