Wednesday, December 31, 2008

Coming Soon - My OpenGL Particle Engine



Got a start on this over the weekend, and will probably spend some time on it New Year's day. Right now, the basic functionality and physics work, but it only supports simple point drawing - no shapes or texture mapping. I'm also working on recycling the particles rather than constantly allocating and freeing the memory.

I have no idea how this is going to perform on an actual device - I've been working on the simulator so far, but I will be curious to see what these little machines can do when I start adding textures, transparencies and the rest.

Oh, and Happy New Year, everybody.



Tuesday, December 30, 2008

Updated OpenGL ES Xcode Project Template

Here is a new version of my OpenGL ES project template for Xcode. This version adds a class called "OpenGLCommon.h", which contains data structures and inline functions for vertices, vectors, triangles, etc. - mostly stuff from the Wavefront OBJ class that has generic applicability, backported into the template.

As before, to use this, unzip the archive and then drag the resulting folder to the following location (you may need administrator privileges to do this):
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/




Another Wavefront OBJ loader

In a somewhat funny coincidence, author/programmer Bill Dudney has been doing almost exactly the same thing I have been with OpenGL ES - trying to learn (and, in some cases, re-learn) 3D graphic programming by writing a wavefront OBJ loader for importing graphics from Blender. If you've been following my progress on the Wavefront OBJ loader, you ought to follow his progress as well.

Bill, in case you don't know, is the author of several books, including a really good one on Core Animation. He's also one of the authors of the upcoming PragProg iPhone SDK book, and one of the iPhone Development trainers for the Pragmatic Studio, which I've heard good things about.



PVRTC Textures & OpenGL resources from Apple

In a couple of earlier posts, I mentioned the use of PVRTC compressed images for OpenGL texture mapping. I don't know if this is new, or if I just failed to see them earlier, but Apple now has a technote on the format along with some sample code. These are both part of the iPhone Dev Center, so you'll need to be a member of the iPhone Dev program (free version okay) to access them.

Also, if you attended WWDC last year, the attendee site is still available so you can still download presentation materials. The Touch Fighter sample code from the session 703 is a great resource for wrapping your head around OpenGL (you can also get a copy of that if you go to one of the iPhone Tech Talks) If you have access to the WWDC video library, the video and presentations for sessions 708 and 703 are good OpenGL resources also.



Monday, December 29, 2008

A Reusable Date Drill-Down Controller

The other day, I posted a reusable table-based drill-down controller. Today, I'm posting another in my series of re-usable drill-down controllers: a reusable date drill-down.

To use this controller, designed to be used as a drill-down in a Navigation-based App, all you have to do is create an instance of the class, and set that instance's delegate to self, and then set the date to be displayed when it is first shown, like so:

DateViewController *controller = [[DateViewController alloc] init];
controller.delegate = self;
controller.date = theDateToBeEdited;
[delegate.rootController pushViewController:controller animated:YES];
[controller release];

Then, implement this delegate method:

- (void)takeNewDate:(NSDate *)newDate
{
myObject.date = newDate;
}

Now, when the drill-down controller is dismissed, if the user pressed the Save button, that takeNewDate: method will get called with the new value, and all you have to do is stick the new date back into your data model. The controller class is self-contained, and there's no need to edit the controller class at all (though feel free to expand on it if you want). If you want to customize the appearance of the date picker, you can use the controller's datePicker property, for example, to change the date picker's mode:

controller.datePicker.datePickerMode = UIDatePickerModeDateAndTime;

Here's the date picker class:

DateViewController.h
/*
DateViewController.h
*/


#import <UIKit/UIKit.h>
#import "Person.h"

@protocol DateViewDelegate <NSObject>
@required
- (void)takeNewDate:(NSDate *)newDate;
- (UINavigationController *)navController; // Return the navigation controller
@end


@interface DateViewController : UIViewController {
UIDatePicker *datePicker;
NSDate *date;

id <DateViewDelegate> delegate; // weak ref
}
@property (nonatomic, retain) IBOutlet UIDatePicker *datePicker;
@property (nonatomic, retain) NSDate *date;
@property (nonatomic, assign) id <DateViewDelegate> delegate;
-(IBAction)dateChanged;
@end


DateViewController.m
/*
DateViewController.m
*/

#import "DateViewController.h"
#import "BirthdaysCategories.h"

@implementation DateViewController
@synthesize datePicker;
@synthesize date;
@synthesize delegate;

-(IBAction)dateChanged
{
self.date = [datePicker date];
}
-(IBAction)cancel
{
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(IBAction)save
{
[self.delegate takeNewDate:date];
[[self.delegate navController] popViewControllerAnimated:YES];
}
- (void)loadView
{
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];


UIDatePicker *theDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 206.0, 320.0, 216.0)];
self.datePicker = theDatePicker;
[theDatePicker release];
[datePicker addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:datePicker];


UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithTitle:@"Cancel"
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel)]
;
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithTitle:@"Save"
style:UIBarButtonItemStylePlain
target:self
action:@selector(save)]
;
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];

}

- (void)viewWillAppear:(BOOL)animated
{
if (self.date != nil)
[self.datePicker setDate:date animated:YES];
else
[self.datePicker setDate:[NSDate date] animated:YES];

[super viewWillAppear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)dealloc {
[datePicker release];
[date release];
[super dealloc];
}
@end





Learn Objective-C on the Mac is Shipping


Learn Objective-C on the Mac, on which I was the Technical Reviewer, is now shipping.

Obviously, I'm not a disinterested or unbiased party but, honestly, Scott and Mark did an amazing job with this book. The chapter on memory management, which is probably the most difficult aspect of iPhone programming for both new programmers and those coming from other languages, is just phenomenal. Mark and Scott took a difficult concept and made it understandable and concrete.

And the book is exhaustive. I can't think of anything about the language that they don't cover and cover well. I've been coding in Objective-C for almost a decade, and programming for twenty-something years, and I learned several things I either didn't know or had forgotten while reviewing the book. It's also just fun to read. Both Scott and Mark have wicked senses of humor. They'll keep a smile on your face while taking you through some tough material.

One of the complaints I've seen about our iPhone book was that we didn't cover the Objective-C language. I understand the criticism, but it was a conscious decision on our part. The reason we didn't include a chapter or two on the Objective-C language in the iPhone book is because we are the third book in a series (the first book is the classic Learn C on the Mac written by Dave Mark, my co-author on the iPhone book).

As it was, we ran nearly two hundred pages over our statement of work. We didn't want to have to cut relevant iPhone material in order to include material that was covered (and covered well) in the earlier books. We wanted to give people of differing experience levels the flexibility to just pay for the material they needed. A considerable number of our potential readers already know C, a smaller, but not insignificant number of our potential readers already know Objective-C.

On a related note, the last six months have been an interesting time for me. I read Scott's books like "How to Write Macintosh Software" back in the late eighties when I was switching from programming the Apple II to programming the Mac. A few years later, I read Dave's "Learn C" book when I switched from Pascal to C.

After the switch to OS X, Mark's first book, Advanced Mac OS X Programming: Core Mac OS X & Unix Programming was a huge help to me in getting a better understanding of what goes on under the hood in a unix-based operating system.

I feel tremendously lucky to have had the opportunity to work with these more experienced authors.



We broke Amazon's Top 100 Books

I've tried not to post too frequently about the iPhone book I co-wrote, but this morning I'm a little bit in shock and felt one short post to mark the occasion of breaking into Amazon's top 100 books this morning would be okay.

Continuing thanks go out to everybody who bought the book, who took the time to do an Amazon review, and everybody's who recommended it to a friend. Thank you all your support and feedback.



Sunday, December 28, 2008

Reusable Controllers

I thought I'd take a break from the OpenGL stuff to finish a post I've been thinking about doing for a while. One misconception that a lot of newer Cocoa/Cocoa Touch developers seem to have is that your application controller classes have to be completely custom. There is some basis for this belief, as every application will need to have some functionality that is specific to itself, but there are a number of generic, reusable controller classes, such as UITableViewController and UINavigationController that handle some or all aspects involved with controlling a certain type of user interface.

Yet, a lot of people don't carry this idea of reusability into their own controller code for some reason. All too often you see projects with several controller classes that are all very similar to each other. One place I've seen this a lot is when people implement table-based detail editing views like the ones used in the Contacts application. Here is an example of what I'm talking about:

I'm honestly surprised that Apple doesn't provide a generic controller class for this particular scenario, but they don't. There's no reason why we can't create our own generic editable detail pane controller class.

I'm going to provide the code for a version that I've used in a few projects. It allows the editing of up to five text fields. The reason only five fields is supported is because that's all that can be displayed along with the keyboard. If you need more than five fields, you should probably split it into multiple drill downs.

To use this class, you create the controller and set yourself as the delegate. You then set three properties. All three arrays represent the fields to be displayed, so the item at index 0 in all three arrays is different data about the same field, like so:
TextFieldEditingViewController *controller = [[TextFieldEditingViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.delegate = self;

controller.fieldNames = myArrayContainingTheLabelsToBeDisplayed;
controller.fieldKeys = myArrayContainingTheKeyValuesForTheItemToBeDisplayed;
controller.fieldValues = myArrayContainingTheCurrentValuesForTheItemsToBeDisplayed;

fieldNames should contain the label to be used by the detail controller for each item. This is the text in blue in the screenshot above that the user sees. The fieldKeys is the key that corresponds to each field. This will generally be the property name for the field being edited, and will be used in the method that is used to pass values back to the delegate. fieldValues will hold the current values for each of the fields. All threes of these properties must be set, and they must be NSArrays or a subclass of it. All three must contain the same number of objects, and all three must contain five objects or less.

There's another optional property that you can set: keyboardTypes, which is a five element array of UIKeyboardTypes. This can be used to tell the detail controller what type of keyboard to show the user for each field that can be edited. These all default to UIKeyboardTypeAlphabet, but if, say, one of the values you want to edit is phone number, you can pass UIKeyboardTypePhonePad for the array index that corresponds to that field.

Notice in the pseudo-code above that we set self as the controller's delegate. This is how we will be notified if the user edited any of the fields. The valuesDidChange: method will be called on the delegate, passing a dictionary using key-value coding that informs the delegate of all the changes made if the user pressed the Save button. The delegate is then responsible for taking the values from that array and stuffing them in the appropriate place.

This class is not as polished and hasn't been as thoroughly tested as Apple's generic controller classes, but it sure has reduced the number of controller classes I need in some of my table-based applications. So, without further ado, here she is:

TextFieldEditingViewController.h
/*
TextFieldEditingViewController.h
*/


#import <UIKit/UIKit.h>
#define kDefaultLabelTag 50002

@protocol TextFieldEditingViewControllerDelegate <NSObject>
@required
- (void)valuesDidChange:(NSDictionary *)newValues;
- (UINavigationController *)navController; // Return the navigation controller
@end


@interface TextFieldEditingViewController : UITableViewController <UITextFieldDelegate> {

NSArray *fieldNames; // Field name to be displayed to user
NSArray *fieldKeys; // Key value to be used in dictionary when values are passed back to delegate
NSArray *fieldValues; // Starting display values for each field. Values should be strings.

NSMutableArray *changedValues; // Changes will be stored in this array , which will also be passed back to delegate on save
UIKeyboardType keyboardTypes[5]; // Keyboard types for each field

id <TextFieldEditingViewControllerDelegate> delegate; // Delegate who will received the changed values. Delegate
// is responsble for converting back from string if necessary

UITextField *textFieldBeingEdited; // The field currently being edited

UIBarButtonItem *oldLeftButton; // Used to restore old button values
UIBarButtonItem *oldRightButton;
}

@property (nonatomic, retain) NSArray *fieldNames;
@property (nonatomic, retain) NSArray *fieldKeys;
@property (nonatomic, retain) NSArray *fieldValues;
@property (nonatomic, retain) NSMutableArray *changedValues;
@property (nonatomic, assign /* for weak ref */) id <TextFieldEditingViewControllerDelegate> delegate;
@property (nonatomic, retain) UITextField *textFieldBeingEdited;
@property (nonatomic, retain) UIBarButtonItem *oldLeftButton;
@property (nonatomic, retain) UIBarButtonItem *oldRightButton;
-(IBAction)cancel;
-(IBAction)save;
-(IBAction)textFieldDone:(id)sender;
-(void)setKeyboardType:(UIKeyboardType)theType forIndex:(NSUInteger)index;
@end




TextFieldEditingViewController.m
/*
TextFieldEditingViewController.m

*/

#import "TextFieldEditingViewController.h"
#import "BirthdaysAppDelegate.h"

@implementation TextFieldEditingViewController
@synthesize fieldNames;
@synthesize fieldKeys;
@synthesize fieldValues;
@synthesize delegate;
@synthesize changedValues;
@synthesize textFieldBeingEdited;
@synthesize oldLeftButton;
@synthesize oldRightButton;
- (id)initWithStyle:(UITableViewStyle)style
{
if (self = [super initWithStyle:style])
{
for (int i =0; i < 5; i++)
keyboardTypes[i] = UIKeyboardTypeAlphabet;
}
return self;
}
-(IBAction)textFieldDone:(id)sender
{
UITableViewCell *cell = (UITableViewCell *)[[(UIView *)sender superview] superview];
UITableView *table = (UITableView *)[cell superview];
NSIndexPath *textFieldIndexPath = [table indexPathForCell:cell];
NSUInteger row = [textFieldIndexPath row];
row++;
if (row >= [fieldNames count])
row = 0;
NSUInteger newIndex[] = {0, row};
NSIndexPath *newPath = [[NSIndexPath alloc] initWithIndexes:newIndex length:2];
UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:newPath];
UITextField *nextField = nil;
for (UIView *oneView in nextCell.contentView.subviews)
{
if ([oneView isMemberOfClass:[UITextField class]])
nextField = (UITextField *)oneView;
}
[nextField becomeFirstResponder];

}
- (void)viewWillAppear:(BOOL)animated
{
self.oldLeftButton = self.navigationItem.leftBarButtonItem;
self.oldRightButton = self.navigationItem.rightBarButtonItem;

UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithTitle:@"Save"
style:UIBarButtonItemStylePlain
target:self
action:@selector(save)]
;
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];

UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithTitle:@"Cancel"
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel)]
;
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];

[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
self.navigationItem.leftBarButtonItem = oldLeftButton;
self.navigationItem.rightBarButtonItem = oldRightButton;
[super viewWillDisappear:animated];
}
-(IBAction)cancel
{
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(IBAction)save
{
if (textFieldBeingEdited != nil)
[changedValues replaceObjectAtIndex:textFieldBeingEdited.tag withObject:textFieldBeingEdited.text];

[self.delegate valuesDidChange:[NSMutableDictionary dictionaryWithObjects:changedValues forKeys:fieldKeys]];
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(void)setKeyboardType:(UIKeyboardType)theType forIndex:(NSUInteger)index
{
keyboardTypes[index] = theType;
}
#pragma mark -
- (void)setFieldNames:(NSArray *)theFieldNames
{
if ([theFieldNames count] > 5)
{
NSException *e = [NSException exceptionWithName:@"Too Many Values"
reason:@"If more than five values are provided, some will be inaccessible because of the keyboard view"
userInfo:nil
]
;
[e raise];
}
[theFieldNames retain];
[fieldNames release];
fieldNames = theFieldNames;
}
- (void)setFieldValues:(NSArray *)theFieldValues
{
[theFieldValues retain];
[fieldValues release];
fieldValues = theFieldValues;

changedValues = [theFieldValues mutableCopy];
}




- (void)dealloc {
[fieldNames release];
[fieldKeys release];
[fieldValues release];
[textFieldBeingEdited release];
[super dealloc];
}

#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [fieldNames count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *textFieldCellIdentifier = @"textFieldCellIdentifier";


UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:textFieldCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:textFieldCellIdentifier] autorelease];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
label.textAlignment = UITextAlignmentRight;
label.tag = kDefaultLabelTag;
UIFont *font = [UIFont boldSystemFontOfSize:11];
label.textColor = [UIColor colorWithRed:0.4 green:0.4 blue:0.6 alpha:1.0];
label.font = font;
[cell.contentView addSubview:label];
[label release];


UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(94, 10, 200, 25)];
//textField.tag = kDefaultTextFieldTag;
textField.clearsOnBeginEditing = NO;
[textField setDelegate:self];
[textField addTarget:self
action:@selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit
]
;
[cell.contentView addSubview:textField];
}
UILabel *label = (UILabel *)[cell.contentView viewWithTag:kDefaultLabelTag];
UITextField *textField = nil;
for (UIView *oneView in cell.contentView.subviews)
{
if ([oneView isMemberOfClass:[UITextField class]])
textField = (UITextField *)oneView;
}

label.text = [fieldNames objectAtIndex:[indexPath row]];
textField.text = [changedValues objectAtIndex:[indexPath row]];
textField.tag = [indexPath row];
textField.keyboardType = keyboardTypes[[indexPath row]];
return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark -
#pragma mark Text Field Delegate Methods
#pragma mark -
#pragma mark Table Delegate Methods
- (NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
#pragma mark -
#pragma mark Text Field Delegate Methods
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
self.textFieldBeingEdited = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{

[changedValues replaceObjectAtIndex:textField.tag withObject:textField.text];
}

@end