Sunday, August 2, 2009

Compatible code for OS 2.2.1 and 3.0

Recently, the itts apple server has a number of changes and one of my favourite opensource app AppSales Mobile failed to get the sales data. It is now updated and thanks to the developer for the updating

http://github.com/omz/AppSales-Mobile/


However, the project was set to compile for SDK3.0, there are only a few changes that need to bring this app to be compatible to OS 2.2.1 and 3.0 at the same time.

Here are the steps involved

(1) Base SDK set to iPhone Device 3.0
(2) In Project Settings, iPhone OS Deployment Target set to iPhone OS 2.2.1
(3) Change the source code in RootViewController.m that have setFont or setImage methods, there are about 18 such changes to be done.


This is how to change the source code by adding respondsToSelector: test before using the deprecated method



change from


  [footer.titleLabel setFont:[UIFont systemFontOfSize:14.0]];


to


  if ( [footer respondsToSelector:@selector(setFont:)] ) {
    [footer setFont:[UIFont systemFontOfSize:14.0]];
  }
  else {
    [footer.titleLabel setFont:[UIFont systemFontOfSize:14.0]];
  }




change from

  cell.imageView.image = [UIImage imageNamed:@"Daily.png"];


to


  if ( [cell respondsToSelector:@selector(setImage:)] ) {
    cell.image = [UIImage imageNamed:@"Daily.png"];
  }
  else {
    cell.imageView.image = [UIImage imageNamed:@"Daily.png"];
  }


You may get warnings when build to actual 2.2.1 OS device, but this is normal. However, you cannot build to 2.2.1 Simulator.

The modified source is available here
http://code.google.com/p/apiexplorer/source/detail?r=20

Revision 20 is a modified source (as above) for version 2009/7

Revision 23 is the updated modification for version 2009/11/19

to checkout a revision

svn checkout -r 20 http://apiexplorer.googlecode.com/svn/branches/AppSalesMobile221 AppSalesMobile221r20
svn checkout -r 23 http://apiexplorer.googlecode.com/svn/branches/AppSalesMobile221 AppSalesMobile221r23



If you want to check out a previous version based on the commit hexdigit from the github repo, do this

git clone git://github.com/omz/AppSales-Mobile.git AppSales0731
cd AppSales0731
git checkout -b b0731 ff248e8e6c23386f867514c1c331a469b7d4cf45
git log






Saturday, August 1, 2009

javacom change name

I have been asked by Sun Microsystems to change my App Store name to others stating that Javacom is an Unauthorized use of Java Trademark.



Obviously, I don't have the ability to counter with their request. But I have a few words to say

First of all, my name in App Store is javacom not Javacom
Second, Java and Java applet are not supported by official iPhone App Store app or web app.
Third, I did not program in Java, I only drink a cup of java when I do programming.

May be I have to drink milk instead.... Coffee may not be good for me




Monday, July 27, 2009

Picture Icon in email signature for iPhone

How to add CSS signature to your iPhone ?

The previous hack was to change the Preferences Settings for jailbreaked iPhone. But I have a better approach for non-jailbreaked iPhone, and with custom picture email signature as well. It works for 2.x firmware.

This will be a new functionality for my app Touch Dial Emoji (version 2.0) in App Store.


See screenshots here

Sunday, July 12, 2009

How to build a single iPhone application support both 2.x and 3.0 at the same time

I asked Apple Technical Support on how to build an iPhone application that runs on iPhone OS 2.x and yet uses iPhone OS 3.0 features if they are available.

They replied with a solution called weak linking

What is weak linking as quoted from http://devworld.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html

When a symbol in a framework is defined as weakly linked, the symbol does not have to be present at runtime for a process to continue running. The static linker identifies a weakly linked symbol as such in any code module that references the symbol. The dynamic linker uses this same information at runtime to determine whether a process can continue running. If a weakly linked symbol is not present in the framework, the code module can continue to run as long as it does not reference the symbol. However, if the symbol is present, the code can use it normally.

Here's the basic steps:

1. Use the latest tools with the latest SDK.

2. In your project, set the IPHONEOS_DEPLOYMENT_TARGET build setting to the oldest OS you want to support (say iPhone OS 2.0). And use GCC 4.2




3. If you use frameworks that are not present on that older OS, set the frameworks to be weak imported. Do this using the Linked Libraries list in the General tab of the target info window.




if you use Makefile to build the app add this in linker flag
  LDFLAGS += -weak_framework MessageUI

and add this key in Info.plist

  <key>MinimumOSVersion</key>
  <string>2.0</string>



This is how to test whether framework is available or not


#import <MessageUI/MessageUI.h>
#include <dlfcn.h>

    if ( dlsym(RTLD_DEFAULT, "MFMailComposeErrorDomain") != NULL ) {
        NSLog(@"%@", @"MessageUI framework is available");
        NSLog(@"MFMailComposeErrorDomain = %@", MFMailComposeErrorDomain);
    } else {
        NSLog(@"%@", @"MessageUI framework is not available");
    }



4. For places where you use C-style imports that aren't present on older systems, check whether the import is present before using it.

5. If you use Objective-C methods that aren't present on older systems, use -respondsToSelector: to verify that the methods are present before calling them.


if ( [[UIApplication sharedApplication] respondsToSelector:@selector(canOpenURL:)] ) {
  if ( [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel:996-1010"]] ) {
    NSLog(@"%@", @"tel URL is supported");
  } else {
    NSLog(@"%@", @"tel URL is not supported");
  }
} else {
  NSLog(@"%@", @"-canOpenURL: not available");
}


6. If you use Objective-C classes that aren't present on older systems, you can't just use the class directly. For example:

obj = [[NSUndoManager alloc] init];

will cause your application to fail to launch on iPhone OS 2.x, even if you weak link to the framework. Rather, you have to do the following:


NSUndoManager undoManager;
Class cls;
cls = NSClassFromString(@"NSUndoManager");
  if (cls != nil) {
    undoManager = [[[cls alloc] init] autorelease];
    NSLog(@"%@", @"NSUndoManager is available");

    // This tests whether we have access to NSUndoManager's selectors.

    [undoManager beginUndoGrouping];
    [undoManager endUndoGrouping];
  } else {
    NSLog(@"%@", @"NSUndoManager not available");
  }
  undoManager = nil;


7. Test, test, test!

Updated:
There is sample source code for the Mail Composer in Developer site
http://developer.apple.com/iphone/library/samplecode/MailComposer/index.html



Sunday, July 5, 2009

How to compile command line utility for iPhone using XCode gcc

hello.c

echo 'main() { printf("Hello, world!\n"); }' > hello.c


compile hello.c

/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.0 -arch armv6 -mthumb -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk -o hello hello.c


compile with framework

/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.0 -arch armv6 -mthumb -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk -framework Foundation -framework CoreFoundation -lobjc -std=c99 main.m -o main


codesign in Mac, if you have developer or self-signed certificate (can't codesign it when in ssh session)

export CODESIGN_ALLOCATE=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate; codesign -f -s "iPhone Developer" hello


or you can codesign in iPhone, that is, send the binary to iPhone and then fake codesign it

ldid -S hello



This is how to use as (assembler) to compile hello.s to ARM thumb binary
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/as -arch armv6 hello.s -o hello


# GAS filename : hello.s
# use as to compile
# /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/as -arch armv6 hello.s -o hello
# use otool to disassemble
# otool -tv hello

  .globl _main
  .code 16
  .thumb_func _main
_main:
  push {r7, lr}
  add r7, sp, #0
  add r3, pc
  mov ip, r3
  mov r3, ip
  mov r0, r3
  pop {r7, pc}


.
.
.

Saturday, June 20, 2009

Use XCode 3.1.2 to build SDK 3.0 app to 3.0 Device without provisioning profile

The trick to skip Provisioning Profile for the new XCode 3.1.3 (iPhone SDK3.0) does not work now. The only thing you can do is to use the old XCode 3.1.2 <Old XCode Dir> to build app for 3.0 device if you don't have the official provisioning profile, until new method to skip provisioning profile can be found.

What you need is to install the new iPhone SDK 3.0 (XCode 3.1.3) in a new directory (non-default dir) <New XCode Dir> and copy the SDK3.0 and the necessary device support files to XCode 3.1.2 and use it build app to 3.0 device.

You need to do the followings:

(1) Create a symbolic link from <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
to <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/

(2) Create a symbolic link from <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/DeviceSupport/3.0 (7A341)
to <Old XCode Dir>Developer/Platforms/iPhoneOS.platform/DeviceSupport/

(3) Backup the folder of <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr

(4) Replace the folder of <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr
by <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr

(5) Edit the <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Info.plist to have the magic keys, may be you have already done so. You can refer to this for instruction.

Info.plist:Select all

<key>OverrideProperties</key>
<dict>
<key>CODE_SIGN_CONTEXT_CLASS</key>
<string>XCiPhoneOSCodeSignContext</string>
<key>DEBUG_INFORMATION_FORMAT</key>
<string>dwarf-with-dsym</string>
<key>EMBEDDED_PROFILE_NAME</key>
<string>embedded.mobileprovision</string>
<key>SDKROOT</key>
<string>iphoneos2.2.1</string>
<key>PROVISIONING_PROFILE_ALLOWED</key>
<string>NO</string>
<key>PROVISIONING_PROFILE_REQUIRED</key>
<string>NO</string>

</dict>


(6) Create a self signed identity say "iPhone Pwned Developer" in your Mac (you probably have this already) see Apple Guide here

(7) Remember to restart Xcode (your old Xcode 3.1.2)

(8) Install the Installd Patch for OS 3.0 to your 3.0 device. This package is in Tweaks Section from the Cydia Source http://iphone.org.hk/apt/ which is released by me. Please reboot device after installation of this package.

(9) Create New Project in in XCode 3.1.2 and in Info.plist, add the magic key of SignerIdentity, you probably know this already. (update, you don't need this after installd patch and for OS 3.0 or above)

<key>SignerIdentity</key>
<string>Apple iPhone OS Application Signing</string>


(10) Use the codesign identity as created in step 6 above to codesign the binary and Build and Go to 3.0 device

P. S. I did not copy the Simulator SDK 3.0, because I can use the iPhone SDK 3.0 in <New XCode Dir> to test 3.0 Simulator. If you want to update the documentation in your old XCode, it is in <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/Documentation

You can use move instead of symbolic link, if you want to uninstall the <New XCode Dir>


For those who wants to get the iPhone SDK 2.2.1 (XCode 3.1.2) , try the direct download link here (you need to login your developer account)

iPhone SDK 2.2.1 direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_for_iphone_os_2.2.1__9m2621a__final/iphone_sdk_for_iphone_os_2.2.19m2621afinal.dmg

iPhone SDK 2.2 direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_for_iphone_os_2.2__9m2621__final/iphone_sdk_for_iphone_os_2.2_9m2621_final.dmg


iPhone SDK 3.0 (Xcode 3.1.3) Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.0__final/iphone_sdk_3.0__leopard__9m2736__final.dmg

iPhone SDK 3.0 (Xcode 3.2) Snow Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.0__final/iphone_sdk_3.0__snow_leopard__final.dmg


iPhone SDK 3.1 with Xcode 3.1.4 Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1__final/iphone_sdk_3.1_with_xcode_3.1_final__leopard__9m2809.dmg

iPhone SDK 3.1 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1__final/iphone_sdk_3.1_with_xcode_3.2_final__snow_leopard__10a432.dmg

iPhone SDK 3.1.2 with XCode 3.1.4 for Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.2__final/iphone_sdk_3.1.2_with_xcode_3.1.4__leopard__9m2809.dmg

iPhone SDK 3.1.2 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.2__final/iphone_sdk_3.1.2_with_xcode_3.2.1__snow_leopard__10m2003.dmg

iPhone SDK 3.1.3 with XCode 3.1.4 for Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.1.4__leopard__9m2809a.dmg

iPhone SDK 3.1.3 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.2.1__snow_leopard__10m2003a.dmg

iPhone SDK 3.2 beta 4 with Xcode 3.2.2 (Snow Leopard)
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.2_beta_4/iphone_sdk_3.2_beta_4_with_xcode_3.2.2.dmg

iPhone SDK 3.2 with Xcode 3.2.2 (Snow Leopard)
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.2__final/xcode_3.2.2_and_iphone_sdk_3.2_final.dmg

Updates: use this script to patch "iPhoneOS\ Build\ System\ Support". If you think the use of dd utility is fragile, don't use it.

patch.sh Select all

#!/bin/bash
cd /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Plug-ins/iPhoneOS\ Build\ System\ Support.xcplugin/Contents/MacOS/

dd if=iPhoneOS\ Build\ System\ Support of=working bs=500 count=255
printf "\x8f\x2a\x00\x00" >> working
dd if=iPhoneOS\ Build\ System\ Support of=working bs=1 skip=127504 seek=127504
/bin/mv -n iPhoneOS\ Build\ System\ Support iPhoneOS\ Build\ System\ Support.original
/bin/mv working iPhoneOS\ Build\ System\ Support
chmod a+x iPhoneOS\ Build\ System\ Support



Enjoy.
.
.
.

Friday, May 15, 2009

OpenGL ES for iPhone : Part 4 with More Drawings and OpenGL Screen Save

In some OpenGL 1.x books you might notice that the gl commands (like belows) are within the code block of glBegin() and glEnd() pair. These gl* commands must be converted to Vertices Array in order to be useful for OpenGL ES for iPhone.


glBegin(GL_LINE_STRIP);
z = -50.0f;
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { x = 50.0f*sin(angle); y = 50.0f*cos(angle); // Specify the point and move the z value up a little glVertex3f(x, y, z); z += 0.5f; } // Done drawing points glEnd();


Typically, you remove the glBegin() and glEnd() commands (that is immediate mode) and create the vertices array and implement the vertices position calculation (if any) inside the setupView and then remove other unsupported gl* commands before putting them to the iPhone OpenGL ES project code.

This is an example converting from the immediate mode to vertex arrays

immediate mode

glBegin(GL_TRIANGLES);
glVertex3f(-2.0, 0.5, 0.0);
glVertex3f(0.0, 4.0, -2.0);
glVertex3f(1.5, 2.5, -0.5);
glEnd();


vertex arrays

GLfloat vertices[] = { -2.0, 0.5, 0.0, 0.0, 4.0, -2.0, 1.5, 2.5, -0.5 };
glVertexPointer(3, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLES, 0, 3);


OpenGL ES does not support the full set of vertex array functions or parameters present in OpenGL






























FunctionNotes
glBegin()Not supported.
glEnd()Not supported.
glEdgeFlag[v]()Not supported.
glVertex{234}{sifd}[v]()Not supported.
glNormal3f()Supported.
glNormal3{bsifd}[v]()Not supported.
glNormal3{bsifd}[v]()Not supported.
glTexCoord{1234}{sifd}[v]()Not supported.
glMultiTexCoord4f()Supported.
glMultiTexCoord{1234}{sifd}[v]()Not supported.
glColor4f()Supported.
glColor{34}{bsifd ub us ui}[v]()Not supported.
glIndex{sifd ub}[v]()Not supported.
glVertexPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glNormalPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glColorPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but
support for GL_UNSIGNED_BYTE has been added.
In addition, the alpha value must be included with all colors;
there is no support for specifying only the RGB values.
glIndexPointer()Not supported.
glTexCoordPointer()Supported.
 Type cannot be GL_INT or GL_DOUBLE, but support
for GL_BYTE has been added. Also, because there is no support
for 1D textures, at least 2 texture coordinates must be provided
per vertex.
glEdgeFlagPointer()Not supported.
glInterleavedArrays()Not supported.
glArrayElement()Not supported.
glDrawArrays()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not supported.
glDrawElements()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not
supported. Type must either be GL_UNSIGNED_BYTE or
GL_UNSIGNED_SHORT (not GL_UNSIGNED_INT).
glDrawRangeElements()Supported.
glEnableClientState()Valid for all supported attributes.
glDisableClientState()Valid for all supported attributes.


Here are some of the typical drawings in 3D. This one is for a rotating Spiral


To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode.

EAGLView.m (for Spiral) Select all

//
// EAGLView.m
// Spiral
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);

glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c = 0;
z = -50.0f;

// Loop around in a circle three times
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { // Calculate x and y values on the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bump up the z value z += 0.5f; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 0.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 0.0f, 0.0f, 0.0f); glRotatef(yRot, 1.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Set Line Width glLineWidth(3.0f); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; xRot += 0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (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]; } [context release]; [super dealloc]; } @end


And this one is for a rotating Fanned Circle.



EAGLView.m (Fanned Circle) Select all

//
// EAGLView.m
// Fanned Circle
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=1.2 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (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]; } [context release]; [super dealloc]; } @end


And also I have found a very nice method saveCurrentScreenToPhotoAlbum to capture the OpenGL view screen here. And below is an implementation on how to capture the screen in iPhone Simulator. You just touch/click the info button at the lower left bottom of iPhone Screen to trigger the screen capture to the Photo Album (Simulator or actual device).

To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode. For this screenshot functionality, you need to add the CoreGraphics Framework to the Xcode Project before build & go.




EAGLView.m (Fanned Circle with Screen Capture) Select all

//
// EAGLView.m
// Fanned Circle with Screen Capture
//
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(saveCurrentScreenToPhotoAlbum) forControlEvents:UIControlEventTouchUpInside];
infoButton.alpha = 0.5f;
infoButton.frame = CGRectMake(17, self.bounds.size.height-33, 33, 33);
[self addSubview:infoButton];
[self bringSubviewToFront:infoButton];

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } // callback for CGDataProviderCreateWithData void releaseScreenshotData(void *info, const void *data, size_t size) { free((void *)data); }; // callback for UIImageWriteToSavedPhotosAlbum - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { NSLog(@"ScreenSave finished\n"); [image release]; // release image } - (void)saveCurrentScreenToPhotoAlbum { NSInteger myDataLength = backingWidth * backingHeight * 4; // allocate array and read pixels into it. GLuint *buffer = (GLuint *) malloc(myDataLength); glReadPixels(0, 0, backingWidth, backingHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // gl renders “upside down” so swap top to bottom into new array. for(int y = 0; y < x =" 0;" top =" buffer[y" bottom =" buffer[(backingHeight" provider =" CGDataProviderCreateWithData(NULL," bitspercomponent =" 8;" bitsperpixel =" 4" bytesperrow =" 4" colorspaceref =" CGColorSpaceCreateDeviceRGB();" bitmapinfo =" kCGBitmapByteOrderDefault;" renderingintent =" kCGRenderingIntentDefault;" imageref =" CGImageCreate(320," myimage =" [[UIImage" viewframebuffer =" 0;" viewrenderbuffer =" 0;" depthrenderbuffer =" 0;" animationtimer =" [NSTimer" animationtimer =" nil;" animationtimer =" newTimer;" animationinterval =" interval;">


In case you might also want to know how to do screenshot for a non-OpenGL ES content programatically. This is the code for viewcontroller. If you put the code in a UIView object change self.view to self

- (void) snapUIView
{
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(myImage, nil, nil, nil);
}