How to Show an Alert with UIAlertView
Showing an alert message to the user isn’t just a handy interface lick, it’s also an invaluable debugging tool. Thankfully, this functionality is built into the iPhone SDK.
To pop an alert with a title, message and OK button, we use the UIAlertView class like so:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"Announcement" message: @"It turns out that you are playing Addicus!" delegate: nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release];
But what if you want to have more than one button on the alert and collect user input from it? Well that is pretty simple too. To do that, you first need to create a class that employs the UIAlertViewDelegate delegate like so:
@interface MyClass : NSObject <UIAlertViewDelegate>
Then, in your class, you need to override the alertView method in order to receive input like so:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { NSLog(@"user pressed OK"); } else { NSLog(@"user pressed Cancel"); } }
Finally, we create the UIAlertView and pass in the delegate. Also note that we’ve labeled the Cancel button “Cancel” and added the “OK” button to the other button titles, which differs slightly from the above example.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"Announcement" message: @"It turns out that you are playing Addicus!" delegate: MY_DELEGATE cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK",nil]; [alert show]; [alert release];
How to Open a URL in Safari
Tis the season to broaden your iPhone dev chops! We have been developing on the iPhone platform for about 6 months now and it turns out that in that time, you tend to learn lots of little tips and tricks. Since we’re overcome by the spirit of giving around this time of year, we are going to be posting 24 of these bite-sized iPhone development tips, 1 every day between now and Christmas day. Consider it an advent calendar of iPhone dev tip goodies.
On With The First Tip!
Say you would like to open a URL in Safari. We used this on the “Get Set” button on the main menu of Addicus. Here is how to do it with just one line of code using the UIApplication class:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://getsetgames.com/"]];
Saving and loading user data and preferences
There is a collectively recognized method for storing application data and preferences which is NSUserDefaults. The API for this is a bit unconventional and I thought an appropriate wrapper class would work well in this case. While not completely understanding the full details of NSUserDefaults I developed a singleton class which we can use in our games to save/load data. The interface is simple…
SettingsManager.h
#import <Foundation/Foundation.h> @interface SettingsManager : NSObject { NSMutableDictionary* settings; } -(NSString *)getString:(NSString*)value; -(int)getInt:(NSString*)value; -(void)setValue:(NSString*)value newString:(NSString *)aValue; -(void)setValue:(NSString*)value newInt:(int)aValue; -(void)save; -(void)load; -(void)logSettings; +(SettingsManager*)sharedSettingsManager; @end
SettingsManager.m
#import "SettingsManager.h" @implementation SettingsManager static SettingsManager* _sharedSettingsManager = nil; -(NSString *)getString:(NSString*)value { return [settings objectForKey:value]; } -(int)getInt:(NSString*)value { return [[settings objectForKey:value] intValue]; } -(void)setValue:(NSString*)value newString:(NSString *)aValue { [settings setObject:aValue forKey:value]; } -(void)setValue:(NSString*)value newInt:(int)aValue { [settings setObject:[NSString stringWithFormat:@"%i",aValue] forKey:value]; } -(void)save { // NOTE: You should be replace "MyAppName" with your own custom application string. // [[NSUserDefaults standardUserDefaults] setObject:settings forKey:@"MyAppName"]; [[NSUserDefaults standardUserDefaults] synchronize]; } -(void)load { // NOTE: You should be replace "MyAppName" with your own custom application string. // [settings addEntriesFromDictionary:[[NSUserDefaults standardUserDefaults] objectForKey:@"MyAppName"]]; } // Logging function great for checkin out what keys/values you have at any given time // -(void)logSettings { for(NSString* item in [settings allKeys]) { NSLog(@"[SettingsManager KEY:%@ - VALUE:%@]", item, [settings valueForKey:item]); } } +(SettingsManager*)sharedSettingsManager { @synchronized([SettingsManager class]) { if (!_sharedSettingsManager) [[self alloc] init]; return _sharedSettingsManager; } return nil; } +(id)alloc { @synchronized([SettingsManager class]) { NSAssert(_sharedSettingsManager == nil, @"Attempted to allocate a second instance of a singleton."); _sharedSettingsManager = [[super alloc] init]; return _sharedSettingsManager; } return nil; } -(id)autorelease { return self; } -(id)init { settings = [[NSMutableDictionary alloc] initWithCapacity:5]; return [super init]; } @end
Values can then be saved/retrieved in code like so…
[[SettingsManager sharedSettingsManager] saveValue:@"hiscore" newValue:@"10000"]; NSString* myValue = [[SettingsManager sharedSettingsManager] getValue:@"hiscore"]; int myInt = [[SettingsManager sharedSettingsManager] getInt:@"challenges_won"];
To save our values on an incoming call or other system interruption we can put save/load calls in the appropriate callbacks of the app delegate…
- (void)applicationDidFinishLaunching:(UIApplication *)application { // ... (some code) ... // Load settings on initial startup // [[SettingsManager sharedSettingsManager] load]; // ... (more code) ... } - (void)applicationWillResignActive:(UIApplication *)application { [[SettingsManager sharedSettingsManager] save]; [[Director sharedDirector] pause]; } - (void)applicationDidBecomeActive:(UIApplication *)application { [[SettingsManager sharedSettingsManager] load]; [[Director sharedDirector] resume]; } - (void)applicationWillTerminate:(UIApplication *)application { [[SettingsManager sharedSettingsManager] save]; [[Director sharedDirector] end]; }
This basic class is great for storing arbitrary user state values that you want to preserve for the next time someone plays your game. Hi scores, level progression, number of lives are a few of the possibilities this class could be used for.
Custom cocos2d Action For Animating an AtlasSprite’s TextureRect
While working on our coming-soon first iPhone game, we have been having a lot of success with cocos2d. One area in particular that we have been impressed with is the ability to run “Actions” on sprites, string them together in sequences and wrap them in modifiers such as ease-in, ease-out etc. This has made it easy to make our UI lively and animated as well as animate our game objects.
Recently we had a need to animate an AtlasSprite’s texture rect in order to pan around an atlas sprite. We ended up creating a custom cocos2d action to do this and we figured it would be a good thing to give back to the community.
This will allow you to transform an AtlasSprite’s texture rect’s origin and size over the length of a provided duration. You can also wrap this in a modifier action such as ease-in or repeater.
Here is the code
TransformTextureRect.h:
#import "cocos2d.h" @interface TransformTextureRect : IntervalAction { CGRect fromRect; CGRect toRect; } +(id) actionWithDuration:(ccTime)d toRect:(CGRect)rect; -(id) initWithDuration:(ccTime)d toRect:(CGRect)rect; @end
TransformTextureRect.m:
#import "TransformTextureRect.h" @implementation TransformTextureRect +(id) actionWithDuration:(ccTime)d toRect:(CGRect)rect { return [[[self alloc] initWithDuration:d toRect:rect] autorelease]; } -(id) initWithDuration:(ccTime)d toRect:(CGRect)rect { if ( (self=[super initWithDuration:d] ) ) toRect = rect; return self; } -(id) copyWithZone: (NSZone*) zone { Action *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] toRect: toRect]; return copy; } -(void) start { [super start]; fromRect = [(AtlasSprite*)target textureRect]; } -(void) update: (ccTime) t { [(AtlasSprite*)target setTextureRect:CGRectMake(fromRect.origin.x + (toRect.origin.x - fromRect.origin.x) * t, fromRect.origin.y + (toRect.origin.y - fromRect.origin.y) * t, fromRect.size.width + (toRect.size.width - fromRect.size.width) * t, fromRect.size.height + (toRect.size.height - fromRect.size.height) * t)]; } @end
Example Usage
This transforms an AtlasSprite’s texture rect to the position (25,25) and with a width and height of 50 over the course of 1 second:
[sprite runAction:[TransformTextureRect actionWithDuration:1 toRect:CGRectMake(25,25,50,50)]];
This does something similar to the above example, only it eases in and out of the action as well.
[sprite runAction:[EaseExponentialInOut actionWithAction:[TransformTextureRect actionWithDuration:2 toRect:CGRectMake(480,0,480,320)]]];
The Objective-C Singleton
One of the most useful patterns that we have employed in our iPhone game is the singleton. For those who don’t know, singletons are a class that only gets instantiated once in your application’s run-time. They often take the form of manager or factory classes. A good example of a singleton is the web-based resource manager class that we posted about recently.
We have been using singletons for a variety of things in our cocos2d-based game. Including:
- Resource management
- Atlas sprite managers
- User settings management
- Score management
Below is a template for the singletons that we use in objective-c.
MySingleton.h:
#import <Foundation/Foundation.h> @interface MySingleton : NSObject { } +(MySingleton*)sharedMySingleton; -(void)sayHello; @end
MySingleton.m:
@implementation MySingleton static MySingleton* _sharedMySingleton = nil; +(MySingleton*)sharedMySingleton { @synchronized([MySingleton class]) { if (!_sharedMySingleton) [[self alloc] init]; return _sharedMySingleton; } return nil; } +(id)alloc { @synchronized([MySingleton class]) { NSAssert(_sharedMySingleton == nil, @"Attempted to allocate a second instance of a singleton."); _sharedMySingleton = [super alloc]; return _sharedMySingleton; } return nil; } -(id)init { self = [super init]; if (self != nil) { // initialize stuff here } return self; } -(void)sayHello { NSLog(@"Hello World!"); } @end
Example Usage
Using the methods of the singleton is then as easy as this:
[[MySingleton sharedMySingleton] sayHello];
Cocos2D and UIScrollView
Learning now to work with a new framework is usually a challenge. You often delve into a new API or SDK expecting things to work in a certain manner. When those expectations fail you need to delve deeper to find your answer in several different ways. It often starts with visiting the website of the package you are working with. This often leads to..
- Code documentation online (Doxygen usually)
- Discussion forums, Cocos thankfully has an excellent support community.
- Developer support sites/communities (Stack Overflow, Experts Exchange)
- Manual code debugging
- Google searches which leads to one of the listed items
All of these approaches have their pros and cons and I have often found its some combination of these sources that will lead you to your answer. In my case I have been trying over the past couple of weeks to get Cocos2D working with a UIScrollView container. This is a requirement for the game we are working on to allow players to get the familiar scrolling movement you see in many iPhone apps.
We tried a number of approaches to get this to work properly…
- Cocos window embedded inside a UIScrollView instance
- Changing the size of the OpenGL rendering window to render a larger space. Works but there is a serious performance hit.
- A UIScrollView instance overlaid on top of a Cocos window. If the scroll movements were caught they could be passed onto the Cocos window manually.
Ultimately we chose option #3 and this is the option I will cover details for in this article as it is ultimately the only solution which worked for us properly.
Modifications to the Cocos source code
The first step to get this to work properly is a small modification to the Cocos source code. The reason being that Cocos will freeze while working with a UIScrollView by default. Luckily the location to you need to modify is noticeably documented in the Cocos source code. In Director.m look for this block of comment with code…
// // If you want to attach the opengl view into UIScrollView // uncomment this line to prevent 'freezing'. It doesn't work on // with the Fast Director // // [[NSRunLoop currentRunLoop] addTimer:animationTimerforMode:NSRunLoopCommonModes];
Simply uncomment this line of code to get freeze free scrolling. Leave the lines commented out to see the difference in movement as my description may not make it readily apparent.
EDIT: If you are using Cocos 0.99.4 or later use the following directions provided by one of our helpful community members “drootang” for solving the freezing issue…
I’m using 0.99.4 and it took me a while to figure out that the new hello world uses the following line in applicationDidFinishLaunching:
CC_DIRECTOR_INIT();
This macro (among other things) does the following:
if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeNSTimer];
You need to force it to set the director type to kCCDirectorTypeNSTimer to prevent cocos2d from “freezing” while scrolling. Either change
the line in the macro, or copy the macro contents to your appDidFinishLaunching method, and change the line above to simply:
[CCDirector setDirectorType:kCCDirectorTypeNSTimer] ;
Using a view controller and a UIScrollView together
Detecting touches in UIScrollView
One of the necessary steps to getting this to work properly would be to detect both touches and swipes in the UIScrollView instance and pass those messages along to the Cocos window. Researching this in Google revealed the following forum posting which was a huge help. The solution there is to use a UIViewController where the view in that controller is an instance of UIScrollView. Here’s what that setup looks like in our codebase…
CocosOverlayViewController.h
#import @interface CocosOverlayViewController : UIViewController { } @end
CocosOverlayViewController.m
#import "CocosOverlayViewController.h" #import "CocosOverlayScrollView.h" @implementation CocosOverlayViewController // Implement loadView to create a view hierarchy programmatically, without using a nib. - (void)loadView { CocosOverlayScrollView *scrollView = [[CocosOverlayScrollView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame]; // NOTE - I have hardcoded the size to 1024x1024 as that is the size of the levels in // our game. Ideally this value would be parameterized or configurable. // scrollView.contentSize = CGSizeMake(1024, 1024); scrollView.delegate = scrollView; [scrollView setUserInteractionEnabled:TRUE]; [scrollView setScrollEnabled:TRUE]; self.view = scrollView; [scrollView release]; } @end
CocosOverlayScrollView.h
#import @interface CocosOverlayScrollView : UIScrollView { } @end
CocosOverlayScrollView.m
#import "CocosOverlayScrollView.h" #import "cocos2d.h" @implementation CocosOverlayScrollView -(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { if (!self.dragging) { UITouch* touch = [[touches allObjects] objectAtIndex:0]; CGPoint location = [touch locationInView: [touch view]]; [self.nextResponder touchesBegan: touches withEvent:event]; [[[Director sharedDirector] openGLView] touchesBegan:touches withEvent:event]; } [super touchesBegan: touches withEvent: event]; } -(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event { if (!self.dragging) { [self.nextResponder touchesEnded: touches withEvent:event]; [[[Director sharedDirector] openGLView] touchesEnded:touches withEvent:event]; } [super touchesEnded: touches withEvent: event]; } - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { // TODO - Custom code for handling deceleration of the scroll view } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGPoint dragPt = [scrollView contentOffset]; Scene* currentScene = [[Director sharedDirector] runningScene]; // Only take the top layer to modify but other layers could be retrieved as well // Layer* topLayer = (Layer *)[currentScene.children objectAtIndex:0]; dragPt = [[Director sharedDirector] convertCoordinate:dragPt]; dragPt.y = dragPt.y * -1; dragPt.x = dragPt.x * -1; CGPoint newLayerPosition = CGPointMake(dragPt.x + (scrollView.contentSize.height * 0.5f), dragPt.y + (scrollView.contentSize.width * 0.5f)); [topLayer setPosition:newLayerPosition]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { CGPoint dragPt = [scrollView contentOffset]; } @end
This is by no means a perfect solution yet as I still have some values hardcoded such as the size of the content for the scroll view window. I will certainly be working to improve the finer points but hopefully the core issues I’ve put together will be useful to some of you. Our next dev video (when we get it posted) will show the scroll view in action.
Improving the Performance of Animating Sprites in cocos2d
cocos2d has proven to be invaluable in enabling us to get our iPhone game up and running fast. But when we started playing with animated sprites, we noticed some performance problems. If you are like us and have many sprites that have to have repeating animations, you may have noticed the same thing.
Conventional wisdom (aka the sample code) says that you would create a RepeatForever action using an Animate action to animate an AtlasSprite. When we tried this, however, we noticed that at the end of each animation cycle, the action would deallocate an AtlasSpriteFrame. This is a by-product of the Animate action being able to restore the original frame of animation once the animation has finished playing. This is also what was causing the performance issues.
To resolve this, we created a cut-down version of the Animate action called FastAnimate. It is very similar to the Animate action with the exception that it does not support restoring the original frame of animation. From what we’ve seen, this simple change greatly increases the performance of animating sprites in cocos2d.
FastAnimate.h:
#import "cocos2d.h" @interface FastAnimate : IntervalAction <NSCopying> { Animation *animation; } +(id) actionWithAnimation:(id<CocosAnimation>) a; -(id) initWithAnimation:(id<CocosAnimation>) a; @end
FastAnimate.m:
#import "FastAnimate.h" @implementation FastAnimate +(id) actionWithAnimation: (id<CocosAnimation>)anim { return [[[self alloc] initWithAnimation:anim] autorelease]; } -(id) initWithAnimation: (id<CocosAnimation>)anim { NSAssert( anim!=nil, @"Animate: argument Animation must be non-nil"); if( (self=[super initWithDuration: [[anim frames] count] * [anim delay]]) ) { animation = [anim retain]; } return self; } -(id) copyWithZone: (NSZone*) zone { return [[[self class] allocWithZone: zone] initWithAnimation: animation]; } -(void) dealloc { [animation release]; [super dealloc]; } -(void) startWithTarget:(id)aTarget { [super startWithTarget:aTarget]; } -(void) stop { [super stop]; } -(void) update: (ccTime) t { NSUInteger idx=0; ccTime slice = 1.0f / [[animation frames] count]; if(t !=0 ) idx = t/ slice; if( idx >= [[animation frames] count] ) { idx = [[animation frames] count] -1; } id<CocosNodeFrames> sprite = (id<CocosNodeFrames>) target; if (! [sprite isFrameDisplayed: [[animation frames] objectAtIndex: idx]] ) { [sprite setDisplayFrame: [[animation frames] objectAtIndex:idx]]; } } @end
5 Ways to Take Screenshots of Your iPhone App
Screenshots are an integral tool in application development, not to mention marketing. You may find you need a screenshot in any number of situations. Here are 5 ways you can capture a screenshot of your iPhone app. Each one is useful in a different way.
Do It With OS X
The first method is good old screen capture that is built into OS X. This is, of course, not exclusive to iPhone apps. You can hold down Command-Shift-3 to save a screenshot to your desktop in PNG format. This is useful for if you want to take a screenshot while you are running your app in the emulator.
Even more useful, in my opinion, is the Command-Shift-4 function. This turns your cursor into cross-hairs and lets you select a region of the screen to save as your screenshot, again as a PNG. This is great if you just wanted to grab a portion of your app.
You can also add Ctrl to both of the above keyboard shortcuts to save the screenshot to your clipboard instead of to disk. Then you can go paste the screenshot into Photoshop or some other image editor.
Do It With Grab
Grab is an application that comes with OS X which you can find in your Applications/Utilities folder. It features the unique ability to take a screenshot of a window:
This means if you run your app in the iPhone Simulator and then use Grab, you can take screenshots of your app looking all hot in the actual device with just a couple of clicks. This is a great way to get shots of your app in the iPhone if you don’t own one or aren’t a paid developer. Here’s the result:
Screenshots that come out of Grab are in TIFF format.
Do It With Xcode
Xcode’s Organizer tool provides a simple interface for taking screenshots of whatever is currently displaying on an iPhone that is connected to your computer. Open it by going to Xcode’s Window menu and selecting Organizer or just hit Ctrl-Command-O.
Once it is opened, select your iPhone from the Devices section on the left. Then go to the Screenshot tab in the main section. Now use your application on the iPhone until it is in a state where you want to take a screenshot. Finally, press the capture button in Organizer and boom: you’re done.
Xcode saves screenshots as PNG files in the following folder on your machine:
~/Library/Application Support/Developer/Shared/Xcode/Screenshots/
Do It With the iPhone
Did you know you can take a screenshot of anything that the iPhone is doing at any time? It’s true. Just press and hold the home button. While you’re holding it, press and release the Sleep/Wake button. The iPhone’s screen will flash and the camera shutter sound will play. The screenshot will have been saved to your camera roll, which you can access in the Camera app or the Photos app.
Do It With Code
Finally, you can capture screenshots at precise moments in your application by using the following pair of objective-c methods. The first one, captureView, returns a UIImage that contains a render of any UIView (except EAGLView, see below). If you pass your app’s UIWindow into this method, it will return you a screenshot of the app.
The second method, saveScreenshotToPhotosAlbum, takes it a step further and saves an image that contains a render of any UIView to your iPhone’s photo album.
#import - (UIImage*)captureView:(UIView *)view { CGRect rect = [[UIScreen mainScreen] bounds]; UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext(); [view.layer renderInContext:context]; UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return img; } - (void)saveScreenshotToPhotosAlbum:(UIView *)view { UIImageWriteToSavedPhotosAlbum([self captureView:view], nil, nil, nil); }
Since the above 2 methods don’t work for EAGLView, we need another solution. Below are a pair of methods that do the equivalent of the above 2 for apps using EAGLView, courtesy iPhone Dev SDK Forum.
- (UIImage*) getGLScreenshot { NSInteger myDataLength = 320 * 480 * 4; // allocate array and read pixels into it. GLubyte *buffer = (GLubyte *) malloc(myDataLength); glReadPixels(0, 0, 320, 480, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // gl renders "upside down" so swap top to bottom into new array. // there's gotta be a better way, but this works. GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); for(int y = 0; y <480; y++) { for(int x = 0; x <320 * 4; x++) { buffer2[(479 - y) * 320 * 4 + x] = buffer[y * 4 * 320 + x]; } } // make data provider with data. CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL); // prep the ingredients int bitsPerComponent = 8; int bitsPerPixel = 32; int bytesPerRow = 4 * 320; CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; // make the cgimage CGImageRef imageRef = CGImageCreate(320, 480, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); // then make the uiimage from that UIImage *myImage = [UIImage imageWithCGImage:imageRef]; return myImage; } - (void)saveGLScreenshotToPhotosAlbum { UIImageWriteToSavedPhotosAlbum([self getGLScreenshot], nil, nil, nil); }












