Game Center Achievement Notification

Game Center has a notification window that slides down and informs the GKLocalPlayer that they've been authenticated. There is no built-in way to display achievements that the user may have earned during use of your application. The GKAchievementNotification classes are a way to display achievements awarded to the player in the same manner as the authenticated message—similar to Xbox Live style achievement dialogs. The achievement dialogs are added to the UIWindow view of your application:


Achievement notifications in the upcoming Cee-lo 1.1 update.

Using It

You can grab the code from github. Add the folder (.h, .m, images) to your Xcode project. The GKAchievementHandler class handles the display of the notifications. You'll primarily use that (there'd usually be no reason to create a notification directly). When your player earns an achievement, you can notify them of this via GKAchievementHandler:

// grab an achievement description from where ever you saved them
GKAchievementDescription *achievement = [[GKAchievementDescription alloc] init];

// notify the user
[[GKAchievementHandler defaultHandler] notifyAchievement:achievement];

You can also use custom messages instead of a GKAchievementDescription object:

[[GKAchievementHandler defaultHandler] notifyAchievementTitle:@"High Roller" andMessage:@"Earned 100 points online."];

Customization

Apples Guidelines state that "it is up to you to do so in a way that fits the style of your game". and Allan Schaffer of Apple stated in the forums that "[the] best way to do that would be to present a custom dialog using the look and feel of your game" (emphasis, his). This to me means you may be rejected for using Apple's artwork in a custom application. If this worries you, use the setImage: methods to change the logo displayed in the dialog or change the gk-icon.png images in your images. You can also set the image to nil to not show any image:

[[GKAchievementHandler defaultHandler] setImage:nil];

You can also edit the gk-notification.png images to change the stretchable background.

October 1st, 2010 | Permalink

Getting UILabel's Text Height with adjustsFontSizeToFitWidth

Working on the 1.1 release of Cee-lo today I ran into a task where I needed to position a button underneath a text field. I wanted said button to be positioned relative to the height of the text field. In Actionscript, this task is easy:

theButton.y = theTextField.y + theTextField.textHeight;

+1 abstracted language, no? In objective-c/iOS SDK, the task is slightly more complex. Especially when the UILabel's font size is set to auto adjust (adjustsFontSizeToFitWidth) based on the width of the text field. Here's the working solution below. In this example, the username field on Cee-lo's winner screen is set and the rematch button is positioned under the UILabel based on the text height of the username field. The max font size for the field is 36 and the minimum is 12.

- (void)setUsernameText:(NSString *)theText
{
    // assign the text to the username field
    self.username.text = theText;

    // actualFontSize accepts a pointer to a CGFloat, so define that here
    CGFloat fontSize;

    // determine the size of the field if we were to show theText
    // in it at the original size
    CGSize size = [theText sizeWithFont:self.username.font 
                            minFontSize:12.0f 
                         actualFontSize:&fontSize 
                               forWidth:self.username.frame.size.width
                          lineBreakMode:self.username.lineBreakMode];
    // so at this point, size is a frame that defines the bounds
    // of the textfield if it were rendered at 36 pt. But now, by reference, fontSize
    // contains the actual size of the text field's font after resizing. 
    // So we use that size to determine how big the field would be
    // with that font size:
    CGSize computed = [theText sizeWithFont:[UIFont fontWithName:HelveticaNeueBold size:fontSize]];

    // position the rematch button under the text field
    self.rematch.frame = CGRectMake(20.0f, self.username.frame.origin.y + computed.height + 5, self.rematch.frame.size.width, self.rematch.frame.size.height);
}
September 19th, 2010 | Permalink

Street Ceelo: Fun Times with the iPhone SDK

Do you follow me on Twitter? You don't? Well you should! If you do though you know how much pain the iPhone SDK has brought me in the last three or four months. I'm very close to being done with a very cool app, but some Apple bugs are making it quite difficult to get it done. Care to read my story and learn about the iPhone application I'm making? Okay...

ceelo1 ceelo3

The Street Ceelo App

I'm working on a dice game – a street dice game called Ceelo. The application itself is essentially a 3D dice rolling simulation. You can play with one phone, passing it back and forth, or you can connect using iOS's GameKit over bluetooth or wi-fi and play with multiple "iDevices". It features custom UI design and elements, networking, peer to peer gameplay, animation, navigation and tab controllers...basically "the works" of the iOS kit. I used this application as a learning exercise and it's turned into an application I'm very proud of.

Pretty cool huh? Well, it was cool until iOS 4.0 came out. You see, 4.0 introduced some new classes called GameCenter that expand on GameKit. It also broke the hell out of the original GameKit classes. So my application runs "flawlessly" (it's essentially complete) on devices running the iOS versions 3.0–3.1.3, but on 4.0.X there are numerous issues with the GameKit in Apple's underlying SDK and in certain places it crashes. This is not acceptable to me so I am working to rewrite much of the code and some of the user experience itself to accommodate the issues.

The Broken

So, what broke when 4.0 was released? Here's an example. In Ceelo, there's a custom peer to peer table display. One player (server) says they are going to start a game and others (clients) join her. Now, if a client quits at any time, a "message" is send to their peers that they disconnected. No problem; we simply remove them from the list of players (if in the game lobby) or the game ends. This works fantastically in 3.x iOS. Now, in 4.0.x, instead of a quick notification, there's a lengthy delay when a player disconnects or cancels a connection request. When the message is finally "sent" (it isn't really), it crashes the server:

Thread 3 Crashed:
0   GameKitServices                 0x06352f90 gckSessionChangeStateCList + 411
1   GameKitServices                 0x0635b49c gckSessionRecvProc + 1474
2   libSystem.B.dylib               0x981c181d _pthread_start + 345
3   libSystem.B.dylib               0x981c16a2 thread_start + 34

For more information on this crash check out my StackOverflow question on the topic or poke into the developer forums. Radar IDs for these issues include 8095838 & 8203566, 8174249 & 8094858.

So I filed a bug with Apple and it's been confirmed as a "known issue". The great thing is that the recent betas of the SDK fix the crashing. Unfortunately the long delays remain. I'm continuing to file a bug with each release of the beta. I'm really shocked that this hasn't been addressed yet as the 4.1 release is supposed to be the GameCenter/GameKit release. Classic Apple though to release broken product to get it to market. It is what it is and I've learned to be patient with them in becoming an "Objective-C developer". Also, from what I understand, the 3.0 release was very much the same way.

The Fixes

At this point I'm changing some of the connection logic to remove some of the opportunities for players to quit the game. I've also changed the peer to peer game selection interface to just auto-accept incoming connection requests. This was thanks to a brilliant suggestion by Avi Itskovich who diagnosed a similar GameKit bug via my Stackoverflow post on the topic. It seems to work much better if you connect people up first.

If the game ever gets to the store, it'll be interesting to see how I'll word the required iOS version. You see, this game will work on *all* iDevices, even those without Bluetooth (I wrote it so there's wifi fallbacks). It works on iPhone, 3G, 3GS, Touches, etc. From iOS 3.0–3.1.3. It'll probably run very well on iPhone4 with 4.1 when that is released (already upscaled all the graphics for the retina display!). The only OS it won't run well on is 4.0. How should I word that? "We don't support 4.0 at all but everything else is grand". Just seems odd to have support for everything all the way back to 3.0, but here in the middle is this turd. Such is life.

That's Nice...

So what's the point of all this? Well, I'm worried that GameKit may be phased out. It doesn't seem like Apple is putting priority on fixing it and is focusing more on the Xbox Live-like GameCenter (which obviously is much more lucrative prospect than peer-to-peer gaming). So, I wanted to share my experience as an introduction to Typeoneerror Studios offering iOS development one of our new services. As frustrating as this has been, I absolutely love the Apple development kits, tools and the overall process.

Want to beta test at some point?

Send an email to ceelo at typeonerror with your device ID. How do you get that? Use this fantastic video that Sam Vermette hosts at http://whatsmyudid.com/.

August 24th, 2010 | Permalink

Objective-C: Properties and Instance Vars Gotcha

Learned an important lesson today regarding properties and instance variables (ivars) in Objective-C today. For example, consider the following interface and implementation:

@interface MyButton : UIButton
{
    NSString *label;
}

@property (nonatomic, copy) NSString *label;

@end

@implementation MyButton

@synthesize label;

@end

Now, inside my button class if I reference label:

label = @"Click Me!";

This directly accesses the ivar (NSString *label) and not the @property. To access the property, you would use self:

self.label = @"Click Me!";

The important thing to note here is that if you access the ivar directly, you are bypassing the getters/setters created by the synthesized property. I was having issues where variables I created were not being retained, and adding "self." solved it, leading me to this realization that I wasn't actually accessing the property. My new style for reminding me when I'm accessing an ivar or property is my old tried-and-true method for differentiating private vars in Actionscript or PHP; the underscore:

@interface MyButton : UIButton
{
    NSString *_label;
}

@property (nonatomic, copy) NSString *label;

@end

@implementation MyButton

@synthesize label=_label;

@end

Check the difference above. I changed the synthesize statement to point to a differently named instance var. So when I access "self.label," it will modify _label through the getter/setter. You can also use _label inside the instance to directly access the ivar.

April 17th, 2010 | Permalink

Flickring UIButton State Issue

Ran into a very specific "bug" in my iPhone application today. We have on our about page of the application two images for the highlighted and normal states of a button. It works as expected when you "press" and then "touch up" at a slow pace, but if you click/tap it quickly, there's a noticeable flicker between states. Here's the code inside the subclass of UIButton that creates the buttons:

UIImage *normalImage = [[UIImage imageNamed:@"btn-small.png"] stretchableImageWithLeftCapWidth:10.0f topCapHeight:0.0f];
UIImage *highlightedImage = [[UIImage imageNamed:@"btn-small-down.png"] stretchableImageWithLeftCapWidth:10.0f topCapHeight:0.0f];

[self setBackgroundImage:normalImage forState:UIControlStateNormal];
[self setBackgroundImage:highlightedImage forState:UIControlStateDisabled];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];

[self setAdjustsImageWhenDisabled:FALSE];
[self setAdjustsImageWhenHighlighted:FALSE];

When a button is tapped it simply disables itself and enables the other button:

- (IBAction)aboutButtonTouched:(id)sender
{
    aboutButton.enabled = FALSE;
    rulesButton.enabled = TRUE;
}

- (IBAction)rulesButtonTouched:(id)sender
{
    rulesButton.enabled = FALSE;
    aboutButton.enabled = TRUE;
}

This works fine, but there's a noticable "flicker" when tapping the button quickly (and not a delicate press, hold, and release). The solution to this flickering took a bit of reverse engineering. The first thing I did was modify the aboutButtonTouched method to log the button's state property which is a bit-mask NSUInteger:

- (IBAction)aboutButtonTouched:(id)sender
{
    rulesButton.enabled = TRUE;
    aboutButton.enabled = FALSE;    
    
    NSLog(@"%d", [sender state]);
}

At this point, the button is disabled through setEnabled, and the log reported that the state was "3". Looking at the bit-mask type for UIControlState (Comments added since I can never remember bitwise):

enum {
   UIControlStateNormal               = 0,            // 0
   UIControlStateHighlighted          = 1 << 0,       // 1
   UIControlStateDisabled             = 1 << 1,       // 2
   UIControlStateSelected             = 1 << 2,       // 4
   UIControlStateApplication          = 0x00FF0000,
   UIControlStateReserved             = 0xFF000000
};

We can see that to get "3" (0011) we should use UIControlStateHighlighted | UIControlStateDisabled (0001|0010 or 1|2), something which I did not have as a state in my original button definition. The key here that there's a brief time when the state is both before only being disabled ("A control enters this state when a touch enters and exits during tracking and and when there is a touch up" -- from the docs). So the final state settings for the button where it does not flicker are:

[self setBackgroundImage:normalImage forState:UIControlStateNormal];
[self setBackgroundImage:highlightedImage forState:UIControlStateDisabled];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];
[self setBackgroundImage:highlightedImage forState:UIControlStateHighlighted|UIControlStateDisabled];
April 4th, 2010 | Permalink