May 26

Here is an example of how to create a view that contains a UITableView that doesn’t take over the whole screen.  An example of an application that uses a small table is the Stocks application that comes with the iPhone / iPod Touch.  You may think you need a UITableViewController – but that only gets in the way and takes over the view.

  • Create a View-based iPhone application and call it TableSize1
  • Modify TableSize1ViewController.h so that it looks like this:

TableSize1ViewController.h

//
//  TableSize1ViewController.h
//  TableSize1
//
//  Created by Mitchell Allen on 5/26/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
#import 
 
@interface TableSize1ViewController : UIViewController {
 
	UITableView *tableView;
}
 
@property (retain, nonatomic) UITableView *tableView;
 
@end

 

  • Modify TableSize1ViewController.m so that it looks like this:

TableSize1ViewController.m

//
//  TableSize1ViewController.m
//  TableSize1
//
//  Created by Mitchell Allen on 5/26/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
#import "TableSize1ViewController.h"
 
@implementation TableSize1ViewController
 
@synthesize tableView;
 
- (void)viewDidLoad {
 
}
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;  // Test hack to display multiple rows.
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    }
 
	NSString *szCell = [[NSString alloc] initWithFormat: @"Row %i", indexPath.row ];
 
	[cell setText:szCell];
 
	[szCell release];
 
    // Set up the cell
    return cell;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic -- create and push a new view controller
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}
 
- (void)dealloc {
    [super dealloc];
}
 
@end
  • In the Resources folder, double-click TableSize1ViewController.xib to launch Interface Builder
  • From Library / Cocoa Touch Plugin / Data Views drag a Table View on to the View and make it about half the height of the view (Select Tools / Library from the main menu if it isn’t visible)
  • Click on the new Table View
  • Click on the second tab in the Inspector window (Select Tools / Inspector from the main menu if it isn’t visible)
  • After clicking the second tab, the title of the Inspector window should be Table View Connections
  • Under Outlets: for both dataSource and delegate, drag the mouse from the circles next to them to the File’s Owner icon in the TableSize1ViewController.xib window.
  • Under Referencing Outlets: drag from New Referencing Outlet to the File’s Owner icon and select tableView.
  • With Interface Builder in focus, select File / Save
  • Go back to your project window and click Build and Go
  • Your application should now display a table that only takes up half the screen.

Tags: , , , ,

May 25

Here is an easy way to create headers and footers for iPhone Table Views.

  1. In your Xcode project right-click on the Resources folder
  2. Select: Add / New File … / iPhone OS / User Interfaces / View XIB / [Next]
  3. Call it HeaderView.xib and click Finish
  4. GIve the View a unique background color and set the height to 44
  5. Repeat for FooterView.xib
  6. Save both files

In your UITableViewController class modify viewDidLoad to contain the following (the editing = YES line is optional – just showing you how to display those cool delete widgets):

- (void)viewDidLoad {
 
	self.tableView.editing = YES;	// Enable delete buttons.
 
	UIView *hView = [[[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil] objectAtIndex:0];
 
	self.tableView.tableHeaderView = hView;
 
	UIView *fView = [[[NSBundle mainBundle] loadNibNamed:@"FooterView" owner:self options:nil] objectAtIndex:0];
 
	self.tableView.tableFooterView = fView;
}

As far as I can tell you can’t lock the header and footer in place. That will require a custom table view.

Tags: , , , ,

May 25

This is my first entry in a new category called “cheat sheet.”

I’m trying to build an iPhone GUI for an application. Every time I want to do something simple – like add a button – I need to pull out my books on the subject and filter through several pages to remind myself how to do it. So I’m putting the info here for myself and anyone else who finds the process unintuitive.

Let’s assume that you are adding a button to an existing UIViewController. The first thing that you need to do is add a handler for the button press to your controller class.

Your *.h file would look something like this:

#import  
 
@interface HomeViewController : UIViewController {
 
}
 
-(IBAction) configButtonPressed:(id)sender;
 
@end

Your *.m (or *.mm) file would contain the corresponding method like this:

-(IBAction) configButtonPressed:(id)sender
{
	NSLog( @"HomeViewController:configButtonPressed:sender" );
}

 

  1. Fire up Interface Builder by double-clicking on your *.xib file.  I’m assuming you already have a View set up.
  2. I’m also assuming that  Tools / Inspector and Tools / Library show when you click on the View.
  3. Under Library / Cocoa Touch Plugin / Inputs & Values drag a Round Rect Button on to your View.
  4. Double-click on the button and fill in a label.
  5. Click elsewhere on the View to get out of label editing mode and single-click the button again.
  6. Select the second tab in the Inspector (or press Apple-Key-2) – the title of the Inspector should say Button Connections.
  7. Put your mouse over the circle to the right of event labeled Touch Up Inside.
  8. Drag the mouse over the File’s Owner icon in the *.xib window.
  9. When you release the mouse, select the method that you added to your controller class (in this example configButtonPressed).
  10. Save the *.xib file, and select Build and Go to make sure you wired the button correctly.
  11. In this example when you click the button it should log something to the Debugger Console.

Tags: , ,

May 15

This is an incomplete experiment creating a class to manage an sqlite3 database on the iPhone.

The place that needs the most work is the mcaDB::queryDB() method. The plan is to eventually have this method populate an array or list that I can access after the query is finished. There are methods in PHP that do similar things.

What got me started was Chapter 11: Basic Data Persistence in Beginning iPhone Development.

You can find documentation and the API reference for SQLite here: http://www.sqlite.org.

/*
 *  mcaDB.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
/*
 *  Instructions for linking to sqlite3 library
 *
 *  In Xcode:
 *
 *      Click on folder: Groups & File / Frameworks
 *      Select menu: Project / Add to Project ...
 *      Navigate to:  /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator[version].sdk/usr/lib/
 *      Select file: libsqlite3.dylib
 *      Uncheck: Copy items into destination groups folder if needed
 *      Change Reference Type to: Relative to Current SDK
 */
 
#include "/usr/include/sqlite3.h"
 
#include "mcaUtils.h"
 
class mcaDB {
 
protected:
 
	sqlite3	*m_database;
 
	bool m_open;
 
public:
 
	void errorDB();
 
	bool openDB( const char *szDB );
 
	void closeDB( bool bForce );
 
	bool execDB( const char *szSQL );
 
	bool queryDB( const char *szSQL );
 
};
/*
 *  mcaDB.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaDB.h"
 
void mcaDB::errorDB()
{
	mcaUtils_logString( @"ERROR", sqlite3_errmsg( m_database ) );
}
 
bool mcaDB::openDB( const char *szDB ) {
 
	NSLog( @"mcaDB::openDB( ... )" );
 
	mcaUtils_logString( @"DATABASE", szDB );
 
	if( sqlite3_open( szDB, &m_database ) != SQLITE_OK ) {
 
		errorDB();
 
		closeDB( true );
 
		return false;
	}
 
	m_open = true;
 
	return true;
}
 
void mcaDB::closeDB( bool bForce ) {
 
	NSLog( @"mcaDB::closeDB( ... )" );
 
	if( m_open || bForce ) {
 
		sqlite3_close( m_database );
 
		m_open = false;
	}
}
 
bool mcaDB::execDB( const char *szQuery ) {
 
	NSLog( @"mcaDB::execDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szQuery );
 
	if( ! m_open ) return false;	
 
	if( sqlite3_exec( m_database, szQuery, NULL, NULL, NULL ) != SQLITE_OK ) {
 
		errorDB();
 
		return false;
	}
 
	return true;
}
 
bool mcaDB::queryDB( const char *szSQL ) {
 
	NSLog( @"mcaDB::queryDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szSQL );
 
	if( ! m_open ) return false;
 
	sqlite3_stmt	*stmt;
 
	if( sqlite3_prepare_v2( m_database, szSQL, -1, &stmt, nil ) == SQLITE_OK) {
 
		while( sqlite3_step( stmt ) == SQLITE_ROW ) {
 
			NSLog( @" ... looping ... " );
 
			int iCols = sqlite3_column_count( stmt );
 
			for( int i = 0; i < iCols; i++ ) {
 
				const char *colName = sqlite3_column_name( stmt, i );
 
				mcaUtils_logString( @"COLUMN", colName );
 
				long colType = sqlite3_column_type( stmt, i );
 
				switch( colType ) {
 
					case SQLITE_INTEGER:
 
						int fldInt = sqlite3_column_int( stmt, i );
						mcaUtils_logInteger( @"INTEGER", fldInt );
 
						break;
					case SQLITE_FLOAT:
 
						NSLog( @"FLOAT" );
 
						float fldFloat = sqlite3_column_double( stmt, i );
						break;
					case SQLITE_TEXT:
 
						const unsigned char * fldStr = sqlite3_column_text( stmt, i );
						mcaUtils_logString( @"TEXT", (const char *) fldStr );
 
						break;
					case SQLITE_BLOB:
 
						int fldSize = sqlite3_column_bytes( stmt, i );
						mcaUtils_logInteger( @"BLOB (size)", fldSize );
						break;
					case SQLITE_NULL:
						break;
					default:
						break;
				}
			}
 
		}
 
		sqlite3_finalize(stmt);
	}
 
	return true;
}

Utility Class

This is just something I whipped up to make it easy to log info in the class above while I debug. This will probably change.

/*
 *  mcaUtils.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
void mcaUtils_logString( NSString *szLabel, const char * str );
void mcaUtils_logInteger( NSString * szLabel, int i );
/*
 *  mcaUtils.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaUtils.h"
 
void mcaUtils_logString( NSString * szLabel, const char * str )
{
	NSString *szTemp = [[NSString alloc] initWithUTF8String:str];
 
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %@", szLabel, szTemp ];
 
	NSLog( szMessage );
 
	[szTemp release];
	[szMessage release];
}
 
void mcaUtils_logInteger( NSString * szLabel, int i )
{
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %d", szLabel, i ];
 
	NSLog( szMessage );
 
	[szMessage release];
}

Sample Usage

This could go into say a viewController class file.

Define the name of your database.

#define TEST_DB_FILE	@"/test.db1"

Build a complete path to your database file.

- (NSString *) dataFilePath;
{
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 
	NSString *documentsDirectory = [paths objectAtIndex:0];
 
	return [documentsDirectory stringByAppendingString:TEST_DB_FILE];
}

Pass the full path of your database to an object – in say your view controllers viewDidLoad method.

    m_table.initDB( [[self dataFilePath] UTF8String] );

This is how you might define the method above. Yes, in a production environment the final method should always close the database before returning.

void testTable::initDB( const char *szDB ) {
 
	NSLog( @"START: testTable::initDB( ... )" );
 
	if( ! m_db.openDB( szDB ) ) {
 
		NSLog( @" ... ERROR opening database" );
 
		return;
	}
 
	if( ! m_db.execDB(  "DROP TABLE TEST_TABLE;" ) ) {
 
		// Since this is just a test database - we drop the table every time to start fresh.
 
		NSLog( @" ... Couldn't drop table ... ignoring " );
	}
 
	if( ! m_db.execDB(  "CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);" ) ) {
 
		NSLog( @" ... ERROR creating table" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT COUNT(*) AS myCount FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT * FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
        m_db.closeDB( false );
 
	NSLog( @"END: testTable::initDB( ... )" );
}

Log Output

mcaDB1[3138:20b] START: testTable::initDB( ... )
mcaDB1[3138:20b] mcaDB::openDB( ... )
mcaDB1[3138:20b] DATABASE = /Users/apple/Library/Application Support/iPhone Simulator/User/Applications/.../Documents/test.db1
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = DROP TABLE TEST_TABLE;
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT COUNT(*) AS myCount FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = myCount
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT * FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 1
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Mercury
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Venus
mcaDB1[3138:20b] mcaDB::closeDB( ... )
mcaDB1[3138:20b] END: testTable::initDB( ... )

Tags: ,

May 08

Because my main interest is in developing portable video games, my focus in the blog has been OpenGL ES. But if you want to know how to develop applications that don’t use OpenGL – the best book I’ve found so far is Beginning iPhone Development: Exploring the iPhone SDK.

The book covers all the important basics and shows you how to build an iPhone UI and wire up the buttons, etc. In fact it’s the book I used to figure out how to write the touch handlers in the previous example. Besides basic interaction (taps, touches, gestures), the book also covers what’s known in the iPhone as “views” (think of them as pages of your application). It also covers things like how to design for rotation, acceleration, location, table views, tab bars, pickers, etc. It even covers the basics of using SQLite3 to save data locally.

Once you are done reading the book, you may realize just how simple it is to recreate many of the apps that you see in the App Store and in the Apple commercials using ready made components supplied by the iPhone SDK.

Even if you decide to just build an OpenGL ES app – you will probably still end up using views to create opening and configuration screens for your app. So no matter what approach you take to developing iPhone apps, you will probably find the information in this book essential.

Besides Amazon – you can buy a PDF version of the book here: http://www.apress.com/book/view/1430216263

Since the book is heavy in Objective-C code, you may also want to pick yourself up a copy of Programming in Objective-C 2.0 (2nd Edition) (Developer’s Library).

Tags: ,

May 06

Before I post anymore code, I figured now would be a good time to backup and show you how to create an OpenGL ES project for the iPhone.

Creating an OpenGL ES project is very simple. Xcode has a wizard for that which will be the starting point. What I’m going to do is show you how to tweak it a bit so that you can do most of your programming in C++.

Step 1: Create an OpenGL ES iPhone Project

  1. Launch Xcode
  2. Under iPhone OS click: Application
  3. Click: OpenGL ES Application
  4. Click: Choose …
  5. For Save As change to: MyWorld1
  6. Click: Save

Step 2: Prepare for C++

  1. Under Groups & Files expand the Classes folder
  2. Rename EAGLView.m to EAGLView.mm – see my post on Mixing C++ and Objective-C if you don’t understand why.
  3. Rename MyWorld1AppDelegate.m to MyWorld1AppDelegate.mm

Step 3: Add a C++ class for your world

  1. Right click on the Classes folder and select Add / New File …
  2. Under Mac OS X click C and C++
  3. Click on the C++ File icon
  4. Click: Next
  5. Highlight the whole filename and change it to MyWorld1.mm
  6. Make sure Also create “MyWorld1.h” is checked
  7. Click: Finish

Step 4: Modify MyWorld1.h

Change the contents of MyWorld1.h to the following:

/*
 *  MyWorld1.h
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MYWORLD1__
#define  __MYWORLD1__
 
//  OpenGL ES includes for the iPhone only
#ifdef TARGET_OS_IPHONE
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
#define SCREEN_X	480
#define SCREEN_Y	320 
 
#endif
 
class MyWorld1 {
 
public:
 
	void setupGL();
	void tearDownGL();
	void resizeGL( int iWidth, int iHeight );
	void paintGL();
 
	void animate();
 
	void clickStart( int x, int y );
	void clickStop( int x, int y );
	void clickDrag( int x, int y );
	void clickCancel( int x, int y );
 
};
 
#endif

Step 5: Modify MyWorld1.mm

Change the contents of MyWorld1.mm to the following:

/*
 *  MyWorld1.mm
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "MyWorld1.h"
 
void MyWorld1::setupGL()
{
	NSLog( @"MyWorld1View::setupGL()" );
 
	// Put your OpenGL ES setup code here.
}
 
void MyWorld1::tearDownGL()
{
	NSLog( @"MyWorld1View::tearDownGL()" );
 
	// Put your OpenGL ES teardown code here.
}
 
void MyWorld1::resizeGL( int w, int h )
{
	// Put your OpenGL ES resize code here.
}
 
void MyWorld1::paintGL()
{
	// Put your OpenGL ES paint code here.
}
 
void MyWorld1::animate()
{
	// Put your OpenGL ES animation code here.
}
 
void MyWorld1::clickStart( int x, int y )
{
	NSLog( @"MyWorld1View::clickStart( %i, %i )", x, y );
 
	// Handle start of user touching the screen
}
 
void MyWorld1::clickStop( int x, int y )
{
	// On iPhone - will not get called if clickCancel is fired.
 
	NSLog( @"MyWorld1View::clickStop( %i, %i )", x, y );
 
	// Handle end of user touching the screen
}
 
void MyWorld1::clickDrag( int x, int y )
{
	NSLog( @"MyWorld1View::clickDrag( %i, %i )", x, y );
 
	// Handle user dragging finger across the screen
}
 
void MyWorld1::clickCancel( int x, int y )
{
	// On iPhone, will be called instead of clickStop - like when phone rings.
 
	NSLog( @"MyWorld1View::clickCancel( %i, %i )", x, y );
 
	// Handle app being stopped by something like the phone ringing on an iPhone.
}

Step 6: Modify EAGLView.h

Change the contents of EAGLView.h to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.h
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
// MCA - Update
#include "MyWorld1.h"
 
/*
This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass.
The view content is basically an EAGL surface you render your OpenGL scene into.
Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
*/
@interface EAGLView : UIView {
 
@private
    /* The pixel dimensions of the backbuffer */
    GLint backingWidth;
    GLint backingHeight;
 
    EAGLContext *context;
 
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint viewRenderbuffer, viewFramebuffer;
 
    /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
    GLuint depthRenderbuffer;
 
    NSTimer *animationTimer;
    NSTimeInterval animationInterval;
 
	// MCA
	MyWorld1	m_world;	// C++ object
}
 
@property NSTimeInterval animationInterval;
 
- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
 
// MCA
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
 
@end

Step 7: Modify EAGLView.mm

Change the contents of EAGLView.mm to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.m
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
 
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
 
#import "EAGLView.h"
 
////////////////////////////////////////////////////
// MCA - IMPORTANT!  Change from 0 to 1
#define USE_DEPTH_BUFFER 1
 
// A class extension to declare private methods
@interface EAGLView ()
 
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;
 
- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;
 
@end
 
 
@implementation EAGLView
 
@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;
 
 
// You must implement this method
+ (Class)layerClass {
    return [CAEAGLLayer class];
}
 
 
//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {
 
    if ((self = [super initWithCoder:coder])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
 
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
 
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
 
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
 
		//////////////////////
		// MCA
		m_world.setupGL();
		m_world.resizeGL( backingWidth, backingHeight );
		////////////////////////
 
        animationInterval = 1.0 / 60.0;
    }
    return self;
}
 
 
- (void)drawView {
 
    [EAGLContext setCurrentContext:context];
 
	/////////////////////////
	// MCA
	m_world.animate();
 
	m_world.resizeGL( backingWidth, backingHeight );
 
	m_world.paintGL();
	///////////////////////////
 
 
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
 
 
- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}
 
 
- (BOOL)createFramebuffer {
 
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
 
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
 
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
 
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
 
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
 
    return YES;
}
 
 
- (void)destroyFramebuffer {
 
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
 
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}
 
 
- (void)startAnimation {
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
 
 
- (void)stopAnimation {
    self.animationTimer = nil;
}
 
 
- (void)setAnimationTimer:(NSTimer *)newTimer {
    [animationTimer invalidate];
    animationTimer = newTimer;
}
 
 
- (void)setAnimationInterval:(NSTimeInterval)interval {
 
    animationInterval = interval;
    if (animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}
 
 
- (void)dealloc {
 
    [self stopAnimation];
 
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
 
	/////////////////////////
	// MCA
	m_world.tearDownGL();
 
    [context release];  
    [super dealloc];
}
 
// MCA
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {
 
	// NSLog(@"DEBUG: Touches began" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStart( pos.x, pos.y );
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches cancelled");
 
	// Will be called if something happens - like the phone rings
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickCancel( pos.x, pos.y );
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches ended");
 
	// Note that if phone rings, touchesCancelled will fire instead of touchesEnded
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStop( pos.x, pos.y );
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches moved" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickDrag( pos.x, pos.y );
}
 
 
@end

Step 8: Click Build and Go

When you run the project you won’t see much – because this is an empty shell waiting for you to add your OpenGL code. If you click on the screen in the simulator, the clicks will appear in the Debugger Console.

Tags: ,

May 05

From my previous example, I’ve refactored the texture class. I’ve moved some of the common code into a new method.

/*
 *  mcaTexture.h
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MCA_TEXTURE__
#define  __MCA_TEXTURE__
 
#ifdef TARGET_OS_IPHONE
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#endif
 
class mcaTexture {
 
protected:
 
	GLuint m_texture;	// OpenGL name for the  texture
 
	void initTexture( size_t w, size_t h, int iType, GLvoid *pImage );
 
public:
 
	GLuint getTexture();
 
	void initWithChecks();
 
	void initFromImage( NSString *location);
 
};
 
#endif
/*
 *  mcaTexture.mm
 *  mcaGL1
 *
 *  Created by Mitchell Allen on 4/24/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaTexture.h"
 
#define checkImageWidth	64
#define checkImageHeight 64
static GLubyte checkImage[ checkImageHeight][checkImageWidth ][ 4 ];
 
GLuint mcaTexture::getTexture()
{
	return m_texture;
}
 
void mcaTexture::initTexture( size_t w, size_t h, int iType, GLvoid *pImage )
{
	NSLog( @"START: mcaTexture::initTexture( '...' )" );
 
	GLint					saveName;
 
	// Now use OpenGL ES to generate a name for the texture.
	// Pass by reference so that our texture variable gets set.
 
	glGenTextures(1, &m_texture);
 
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &saveName);
 
	// Bind the texture name. 
	glBindTexture(GL_TEXTURE_2D, m_texture);
 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
	// Specify a 2D texture image, providing a pointer to the image data in memory
 
	switch( iType ) {
		case 1:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pImage);
			break;
		case 2:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pImage);
			break;
		case 3:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pImage);
			break;
		default:
			break;
	}
 
	glBindTexture(GL_TEXTURE_2D, saveName);
 
	NSLog( @"END: mcaTexture::initTexture()" );
}
 
void mcaTexture::initWithChecks() 
{
	NSLog( @"START: mcaTexture::initWithChecks( '...' )" );
 
	int i, j, c;
 
	for( i = 0; i < checkImageHeight; i++ ) {
		for( j = 0; j < checkImageWidth; j++ ) {
			c = ((((i&0x8)==0)^((j&0x8))==0))*255;
			checkImage[i][j][0] = (GLubyte) c;
			checkImage[i][j][1] = (GLubyte) c;
			checkImage[i][j][2] = (GLubyte) c;
			checkImage[i][j][3] = (GLubyte) 255;  // For ghost effect ( 255 / 2 )
		}
	}
 
	initTexture( checkImageWidth, checkImageHeight, 1, checkImage );
 
	NSLog( @"END: mcaTexture::initWithChecks( '...' )" );
}
 
 
void mcaTexture::initFromImage( NSString *location)
{
	NSLog( @"START: mcaTexture::initFromImage( '...' )" );
 
	// Creates a Core Graphics image from an image file using our location.
	CGImageRef spriteImage = [UIImage imageNamed:location].CGImage;
 
	// Get the width and height of the image.
	size_t w = CGImageGetWidth(spriteImage);
	size_t h = CGImageGetHeight(spriteImage);
 
	//TODO - resize the width and the height to the nearest power of 2.
 
	//Only create a sprite if we were able to properly load the CG image.
	if(spriteImage)
	{
		NSLog( @"... image loaded successfully ..." );
 
		// Allocated memory needed for the bitmap context
		GLubyte *spriteData = (GLubyte *) malloc(w * h * 4);
 
		// Use the bitmap creation function provided by the Core Graphics framework. 
		CGContextRef spriteContext = CGBitmapContextCreate(spriteData, w, h, 8, w * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
 
		// After we create the context, we can draw the sprite image to the context.
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)w, (CGFloat)h), spriteImage);
 
		// We don't need the context at this point, so we need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
 
		initTexture( w, h, 1, spriteData );
 
		// Release the image data, which is now unused.
		free(spriteData);
	}
 
	GLenum	errGL = glGetError();
 
	NSLog( @"... glGetError = %1 ...", errGL );
 
	NSLog( @"END: mcaTexture::initFromImage( '...' )" );
}

Tags: ,

May 04

The code below is my experiment with creating a C++ class for generating and/or loading OpenGL textures for the iPhone (it’s also an experiment with posting mixed C++ and Objective-C code). Not all graphics will load. It will load the ship.png file from the Apple CrashLanding example.

No one codes in a vacuum. A lot of the code here is from bits I pulled together from Apple sample code as well as the following:

OpenGL(R) SuperBible: Comprehensive Tutorial and Reference (4th Edition)

OpenGL(R) Programming Guide: The Official Guide to Learning OpenGL(R), Version 2.1 (6th Edition) – the source of the checkerboard texture code.

http://discussions.apple.com/thread.jspa?messageID=8858657&tstart=0

http://www.idevgames.com/forum/showthread.php?t=16578

http://andreicostin.com – check out his video demo.

/*
 *  mcaTexture.h
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MCA_TEXTURE__
#define  __MCA_TEXTURE__
 
#ifdef TARGET_OS_IPHONE
#import 
#import 
#endif
 
class mcaTexture {
 
protected:
 
	GLuint m_texture;	// OpenGL name for the  texture
 
public:
 
	GLuint getTexture();
 
	void initWithChecks();
 
	void initFromImage( NSString *location);
 
};
 
#endif

UPDATE: Since the two main methods below have a block of common code – I refactored it into another method. You can find the updated listing in my next post: Refactored Texture Class.

/*
 *  mcaTexture.mm
 *  mcaGL1
 *
 *  Created by Mitchell Allen on 4/24/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaTexture.h"
 
#define checkImageWidth	64
#define checkImageHeight 64
static GLubyte checkImage[ checkImageHeight][checkImageWidth ][ 4 ];
 
GLuint mcaTexture::getTexture()
{
	return m_texture;
}
 
void mcaTexture::initWithChecks()
{
	NSLog( @"START: mcaTexture::initWithChecks( '...' )" );
 
	int i, j, c;
 
	for( i = 0; i &lt; checkImageHeight; i++ ) {
		for( j = 0; j &lt; checkImageWidth; j++ ) {
			c = ((((i&amp;0x8)==0)^((j&amp;0x8))==0))*255;
			checkImage[i][j][0] = (GLubyte) c;
			checkImage[i][j][1] = (GLubyte) c;
			checkImage[i][j][2] = (GLubyte) c;
			checkImage[i][j][3] = (GLubyte) 255;  // For ghost effect ( 255 / 2 )
		}
	}
 
	GLint					saveName;
 
	// Now use OpenGL ES to generate a name for the texture.
	// Pass by reference so that our texture variable gets set.
	glGenTextures(1, &amp;m_texture);
 
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &amp;saveName);
 
	// Bind the texture name.
	glBindTexture(GL_TEXTURE_2D, m_texture);
 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
	// Specify a 2D texture image, providing a pointer to the image data in memory
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, checkImage);
	// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, spriteData);
	// glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, spriteData);
 
	glBindTexture(GL_TEXTURE_2D, saveName);
 
	NSLog( @"END: mcaTexture::initWithChecks( '...' )" );
}
 
void mcaTexture::initFromImage( NSString *location)
{
	NSLog( @"START: mcaTexture::initFromImage( '...' )" );
 
	// Creates a Core Graphics image from an image file using our location.
	CGImageRef spriteImage = [UIImage imageNamed:location].CGImage;
 
	// Get the width and height of the image.
	size_t w = CGImageGetWidth(spriteImage);
	size_t h = CGImageGetHeight(spriteImage);
 
	//TODO - resize the width and the height to the nearest power of 2.
 
	//Only create a sprite if we were able to properly load the CG image.
	if(spriteImage)
	{
		NSLog( @"... image loaded successfully ..." );
 
		// Allocated memory needed for the bitmap context
		GLubyte *spriteData = (GLubyte *) malloc(w * h * 4);
 
		// Use the bitmap creation function provided by the Core Graphics framework.
		CGContextRef spriteContext = CGBitmapContextCreate(spriteData, w, h, 8, w * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
 
		// After we create the context, we can draw the sprite image to the context.
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)w, (CGFloat)h), spriteImage);
 
		// We don't need the context at this point, so we need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
 
		GLint					saveName;
 
		// Now use OpenGL ES to generate a name for the texture.
		// Pass by reference so that our texture variable gets set.
		glGenTextures(1, &amp;m_texture);
 
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &amp;saveName);
 
		// Bind the texture name.
		glBindTexture(GL_TEXTURE_2D, m_texture);
 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
		// Specify a 2D texture image, providing a pointer to the image data in memory
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
		// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, spriteData);
		// glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, spriteData);
 
		glBindTexture(GL_TEXTURE_2D, saveName);
 
		// glEnable(GL_LIGHTING);
 
		// Release the image data, which is now unused.
		free(spriteData);
	}
 
	GLenum	errGL = glGetError();
 
	NSLog( @"... glGetError = %1 ...", errGL );
 
	NSLog( @"END: mcaTexture::initFromImage( '...' )" );
}

Sample Usage

The sample code below is incomplete for brevity. It’s just meant to give you a general idea of how to use the texture class.

/*
 *  mcaPlane.mm
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaPlane.h"
 
void mcaPlane::init( GLfloat xpos, GLfloat ypos, GLfloat fSize )
{
	// ...
}
 
void mcaPlane::initWithTexture( GLfloat xpos, GLfloat ypos, GLfloat fSize, GLuint gTexture )
{
	init( xpos, ypos, fSize );
 
	m_glTexture = gTexture;
}
 
void mcaPlane::paint()
{
	if( ! isVisible() ) return;
 
	//Push the matrix so we can keep it as it was previously.
	glPushMatrix();
 
	glMatrixMode(GL_MODELVIEW);
 
	//Enable 2D textures.
	glEnable(GL_TEXTURE_2D);
 
	//Bind this texture.
	glBindTexture(GL_TEXTURE_2D, m_glTexture );
 
	// Set the texture parameters to use a minifying filter and a linear filer.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
	//Store the coordinates/dimensions from the rectangle.
	CGFloat w = m_planeSize;
	CGFloat h = m_planeSize;
 
	//Translate the OpenGL context to the center for rotation.
	glTranslatef( m_xpos + w/2, m_ypos +h/2, 0.0f);
 
	//Apply the rotation over the axis.
	glRotatef(m_spriteRotation, 1.0f, 0.0f, 0.0f);
 
	//Translate back to the top left corner  for drawing.
	glTranslatef(-w/2, -h/2, 0.0f);
 
	//Draw to match 
 
	const GLfloat planeVertices[] = {
 
            // Define the plane face
 
            -1.0,   1.0, 0.0,            // top left
            -1.0, -1.0, 0.0,            // bottom left
              1.0, -1.0, 0.0,            // bottom right
             1.0,    1.0, 0.0,            // top right
       };    
 
	const GLshort squareTextureCoords[] = {
 
           // Plane
 
           0, 1,       // top left
           0, 0,       // bottom left
           1, 0,       // bottom right
           1, 1,       // top right
        };
 
	glScalef( m_planeSize, m_planeSize, m_planeSize );
 
	glRotatef( m_xRot, 1.0f, 0.0f, 0.0f );
	glRotatef( m_yRot, 0.0f, 1.0f, 0.0f );
	glRotatef( m_zRot, 0.0f, 0.0f, 1.0f );
 
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 
	glBindTexture(GL_TEXTURE_2D, m_glTexture );
 
	glVertexPointer(3, GL_FLOAT, 0, planeVertices );
	glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords );
 
	// Draw the plane in white
	glColor4f(1.0, 1.0, 1.0, 1.0);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
	//Allow transparency and blending.
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
	//Restore the model view matrix to prevent contamination.
	glPopMatrix();
}
	mcaTexture txChecks, txShip;
 
	NSLog( @"... initializing textures ..." );
 
	txChecks.initWithChecks();
	txShip.initFromImage( @"Ship.png" );
 
	NSLog( @"... initializing objects ..." );
 
	testPlane[ 0 ].initWithTexture(  0.0f, 0.0f, 100.f, txChecks.getTexture() );
	testPlane[ 1 ].initWithTexture( 50.0f, 0.0f, 100.f, txChecks.getTexture() );
	testPlane[ 2 ].initWithTexture(  0.0f, 0.0f, 125.f, txShip.getTexture() );

Tags: ,