Skip to content

CommentCast Review

FeatureImg

One of the daily routines of developers and publishers is checking the AppStore for the latest ratings and reviews for their products. There are dozens of tools and websites around to support collecting that information, one of which is CommentCast for MAC OS X by Tyler Bindon. And it’s a bit different.

I found CommentCast in search of one tool that also takes ratings seriously. Most tools that I used before had a focus on reviews only. They collected them and notified me, made nice presentations of those new reviews coming up around the world…but they always ignored new ratings being awarded, i.e. reviews without a text, so to say. If you remembered the numbers from the past, you could do the math on your own, but most often you just missed it when new ratings where given somewhere around the world. With CommentCast, this won’t happen again!

Get Started

Selection

You start with selecting the products you want to monitor. This is not just software (iOS & MAC AppStore), but can be any media item from iTunes, like also music, films etc. You can enter a search term, an app ID or AppStore URL and will get a nicely ranked and tagged result list to choose what you want. That result is then added to the list of monitored products, which can be further categorized into subfolders, if you want.

With one click you can now read all worldwide reviews and ratings for a selected or simply all monitored products. When all stores around the world were checked, you get a comprehensive overview about the results in the main window of CommentCast.

Main

With some simple clicks you can now apply filters, e.g. just see values pertaining to the current version  of an app only, filter for number of stars, just show those added after the last poll or simply search within all of the shown. A green badge next to an entry in the app list shows you the number of changes since the last retrieval right away.

ReviewDeltaThe big value over other solutions is that CommentCast also considers simple ratings that don’t come with a review text (usually even the majority of feedback that you get). You get an excellent overview about how many stars you got since the last time you asked for it.

If you simply don’t get what one of your Japanese customers wrote, an integrated translation feature provides a rudimentary translation to a desired target language (Japanese to English translations are a fun entertainment on its own, BTW ;-) …)

On the top right of the main screen, a small worldmap indicates the distribution of reviews/ratings around the world. Clicking on it reveals a large view of the map, including an explanation of the color coding.

DistributionMap

Export

CommentCast allows you to export the filtered result list in a configurable HTML format, or simply as a CSV list for export into spreadsheets or other further processing. The HTML configuration is done directly in a template page with some dedicated tags for looping over the content and inserting individual result attributes.

Export

The export has to be done manually, since CommentCast does not yet support Apple script automation. Furthermore, you can not do programmatic filtering or conditional processing within that template, so the usecase for the export is limited. The developer is however looking into extending the functionality in theses areas. I found that feature handy anyway to provide a nice overview of selected reviews to others.

Conclusion

At $5 to be paid through PayPal, CommentCast is a bargain to buy and a must-have for every app developer. The additional consideration of ratings in addition to reviews is an outstanding feature that separates CommentCast from all it’s online and offline competitors. By now I couldn’t live without it any longer and use it at least twice a day! It will get the perfect final touch once the developer adds automation support.

You can download it from the CommentCast homepage.

Café International

The all-time classic board game Café International (Game of the Year 1989!) is finally coming to your iPhone, iPod and iPad. Enjoy an incredibly addictive gameplay, either alone in two challenging solitary modes, or as multiplayer game against friends and computer players. The unique funny original graphics (of course fully supporting iPad retina) and an awesome gameplay and handling will provide a great atmosphere and countless hours of fun and challenges.

Get it right here on the AppStore or watch the product trailer video:

SpriteSheets — The Movie

Initially I just wanted to provide original content in my blog and provide stuff that I couldn’t find elsewhere (or at least not the way I wanted or needed it), but this one is so cool that you should also be able to watch it if you browse my blog, courtesy of Andreas Loew of Code’n'Web, who provides the excellent and absolutely essential tool Texture Packer and Physics Editor (both of which I heavily use and recommend)!.

If you do for example Cocos2D programming, this is what you need to know about sprite sheets!

A Magnifying Glass for Cocos2D

Magnifier
UPDATED VERSION 12/5/2012

During development of our new iOS game, using Cocos2D, I needed a magnifying glass that you could move over the screen and enlarge the portions below it. I did something similar before with UIKit, and that wasn’t a big deal at all, however with OpenGL it is a different story. I experimented with CCLens3D effects, but wasn’t satisfied with it. Surfing around didn’t reveal much, except that there a) is a need, and b) no real complete solution around. At the end of this blog, you can download everything you need for a great magnifying glass Cocos2D solution!

Based on how to grab a screenshot to get the data that is currently shown (discussed e.g. here in the Cocos2D forum), I came up with a solution to simply take the UIKit technique I knew already: grab the data (in this case from the OpenGL buffer, but just the piece that I need), redraw it enlarged into an UIView (depending on a variable magnification scale) and then let that view hover on top of the OpenGL view. This solves at the same time the issue that any loupe graphics you use in Cocos2D would be part of the contents of the current OpenGL buffer and be enlarged as well, ending up with a funny mirror-in-mirror effect.

The UIView will be wrapped by a CCNode-derived class, so you can use it like other nodes within your scene. The solution is surprisingly lean and easy to use, does not eat much of your precious memory and is fast. In contrast to the well-known textedit loupe, I chose to not enlarge the area below your finger, but that below the center of the loupe. This turned out to be much more intuitive for my use-case, but of course you can do it differently by simply changing the grabbed rectangle coordinates.

The loupe will hover quite a bit above your finger, which is determined by setting the anchor position in the initialization. You can choose a different setting here, or make it even dynamic by adapting the anchor in setPosition (so the loupe will move around your finger when approaching the lower screen border — pretty nice effect).
For the loupe frame itself you can create whatever graphics you like. My example provides a very thin and simple rounded frame with a little 3D effect and shadow, but you can design whatever you like. For simplicity I chose a centered inner area, which makes rendering the magnified area very easy. If you need it, however, you can also go for a masked approach and mask out the inner part by providing an according masking image (and the required coding for it, of course. Ray Wenderlich provides some great samples for masking in his blog!).

Update 5/12/2012: For Cocos2D V2.0 it is no longer required to explicitly rotate the contents according to device orientation. The coding to download below  now checks the Cocos-Version and omits the rotation now automatically.

If you create your own loupe image with different proportions, you need to change the percentage of the inner part. You can compute that simply by dividing the diameter of the inner part by the total side-length of your image. Included you’ll find all required graphics for iPhone & iPad, ready & tested for standard and retina resolutions. I chose to have a relatively larger loupe for the iPhone/iPod, since magnification was even more required here than on the iPad, but it’s up to you what size of graphics you provide.

Once you imported the Magnifier class and the images into your project, you set the loupe up like that somewhere in your scene:

  Magnifier *magnifier = [[[Magnifier alloc] initWithMagnification: 2.0] autorelease];
  magnifier.position = <some position, e.g. touchLocation in ccTouchesBegan/Moved>;
  [self addChild:magnifier];

and there you go. Whenever you change the position, the magnified area will be updated as well automatically. If you want the magnified content to change dynamically also while you don’t move the loupe, you need to call the magnify method either periodically from a timer method, or at points in time where you know that content has changed. From my experience, though, having it updated on position changes is completely sufficient.

Update: Fix for iOS6 and above

There is nothing else to do for all iOS versions up to iOS 5.1. For newer versions of iOS in the future, however, you will need to tweak the way OpenGL handles graphics buffering, otherwise you will see a unicolor area only in the loupe. Fortunately, this is pretty easy to do: just replace this standard EAGLView initialization in your app delegate:

EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
	                       pixelFormat:kEAGLColorFormatRGBA8 // or whatever you use...
			       depthFormat:0];

with this one to make sure that OpenGL will retain it’s backbuffers:

EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
                               pixelFormat:kEAGLColorFormatRGBA8 // or whatever you use...
                               depthFormat:0
                        preserveBackbuffer:YES
                                sharegroup:nil
                             multiSampling:NO
                           numberOfSamples:0];

The Beef

The loupe graphics and Magnifier class ready for consumption can be downloaded here.

Prevent blurry fonts when using CCLabelTTF

Note: this in an updated blog (August 11th, 2012) to the original version, including a new download!

When using CCLabelTTF to use True Type fonts with Cocos2D, you might have noticed that they sometimes look pretty blurry, while in other places they appear reasonably sharp. What’s wrong here?

The rendering of the fonts tries to strictly consider the exact position of a font, which is given as a floating point number. If this position is not exactly hitting an actual pixel on the screen, the rendering is adjusted accordingly by introducing additional anti-aliasing, which makes the font appear to start exactly between two pixels. In theory. In reality, it simply looks blurry now, and I am sure in favor of a sharp font you would have rather adjusted a correct 0.3 pixel offset to the nearest pixel instead.

Fortunately, it is really not a big deal to do exactly this. We just need to derive a new class from CCLabelTTF and check (and possibly correct) the top left position each time it might change. Considering the complete node structure we are embedded in, we need to round the top left corner coordinates (in world/screen space) to a full pixel, and then recompute the position all the way back to our local coordinate system and fix the position parameter accordingly. Why the top left corner? Because the string is ultimately converted to a Cocos2D texture using the core NSString drawInRect method, which is based on the UIKit coordinate system and has its (0,0) coordinates always top left (rather than Cocos2D bottom left origin).
To make sure that rounding errors don’t sum up and make your label move uncontrolled, we always remember the originally intended position and adjust always referring to that position.

As a reward for reading this blog, you can simply download a ready-made class SharpLabelTTF right away! You can use it instead of CCLabelTTF and integrate it exactly like that. You don’t need to consider anything else.

Using CCLabelTTF:

Using SharpLabelTTF:

And here is the beef:

@implementation SharpLabelTTF
- (void) fixPosition {

    CGSize dim = self.texture.contentSize;    
    
    [super setPosition:intendedPosition_];
    if (scaleX_ < 0.3 || scaleY_ < 0.3) return;

    // compute world (= screen) coordinate of top left position of label  
    CGPoint worldSpaceTopleft = [self convertToWorldSpace: ccp(0,dim.height)];

    // align origin on a pixel boundary on screen coordinates
    worldSpaceTopleft.x = roundf(worldSpaceTopleft.x * CC_CONTENT_SCALE_FACTOR()) / CC_CONTENT_SCALE_FACTOR();
    worldSpaceTopleft.y = roundf(worldSpaceTopleft.y * CC_CONTENT_SCALE_FACTOR()) / CC_CONTENT_SCALE_FACTOR();

    // transform back into node space
    CGPoint nodeSpaceTopleft = [self convertToNodeSpace:worldSpaceTopleft];

    // adjust position by the computed delta
    CGPoint delta = ccpSub(nodeSpaceTopleft, ccp(0,dim.height));
    CGPoint newPos = ccpAdd(position_, delta);

    // finally set the position data
    [super setPosition:newPos];      
}

// capture modification calls to adjust position
- (void)onEnter {
    [self fixPosition];
    [super onEnter];
}

- (void)setParent:(CCNode *)parent {
    [super setParent:parent];
    [self fixPosition];
}

- (void)setString:(NSString *)str {
    [super setString:str];
    [self fixPosition];
}

- (void)setPosition:(CGPoint)position {
    intendedPosition_ = position;
    [self fixPosition];
}

-(void)setRotation:(float)rotation {
    [super setRotation:rotation];
    [self fixPosition];
}

@end

Automatically Create Icons up to iPad 3 Retina Resolution

With the latest iPad 3 … oh — excuse me — the New iPad, there is a bunch of new icons you need to create if you want to support the mega-retina resolution of the iPad 3 (call me old-school…I won’t stick to New iPad). There is probably tons of artwork you need to fix, and nobody can help you with that. But here is a little support for you to at least have your icons correctly in place.

If you are developing an app for the iPhone/iPod or the iPad, or both as universal app, there is the Apple bible for what you need to provide as icon formats:

  • App icon for the iPhone in 57×57 px
  • App icon for the iPad in 72×72 px
  • Settings & search result icon for the iPhone/iPod/iPad in 29×29 px
  • Search result icon for the iPad in 50×50 px

For retina displays, you need to provide each of them with the quadruple resolution as @2x variants (well, the last two are somewhat optional, but since we are automating it anyway now…). So you end up with 8 icon files. What you probably usually do is take your large master artwork and then save all the required resolutions. This is quite a tedious hassle, since for the sake of supporting older devices you should stick to certain naming conventions, and doing all the resizing and renaming manually can be pretty error prone.

With the newly required iPad 3 retina files, I didn’t want to go the manual way again and created a small Automator action that takes my large 512×512 master artwork as input and creates all required files from it. Just drag your input file to the IconCreator.app automation tool, and it will create a folder Icons next to it containing all the required files, plus a 512×512 iTunesArtwork file (note: no extension!) that is required if you distribute IPA-betas to testers, so that they don’t just see white squares for your app in iTunes. The input can actually be larger or smaller than 512×512 px, but smaller is not recommended as at least the iTunesArtwork will get pixelated then. If you don’t drag a file to the droplet, but start it directly, a file-selector will ask you to select the input graphics.

You can download the automation task right here.

After adding the resulting icon files to your project, the only thing that is then still left to be done is adding them to your CFBundleIcons key in the info.plist file. Just copy the following entries and add them to your info.plist using a regular text editor. You can just insert it at an appropriate position between any two other entries (i.e. right before any existing <key>…</key> entry):

  <key>CFBundleIconFiles</key>
  <array>
    <string>Icon.png</string>
    <string>Icon@2x.png</string>
    <string>Icon-72.png</string>
    <string>Icon-72@2x.png</string>
    <string>Icon-Small-50.png</string>
    <string>Icon-Small-50@2x.png</string>
    <string>Icon-Small.png</string>
    <string>Icon-Small@2x.png</string>
  </array>

Modal Alerts for Cocos2D

dialogboxScreenshot

Cocos2D is insanely great for game development, and a lot of otherwise complex or tedious gaming tasks are almost no-brainers here. The one thing, however, that I found less straightforward, is bringing up small alerts to ask for some user confirmation, while at the same time blocking the rest of the application.

There are actually tons of bits and pieces around that solve the one or the other part of it, but no one-shot solution as I wanted it to be. What I was looking for is:

  • Small alert-box with custom design fitting to the overall game look-and-feel
  • Alike support for iPad and iPhone, incl. retina
  • Predefined “Ask”, “Confirm” and “Tell” type of alerts that can be easily called from within the code
  • Modal operation, i.e. lock and dim the game scene and ideally have a no-hassle modal kind of control-flow within the calling code without the need for additional callbacks that make the code hard to read from there on
  • Fire-and-forget operation without need for any further cleanup
  • Nice animations

So what I finally came up with is actually a bit more powerful and complex than what I want to present here. But this stripped-down version is a great start to do your own further customizations, and it is fully functional and can be integrated right away. You just ask for the dialog to be brought up with your own message, and you’re done. Code that should be executed when pressing a button is written inline as a block, thus being kind of a modal control-flow schema. And if you want code to be executed right away, just put it directly after the statement itself. Asking the user for a simple confirmation is as easy as this:

[ModalAlert Ask: @"Do you want to cancel the game?"
        onLayer:self // this is usually the current scene layer
       yesBlock: ^{
           // code that should be executed on "yes" goes here 
           ...
           }
        noBlock: ^{
           // code that should be executed on "no" goes here
           ...
           }];
// control flow proceeds here right away (non-modal) ...

That’s all. Everything is encapsulated into simple class-functions and requires no allocations or anything on the caller side. There is a predefined call for a Yes/No question, an OK/Cancel confirmation and a Tell-function that is just to be confirmed with an OK-button.

In principle a semi-transparent black color layer is created that covers the whole current scene and swallows all touches, except those that should go to the dialog sprite and the buttons on it. The dialogbox itself and the button are images that fit for your purpose, and you can design them any way you like (color and size). For this sample, I provided three different sizes for iPhone retina and non-retina, and an extra one for the iPad. The non-retina size would be too small for the iPad, and HD resolution too large and would look clumsy. So from the HD retina resolution, the iPad is sized 70% of it, and the SD non-retina is as usual 50%. If you change the size you might also need to tweak some of the sizings, however they are relative to the dialog box size and should fit at least as long as you approximately retain the height/width relation.

Beside the fact that you now have a fully functional base to start from for the ultimate and universal alerting library, you can take away some interesting things from the code, if you are new to Cocos2D or iPhone programming in general, like animations and block programming (which BTW is only available starting with iOS 4).

Where could you go from here? Include localization for the predefined buttons, provide more formats, provide a function where you can also customize the button texts, center the message text vertically, draw all boxes dynamically using OpenGL (instead of having to provide images), include a nice image in the dialog box to make it more friendly, etc…there is tons you could elaborate on. But probably you won’t even need much more.

So here is the coding of the ModalAlert class. I leave out the *.h file here, because it is pretty unspectacular. All the code files and the required images for the sample can be downloaded as ZIP file to get you jump-started.

 
// ModalAlert.m
// 
// Created by Hans-Juergen Richstein
// Copyright 2012 ROMBOS. All rights reserved. 

#import "ModalAlert.h"
#import "cocos2d.h"

// constants
#define kDialogTag 1234
#define kAnimationTime 0.4f
#define kDialogImg @"dialogBox.png"
#define kButtonImg @"dialogButton.png"
#define kFontName @"MarkerFelt-Thin"

// class that implements a black colored layer that will cover the whole screen 
// and eats all touches except within the dialog box child 
@interface CoverLayer : CCLayerColor {
}
@end
@implementation CoverLayer
- (id)init {
    self = [super init];
    if (self) {
        [self initWithColor:ccc4(0,0,0,0)
                      width:[CCDirector sharedDirector].winSize.width
                     height:[CCDirector sharedDirector].winSize.height];
        self.isTouchEnabled = YES;
    }
    return self;
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint touchLocation = [self convertTouchToNodeSpace: touch];
    CCNode *dialogBox = [self getChildByTag: kDialogTag];

    // eat all touches outside of dialog box
    return !CGRectContainsPoint(dialogBox.boundingBox, touchLocation);
}

- (void) registerWithTouchDispatcher {
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
}
@end

// the actual ModalAlert implemenation class
@implementation ModalAlert

// this is called to gracefully close the alert, cleanup everything
// and call the according block belonging to the pressed button
+ (void) CloseAlert: (CCSprite*) alertDialog onCoverLayer: (CCLayer*) coverLayer executingBlock: (void(^)())block {
    // shrink dialog box 
    [alertDialog runAction:[CCScaleTo actionWithDuration:kAnimationTime scale:0]];

    // in parallel, fadeout and remove cover layer and execute block 
    // (note: you can't use CCFadeOut since we don't start at opacity 1!) 
    [coverLayer runAction:[CCSequence actions:
                           [CCFadeTo actionWithDuration:kAnimationTime opacity:0],
                           [CCCallBlock actionWithBlock:^{
                                [coverLayer removeFromParentAndCleanup:YES];
                                if (block) block();
                            }],
                           nil]];
}

+ (void) ShowAlert: (NSString*) message onLayer: (CCLayer *) layer
          withOpt1: (NSString*) opt1 withOpt1Block: (void(^)())opt1Block
           andOpt2: (NSString*) opt2 withOpt2Block: (void(^)())opt2Block {

    // create the cover layer that "hides" the current application     
    CCLayerColor *coverLayer = [CoverLayer new];
    [layer addChild:coverLayer z:INT_MAX]; // put to the very top to block application touches
    [coverLayer runAction:[CCFadeTo actionWithDuration:kAnimationTime opacity:80]]; // smooth fade-in to dim with semi-transparency

    // open the dialog 
    CCSprite *dialog = [CCSprite spriteWithFile:kDialogImg];
    dialog.tag = kDialogTag;
    dialog.position = ccp(coverLayer.contentSize.width/2, coverLayer.contentSize.height/2);
    dialog.opacity = 220; // make it a bit transparent for a cooler look

    // add the alert text
    CGSize msgSize = CGSizeMake(dialog.contentSize.width * 0.9, dialog.contentSize.height * 0.55);
    float fontSize = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)?42:30;

    CCLabelTTF *dialogMsg = [CCLabelTTF labelWithString:message dimensions: msgSize alignment:UITextAlignmentCenter
                                               fontName:kFontName fontSize:fontSize];
    dialogMsg.position = ccp(dialog.contentSize.width/2, dialog.contentSize.height * 0.6);
    dialogMsg.color = ccBLACK;
    [dialog addChild: dialogMsg]; 

    // add one or two buttons, as needed
    CCMenuItemSprite *opt1Button = [CCMenuItemSprite itemFromNormalSprite:[CCSprite spriteWithFile: kButtonImg]
                                                             selectedSprite:[CCSprite spriteWithFile: kButtonImg]
                                                                    block:^(id sender){
                                                                        // close alert and call opt1block when first button is pressed                                                                         [self CloseAlert:dialog onCoverLayer: coverLayer executingBlock:opt1Block];
                                                                    } ];
    opt1Button.position = ccp(dialog.textureRect.size.width * (opt2 ? 0.27f:0.5f), opt1Button.contentSize.height * 0.8f);

    CCLabelTTF *opt1Label = [CCLabelTTF labelWithString:opt1 dimensions:opt1Button.contentSize alignment:UITextAlignmentCenter
                                               fontName:kFontName fontSize:fontSize];
    opt1Label.anchorPoint = ccp(0, 0.1);
    opt1Label.color = ccBLACK;
    [opt1Button addChild: opt1Label];    

    // create second button, if requested
    CCMenuItemSprite *opt2Button = nil;
    if (opt2) {
        opt2Button = [CCMenuItemSprite itemFromNormalSprite:[CCSprite spriteWithFile: kButtonImg]
                                             selectedSprite:[CCSprite spriteWithFile: kButtonImg]
                                                      block:^(id sender){
                                                          // close alert and call opt2block when second button is pressed
                                                          [self CloseAlert:dialog onCoverLayer: coverLayer executingBlock:opt2Block];
                                                      } ];

        opt2Button.position = ccp(dialog.textureRect.size.width * 0.73f, opt1Button.contentSize.height * 0.8f);

        CCLabelTTF *opt2Label = [CCLabelTTF labelWithString:opt2 dimensions:opt2Button.contentSize alignment:UITextAlignmentCenter
                                                   fontName:kFontName fontSize:fontSize];
        opt2Label.anchorPoint = ccp(0, 0.1);
        opt2Label.color = ccBLACK;
        [opt2Button addChild: opt2Label];
    }

    // add the menu to the dialog
    CCMenu *menu = [CCMenu menuWithItems:opt1Button, opt2Button, nil];
    menu.position = CGPointZero;

    [dialog addChild:menu];
    [coverLayer addChild:dialog];

    // open the dialog with a nice popup-effect 
    dialog.scale = 0;
    [dialog runAction:[CCEaseBackOut actionWithAction:[CCScaleTo actionWithDuration:kAnimationTime scale:1.0]]];
}

+ (void) Ask: (NSString *) question onLayer: (CCLayer *) layer yesBlock: (void(^)())yesBlock noBlock: (void(^)())noBlock {
    [self ShowAlert:question onLayer:layer withOpt1:@"Yes" withOpt1Block:yesBlock andOpt2:@"No" withOpt2Block:noBlock];
}

+ (void) Confirm: (NSString *) question onLayer: (CCLayer *) layer okBlock: (void(^)())okBlock cancelBlock: (void(^)())cancelBlock {
    [self ShowAlert:question onLayer:layer withOpt1:@"Ok" withOpt1Block: okBlock andOpt2:@"Cancel" withOpt2Block:cancelBlock];
}

+ (void) Tell: (NSString *) statement onLayer: (CCLayer *) layer okBlock: (void(^)())okBlock {
    [self ShowAlert:statement onLayer:layer withOpt1:@"Ok" withOpt1Block: okBlock andOpt2:nil withOpt2Block:nil];
}

@end
Follow

Get every new post delivered to your Inbox.