OK, don't make things free...

by jegeblad

I decided to see if I could get more people to play Eyestorm if I made it free. You would expect that the more people who play, the more they recommend it to friends, etc.

Well it turns out that was a super bad idea. Eyestorm went from having a 4.5 star rating to 3 in less than 12 hours. Obviously people like free, but they don't value it very much. We got some extremely good reviews of Eyestorm; For instance this one at the Portable Gamer: http://theportablegamer.com/2009/08/eyestorm-review/.

I still don't understand Apple's rating system where you just give an app stars without a reason. I think that if you have time to give stars but not write 10 words about what you think, you haven't really given the app the time it deserves. Not a single one of those people who got the app for free wrote a review.

...Maybe I should raise the price to $59.99.

Update: Numbers

In case anyone wants some numbers. Eyestorm was downloaded 1500 times in the less than 12 hours it was free. Eyestorm Lite was downloaded 236 times the same day (so 24 hours). Eyestorm is usually bought less than 20 times per week and has not been bought more than 250 times in total. That should give you some idea of how popular "free" is. Amazingly, 9 people actually bought Eyestorm after I raised the price yesterday.

Update 2: More numbers

  • The week of the giveaway 1579 Eyestorms were downloaded for free
  • The week of the giveaway 23 Eyestorms were bought
  • The week prior to the giveaway only 6 Eyestorms were bought

Did it help to put in on discount? Well we sold 17 (almost 300%) during the week despite giving it away for free. Did we make a lot of money: "NO!".

  • Before the giveaway Eyestorm had an average rating in the US store of 4.5
  • 1 week after the giveaway Eyestorm has an average rating of 3.1

We did get a couple of new reviews on the iTunes store. Interestingly enough, we did not get a single review with less than a 5 star rating. So people don't mind writing positive reviews, but they hate explaining why they give an app 1-4 stars... Same thing applies to Lifecards.

Microsoft nuisance of the day

by jegeblad

As an iPhone developer you would think that I would be more concerned with dealing with limitations in XCode, the iPhone SDK and Apple's review process, but a large portion of my time I work on non-iPhone projects. Today I was sitting with a c-file that I first edited with Textmate. When I tried to compile it with Visual Studio I got the following warning message:

"error C4335: Mac file format detected: please convert the source file to either DOS or UNIX format"

This is really ridiculous. The file looks absolutely fine in the Visual Studio editor, so what's wrong? Microsoft doesn't give a hint as to how it can be converted or what is so Mac-like about the file.

How to solve it? Well I just selected all, copied all to the clipboard, deleted all, and pasted from the clipboard.

I still don't know what was Mac about the file, but it is amazing that Visual Studio's compiler cannot handle it since it can easily handle DOS or Unix file. The encoding looked OK, so the only thing I can think of that distinguished this file is line-endings. Great that we still have that problem today.

BTW: Even though I though a lot of cross development on Windows and Mac I have never experienced this error before.

Ramsay's rules applied to software

by jegeblad

Over the last couple of years I have enjoyed watching the English chef Gordon Ramsay's Kitchen Nightmares. That's the show where he travels all over the world and tries to help restaurants in need.

The formula he applies consists of the following:

  1. Reduce menu. Often the restaurants have in the order of 100 meals to choose from. As a customer that is a hopeless set of options. I was in a pizzaria in Italy once which had more than 150 pizzas to choose from. I ended up spending 25 minutes just looking at the menu, and in the end I was still speculating if I chose the right pizza.
  2. Increase kitchen efficiency. Ramsey does this both by reducing the menu -- A shorter menu will make the kitchen staff's work simpler -- and by looking at the work-processes taking place in the kitchen. I.e. are people communicating enough? Is it clear who has responsibility over what? Are the right people doing the right things? Are people using the right tools (pans instead of oven)?
  3. Get proper produce. A lot of places Ramsey goes to uses frozen and canned produce when they could use fresh. Of course, Ramsey's motivation is that fresh tastes better.
  4. Focus. Often he will suggest that the restaurant specializes in pasta, steaks, fresh seafood, etc. This further helps to reduce the menu.

Although I think the shows have become repetitive I still enjoy watching them because they are often about how people are reluctant to change and how it can be hard to see things properly when you are in the middle of them.

The point here though, is that the exact same formula can be made to consumer software. In that case Ramsey's software rules would be something like:

  1. Reduce feature set. Have a short list of important good sollid features/options rather than a long one. Users prefer to have 10 good features than 100 they never use. This is especially true for iPhone apps which users are not going to spend a long time getting used to. If an app has a lot of features make sure they are easily accessible. Do not make long and confusing menus. Select the right and exciting features.
  2. Increase efficiency. It doesn't make sense to implement complicated features unless customers are going to appreciate them. Do not try to implement features you have no idea how to do properly -- Accept your limits. Also, there are right and wrong ways to implement individual features. Use the right tools for coding, automating tasks, drawing, etc. Identify the right programming language for each task. Use libraries, but be careful that they are the "fresh" and not the "canned/frozen" kind.
  3. Make the proper code. Make sure your program code is clean and avoid making hacks. Just as a customer can sense if a meal is based on quality produce, a user can also feel if an app contains many hacks and is only hold together with bubble gum, or if it is based on a solid foundation.
  4. Focus. Focus on one area. Do not expand from that. Do not expand!!! Good applications that do many things focuses on solid core and let their users extend on it (TextMate has bundles. Photoshop has plugins).

I recently watched one of Ramsey's shows and it struck me that I had not followed those four rules. In fact, I have been doing the exact opposite the last 3 months or so. I opened up Lifecards immediately after the show and it hit me like an express train. With Lifecards there are hundreds of cards to choose from. Not the 10-15 categories with 10-15 in each which would have been optimal. The original reasoning was something like: since I am not a designer, I cannot focus on quality so I have to focus on quantity. I also added features over the summer that I suspect no one uses, e.g. image filters, transparent text, and speech bubbles. I added those because I already added them to Lifestrips (where they make sense), so since the apps share codebase, adding the features to Lifecards happened automatically. Although the foundation code is quite clean, some of the GUI and OpenGL code has become a bit more dirty than it should have been. Voice alarm also suffered a little bit from feature explosion.

It is kind of interesting to look back at because I've known those four golden rules for years. Still, I somehow got carried away and started thinking:"If only I build this feature, the customers will come.", "If I add 100 card templates, they'll come". So now I am trying to figure out how I can apply Ramsey's rules to my current apps, but the hard part isn't realizing what is wrong. The hard part is figuring out how to fix it and fixing it.

I think what confused me was that I started working with computers when features were really important. In the early 90's applications which had a limited set of features really annoyed me. I actually liked applications which lots of features to explore, where I could do exactly as I wanted. However, even back then I hated applications that tried to more than their focus area. Like adding drawing tools to Word for Windows 6.0 -- What a stupid idea. Let users use proper drawing tools for their drawings. I suppose, the trick is to identify which features are important, implement them elegantly so they do not get in the way of the user but the application still feels accessible.

The last of Ramsey's shows I saw contained a segment in which the restaurant owner refused to remove meals from the menu. "People use them", he said. "They expect to be able to order them". Of course the reality was that there were no customers in the restaurant. I understand his view completely, because now that I know I should cut back and clean-up features, I tell myself:"No, I cannot remove that feature. People expect it to be there now." I guess it is much easier to see other people's mistakes than your own.

Messing with PDF files

by jegeblad

A couple of weeks ago I messed about a bit with creating PDF files on the iPhone. Although the iPhone OS includes Apple's excellent drawing kit, Quartz which is built to generate PDF files, I stumbled on several issues, so I thought I would share some of my experiences here.

PDF creation
PDF creation is essentially quite easy. You create a PDF document and CGContext for it. Here PDF output will appear in a CFMutableData, but you can output to a file quite easily as well:

double const pointToMM = 0.35277777778;
double targetW = 145.0/pointToMM, targetH = 90.0/pointToMM;
double rescale = min( targetW/(cDimensionX), targetH/(cDimensionY));
CGRect pageRect = CGRectMake(0,0,targetW,targetH);
CFMutableDataRef data = CFDataCreateMutable(NULL, 0);
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData(data);
CGContextRef pdfContext = CGPDFContextCreate (consumer, &pageRect, NULL);
// Create PDF Page
CGContextBeginPage (pdfContext, &pageRect);
// Draw PDF into pdfContext
// [INSERT CODE HERE]
// end draw PDF
CGContextEndPage (pdfContext);
// Close PDF
CGPDFContextClose(pdfContext);
CGContextRelease (pdfContext);
// Use data
CFRelease(data);

I stumbled on three problems on the iPhone during the drawing phase:

  1. Drawing images
  2. Drawing text
  3. Drawing from other PDF files

Drawing images
It took a while for me to realize it but when I used an UIImage from e.g. a JPEG file it seems like the JPEG source is embedded in the PDF file. That is a good thing because it allows us to control compression of images embedded in PDF files. It is also important if you create your own images. E.g. If a user uses a filter in Lifecards I create my own UIImage with code that looks like this:

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
CGContextRef offscreenContext = CGBitmapContextCreate(filterImageBuffer, width, height, 8, rowBytes, rgbColorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
// Begin drawing code
// [INSERT DRAWING CODE]
// End drawing code
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, filterImageBuffer, rowBytes * height, NULL);
CGImageRef cgImage = CGImageCreate(width, height, 8, 32, rowBytes, rgbColorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big, dataProvider, NULL, false, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
CGColorSpaceRelease(rgbColorSpace);
UIImage * newUIImage = [[UIImage alloc] initWithCGImage:cgImage];
CGImageRelease(cgImage);

This code creates a new UIImage newUIImage which is uncompressed. We can create a UIImage with jpeg compression as source as follows:

// Create compressed
// Note: data here is auto-released
NSData * data = UIImageJPEGRepresentation(newUIImage, 0.95);
UIImage * compressedImage = [[UIImage alloc] initWithData:data];

Then whenever we wish to draw the image in the PDF file we simply draw the compressed image instead of the uncompressed one:

[compressedImage drawInRect:CGRectMake(0,0,width,height)]

This way the jpeg compressed version is used instead and it will greatly reduce the size of your PDF files. Of course, newImage could just be your source image if that is also a UIImage.

Note: One thing which is very important is not to release the compressed image until you finish the PDF page. Quartz needs the images when it finalizes the current PDF page for some reason. It doesn't seem like the image is expanded in memory at that time, but I could be wrong since I used the Leaks application and activity monitor to tell.

I should note that I never did find any documentation on this subject so some of this is speculation but it looks like it works.

Drawing text

The other problem I had was drawing text. I use the well-known NSString extension in UIKit:

drawInRect:withFont:

Whenever I used that I got some errors on the console of the form

can't get CIDs for glyphs for 'ArialMT'.
CGFont/Freetype: The function 'get_subset_format' is currently unimplemented

As far as I can tell those errors are related to the fact that the iPhone is unable to embed fonts in the PDF-files.

At one point I thought that I could get the MacRoman parts (non unicode characters) of fonts embedded with a couple of tricks, but when I returned to look at the code after a couple of days I was unable to reproduce those results. So there may or may not be a way to do it.

Anyway, being a man of the world I was more interested in being able to include unicode text. It works directly using the NSString extension, but it returns those ugly error messages, and the fonts used must be installed in the operating system that you use to view the PDF files. Since most Windows installations does not include Georgia, Marker Felt, Zapfino, etc., you pretty much exclude the use of those fonts.

There is another way though, which requires the use of an undocumented function:

extern "C" bool CGFontGetGlyphsForUnichars(CGFontRef, unichar[], CGGlyph[], size_t);

When I first tried to use it from my C++ code I forgot the extern "C" bit, so remember that. The function is somewhere in the public API and while it is available on the desktop versions of Mac OS X, it seems like Apple forgot to include it in the header files for the iPhone.
Basically this function can give you glyphs that you need to draw the text directly. Glyphs are not directly characters. For instance a glyph can contain two characters if the font designer allows for a bit of overlap. It often happens with letters "f" and "i".

Before we can draw anything we need to create a CGFont of course:

CGFont cgfont = CGFontCreateWithFontName ((CFStringRef)fontName);
CGContextSetFont(cg, cgfont);
CGContextSetFontSize(cg, fontSize);

(here fontName is an NSString)

Now, if we wish to draw e.g. an NSString called word we begin by getting some info about the glyphs as follows:

int count = [word.s length];
unichar * buffer = new unichar[count+1];
CGGlyph * glyphs = new CGGlyph[count+1];
int * adv = new int[count+1];
CGRect * rects = new CGRect[count+1];
[word.s getCharacters:buffer];
CGFontGetGlyphsForUnichars(cgfont, buffer, glyphs, [word.s length]);
CGFontGetGlyphAdvances(cgfont,glyphs,count,adv);
CGFontGetGlyphBBoxes(cgfont, glyphs, count, rects);

In other words, we get the glyphs for the letters of the word and then we get the dimensions and advances of the individual glyphs. Note: I could not find any way to determine the number of glyphs for a word.

Now we are ready to draw the glyphs. We can do it by:

CGContextShowGlyphsAtPoint(outputCG, x, y, glyphs,count);

That's neat but it doesn't solve the whole font-embedding problem. However, we can solve that problem if get quartz to draw the text as vector elements instead of ... well... text. We simply change the way text is drawn using the function:


CGContextSetTextDrawingMode (outputCG, kCGTextClip);

This will use the text as a clipping path instead of simply drawing it. If we first call CGContextShowGlyphsAtPoint and then draw a filled rectangle around the area where the glyphs are shown, that rectangle will be clipped to the glyphs and essentially we are drawing the letters of the word. However, since we are drawing a rectangle and not text, the resulting PDF file will not include the text but instead a set of vector elements that look like text. Therefore Quartz no longer needs to embed the fonts in the PDF file. One way to do this is by the following loop:

double scale = ffontSize / double( CGFontGetUnitsPerEm(cgfont) );
for (size_t i = 0; i < count; ++i) {
CGContextSaveGState(outputCG);
CGContextShowGlyphsAtPoint(outputCG, x, p.y + spaceH, &glyphs[i], 1 );
CGContextBeginPath(outputCG);
CGContextAddRect(outputCG,
CGRectMake(x+scale* rects[i].origin.x , p.y + spaceH + scale*(rects[i].origin.y),
scale*(rects[i].size.width), scale*(rects[i].size.height)) );
CGContextFillPath(outputCG);
CGContextRestoreGState(outputCG);
x += adv[i] * scale;
}

Here I draw one glyph at a time by setting the clipping path to the individual glyph and drawing a filled rectangle behind it. I have of course set the current fill color to the color I want the text in. The beauty of this is that you could also have gradient fills and other neat effects that I hope to explore in the near future.

Note: Remeber to release the font when you are done:


CGFontRelease(cgfont);

PDF embedding

One of the cool things about quartz is its ability to draw from other PDF files. The code looks like this:

CGPDFDocumentRef document = GetPDFDocumentRef (filename);
CGPDFPageRef page = CGPDFDocumentGetPage (document, 1);
CGRect box = CGPDFPageGetBoxRect(page,kCGPDFArtBox);
CGRectMake(bounds.origin.x,bounds.origin.y,bounds.dimension.x,bounds.dimension.y),0,false);
CGContextSaveGState(context);
CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y);
CGContextScaleCTM(context, bounds.dimension.x/box.size.width, bounds.dimension.y/box.size.height);
CGContextTranslateCTM(context, -box.origin.x, -box.origin.y);
CGContextDrawPDFPage (context, page);
CGPDFDocumentRelease (document);
CGContextRestoreGState(context);

If you do this directly on the iPhone you'll experience a crash. From what I can tell, it comes from the fact that quartz doesn't retain the PDF document when you draw it, so you have to postpone
CGPDFDocumentRelease (document);
until you have called CGContextEndPage (pdfContext).

Epilogue

That's it for now. It may seem pretty straight-forward but realizing how to embed images and dealing with the whole font-embedding issues took me a couple of days of experimenting, so I hope I have made life easier for someone else out there!

Apple speeding up reviews?

by jegeblad

Here is a bit of my review experience times:

Back in October last year the review process in Games/Puzzle took about 7 days. In november, the photography section took about 4 days. Then around december something happened and all my reviews have taken about 6-8 days, with a few exceptions here and there of about 10 days (This includes games, photography and productivity).

Then coinciding with the iPhone 3.0 release reviews started taking much longer. In June, Lifecards (photography) was floating in review-space for about 17 days, so even though it was submitted about 8 days before 3.0 was released, it didn't surface until more than a week after. Voice alarm (productivity) took about 12 days. Last round of Lifecards and Lifestrips took about 10 days (to get rejected) but only 5 days to get accepted after the rejection.

The two apps were initially rejected because they could have confused the user at one point where an internet connection was required. They didn't crash or anything, they just didn't announce that network access was needed. I am glad Apple is pointing it out, but I can understand why a lot of developers would be annoyed by it.

I submitted Lifecards and Lifestrips Monday this week and they both appeared on the app store yesterday evening: i.e. approximately 5 days.

So the last two times I have submitted something in the photography category, it has taken 5 days for it to go through, which I think is pretty good.

If you have an urgent bug 5 days is almost bearable, but it still resulted in a few emails and ugly reviews.

Voice alarm was been submitted also on Monday, but so far nothing -- 6 days and counting.

In academia, I have waited up-to 12 months for reviews of papers. But then again, they were not really urgent.

<< 1 2 3 4 5 6 7 8 9 10 11 >>