Archive

Posts Tagged ‘UIKit’

iPhone Date Range Editor

February 19th, 2010 1 comment

Date range editing is a useful when dealing with calendars or time periods, the iPhone calendar provides a nice simple way of editing a date range. Just open up the Calendar hit the + button and edit the events date range, you get a lovely screen that looks like the image below:



Calendar Date Rang Editor

Unfortunately UIkit does not provide a basic control to edit a date range editor, it does provide the UIDatePicker which can be used to build your own date range editor and this is what I have set about to do. The basic idea is to emulate the basic functionality implemented in the calendar date range editor, i.e. The start and end dates can be selected which in turn alters the date displayed in the UIDatePicker and the control provides basic error detection changing the start date red when invalid and prevent savings if there is an invalid date range.

The following is the result of my attempt to recreate this control in a simple and customisable way, the header file below is rather long but there are only a few items exposed to interface builder to make it easy to setup.

//
//  UIViewControllerDateRangeEditor.h
//  UIViewControllerDateRangeEditor
//

#import

@interface UIViewControllerDateRangeEditor : UIViewController
{
@private

// ----------------------------------------
// control objects
// ----------------------------------------
IBOutlet UIBarButtonItem *	leftBarButton;          // Navigation Button to be used on the left    : Cancel
IBOutlet UIBarButtonItem *	rightBarButton;      // Navigation Button to be used on the right  : Save
// ----------------------------------------

// ----------------------------------------
// Display Content
// ----------------------------------------
IBOutlet UILabel  *			startLabel;                   // start label : used for highlighting and is not altered
IBOutlet UILabel  *			startDate;                     // start date label : used for highlighting and contains the current start date

IBOutlet UILabel  *			endLabel;                     // end label : used for highlighting and is not altered
IBOutlet UILabel  *			endDate;                      // end date label : used for highlighting and contains the current end date
// ----------------------------------------

// ----------------------------------------
// control objects
// ----------------------------------------
IBOutlet UIDatePicker *		datePicker;       // date picker : link the value change to the dateChanged action
// ----------------------------------------

// ----------------------------------------
// Start Date Colors
// ----------------------------------------
UIColor *					normalColor;
UIColor *					highlightColor;
// ----------------------------------------

// ----------------------------------------
// Buttons
// ----------------------------------------
IBOutlet UIButton *			buttonStart;              // button that is to be highlighted at the start
UIButton *					buttonSelected;
UIImage  *					buttonImage;
// ----------------------------------------

// ----------------------------------------
// Date Formatter
// ----------------------------------------
NSDateFormatter *			dateFormatter;
// ----------------------------------------

// ----------------------------------------
// current dateValue
// ----------------------------------------
NSTimeInterval				startTimeInterval;
NSTimeInterval				endTimeInterval;

NSTimeInterval				minTimeInterval;
NSTimeInterval				maxTimeInterval;
// ----------------------------------------

// ----------------------------------------
Boolean						editingStartDate;
Boolean						savePressed;
Boolean						delayReset;
// ----------------------------------------
}

@property (nonatomic) NSTimeInterval startTimeInterval;
@property (nonatomic) NSTimeInterval endTimeInterval;

@property (nonatomic) NSTimeInterval minTimeInterval;
@property (nonatomic) NSTimeInterval maxTimeInterval;

@property (nonatomic, readonly) Boolean savePressed;

- (void) reset;

- (IBAction) cancel:(id)sender;
- (IBAction) save:(id)sender;

- (IBAction) dateChanged:(id)sender;

- (IBAction) selectStartDate:(id)sender;
- (IBAction) selectEndDate:(id)sender;

@end

The View controller provided with this article should permit you to create your own custom date range editor using interface builder, this can either be loaded manually or via the main nib. The sample project provide demonstrates how the view controller is setup and how you can use it in your own project.



Sample Images

Download : UIViewControllerDateRangeEditor.h, UIViewControllerDateRangeEditor.m

Sample Project : UIViewControllerDateRangeEditor

Example Usage :


// -------------------------------------------------------------------
// load  the date range editor and push it onto the view stack
// -------------------------------------------------------------------
- (IBAction) editDateRange:(id)sender
{
if ( editor == nil )
{
editor = [[UIViewControllerDateRangeEditor alloc] initWithNibName:@"UIViewControllerDateRangeEditor" bundle:nil];
editor.title = @"Edit";
}

editor.startTimeInterval = startTime;
editor.endTimeInterval   = endTime;

[[self navigationController] pushViewController:editor animated:TRUE];
}

// -------------------------------------------------------------------
// when the controller returns from the editor copy the date out if save has been
// pressed and reset the controller.
// -------------------------------------------------------------------
- (void) viewWillAppear:(BOOL)animated
{
if ( editor )
{
if ( editor.savePressed )
{
startTime = editor.startTimeInterval;
endTime = editor.endTimeInterval;
}

[editor reset];
}
}

As always the source is provided as is and use it at your own risk, feel free to alter and extend as you see fit. Have fun.

Full Screen Animator for a UIImageView

January 21st, 2010 2 comments

The UIImageView provided by UIKit is a very useful class, it provides a very simple way to add Images into a View hierarchy. It also provides a basic way to provide texture animation, though it does have it’s limitations. 1) you have to load all the images up front, which is fine for a bunch of small images but you don’t really want to do that for fullscreen animations with more than 10 frames. You will start to run low on memory and if you are unable to free enough memory to OS will kill your application. 2) there is no callback support provided when using the UIImageView animations, all you can do is start / stop and check to see if it is animating. If you want to do anything else you need to create your own timer and hope that you get the timing right. The following code is to try and build upon the UIImageView and provide a simple way to add full screen texture animations to your project and have the ability to receive callbacks when an animation starts / stops and when a frame changed.

The UIImageView provides the following API for texture animation:

startAnimating
Starts animating the images in the receiver.

- (void)startAnimating

stopAnimating
Stops animating the images in the receiver.

- (void)stopAnimating

isAnimating
Returns a Boolean value indicating whether the animation is running.

- (BOOL)isAnimating

animationDuration
The amount of time it takes to go through one cycle of the images.

@property(nonatomic) NSTimeInterval animationDuration

animationImages
An array of UIImage objects to use for an animation.

@property(nonatomic, copy) NSArray *animationImages

animationRepeatCount
Specifies the number of times to repeat the animation.

@property(nonatomic) NSInteger animationRepeatCount

So how do you use it? Apples developers have made it really simple, load an array UIImages. Add the image array into the UIImageView via the animationImages property, set the animations total duration. Insert the View into the hierarchy if it’s not already in there and start the animation using the function startAnimating. Simple and easy, I wanted to provide some thing similar and I copied the provided APU and add the extra functionality I wanted, such as callbacks.

UIImageViewAnimator header file:

//
//  UIImageViewAnimator.h
//
//  Created on 16/01/2010.
//

#import <UIKit/UIKit.h>

@interface UIImageViewAnimator : UIView
{
@private
// Image View to Animate.
IBOutlet UIImageView *	imageview;

// Timer
NSTimer *				animtimer;
// Array of Image Names
NSArray *				imageNames;
// Current Animation Index
NSInteger				index;
// Animation Duration
NSTimeInterval			duration;

id						context;
id						delegate;
SEL						frameChangeSelector;
SEL						startSelector;
SEL						stopSelector;

Boolean					cacheImages;
Boolean					reverse;
Boolean					imageset;
}

@property (nonatomic)			NSInteger		index;
@property (nonatomic,readonly)	NSInteger		count;
@property (nonatomic,copy)		NSArray *		imageNames;
@property (nonatomic)			NSTimeInterval	duration;
@property (nonatomic)			Boolean			cacheImages;
@property (nonatomic)			Boolean			reverse;

@property (nonatomic,retain)	id				delegate;
@property (nonatomic)			SEL				startSelector;
@property (nonatomic)			SEL				stopSelector;
@property (nonatomic)			SEL				frameChangeSelector;

- (void) startAnimating;
- (void) startAnimatingWithContext:(id)_context;
- (void) stopAnimating;
- (Boolean) isAnimating;

- (void) precache;

@end

The big difference between the two implementations is that you provide an array of Image Names rather than an array of UIImage’s, this will let the underlying animation code dynamically load the images as they are needed reducing the amount of memory used and decreasing the lightly hood of running out of memory. There is also an extra startAnimating that takes a context or user data object, this will be stored and passed back to the callbacks, the callbacks are called (if provided, they are optional) when the animation is started / stopped and when a frame has been incremented.

Caching:
The code provides two ways of loading images, the caching system uses the [UIImage NamesImage:@"name"] function, this provides a texture cache provided by UIKit if we start to run low on resources the OS will automatically flush some of the images that are not being used. The non-caching system uses the [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@”name” ofType:nil] function this will by pass the OS image cache and when the image is released it’s memory will also be freed. I think that at some point I want to implement a better of doing this.

The code implementation is in no way perfect and really could do with a bit more work, but it has been implemented to meet my needs. Feel free to extend and re-work it as much as you like I have a few changes that I would really like to make but time as always is against me.

Download : UIImageViewAnimator.h, UIImageViewAnimator.m

Sample Project : UIImageViewAnimatorSample

Example Use :

// This sample code and may not compile as it is.

// Fill the animator with the image list
- (void) createAnimation
{
      NSMutableArray * dataset = [NSMutableArray arrayWithCapacity:20];
      for ( int i=0; i<20; i++ )
      {
            NSString * imageName = [NSString stringWithFormat:@"animationImageName%d.png",i];
            [dataset addObject:imageName];
      }

      // image animator constructed inside a nib
      [imageAnimator setImageNames:dataset];

      // set the duration to show the 20 images in 2 seconds
      // thats 1 every tenth of a seconds, though it may be
      // slower due to having to load each individual image.
      [imageAnimator setDuration:2];

      // This will use a different loading function
      // where the OS will try and keep as much
      // of the image data in memory as possible.
      // It may improve the speed of the second
      // playback.
      [imageAnimator setCacheImages:TRUE];

      // Setup the delegates and callbacks
      [imageAnimator setDelegate:self];
      [imageAnimator setStartSelector:@selector(start:)];
      [imageAnimator setStopSelector:@selector(stop:)];
}

- (void) start:(id)_context
{
       NSLog( @"Animation playback has been triggered" );
}

- (void) stop:(id)_context
{
       NSLog( @"Animation playback has finished" );
}

- (void) playAnimation
{
        // If the animation has a few images
        // you can try to pre load all the images
        // this will proved a faster/smoother
        // animation as they will be resident
       // in main memory.
       //[imageAnimator preCache];

       // Reverse will permit you to
       // play the animation backwards
       // Note : You will need to set the
       // index to be the last frame to
       // make use of this feature.
       //[imageAnimator setIndex:[imageAnimator count]-1];
       //[imageAnimator setReverse:TRUE];

       // start the animation playing
       [imageAnimator startAnimating];
}

Note : The current implementation requires the animator to be constructed and setup via Interface Builder. The simplest way to do is change the root views class to be that of the UIImageViewAnimator, add a UIImageView into the high-archy and link the image view to the animator.