Increase your app ranking with Facebook mobile app install ads

I was looking into Facebook Mobile app install ads lately for my apps. There is lots of noise about them and most say that they are really worth it. And they probably do, but only if you have a high grossing app. So how well should your app perform? Let’s see.

Let’s assume that you set a cost per ad click of of $ 0.4. You can try to go as low as $ 0.2 but you might see many days when your ad is not being displayed due to higher bidders.

The industry standard conversion rate to installs for mobile ads is 2-5%. In the case of Facebook things are better. I have seen people mentioning a conversion rate of 7-10%. Let’s say that you have a super cool app icon and awesome screenshots and the user will download your app 1 out of 10 times (10% conversion rate).

Let’s also assume that we would like to improve our app downloads by +1,000 / day. Then we would need to have a budget of $ 4,000/day. That way you will get 10 thousand clicks and out of them you will get one thousand installs.

That way your cost per install ends up being $4. No wonder you are seeing card games and casino apps all the time. Those apps make on average much more than $4/install.

With Facebook you can set your ad campaign to CPC or CPI. In our example we paid for CPC and calculated the CPI. Try both approaches and see for your self what is better for your app.

To see how many extra downloads your app needs to enter the top 25 ranks check the following post by Distimo, it is a little old so the numbers are not 100% accurate: http://www.distimo.com/blog/2012_05_quora-answering-series-download-volume-needed-to-hit-top-25-per-category/

The following post by Katie Smillie offers more detailed information on Mobile App Install Ads: http://katiesmillie.com/2012/12/19/facebook-crushes-the-competition/

Is localizing your app really worth it?

After localizing most of my apps at http://appcamelot.com/apps, I began talking with lots of app developers about localization. Like me, they usually localize their apps because they believe that it will bring in more more revenues. But very few have the numbers to support that and I am starting to doubt if it is worth the time and effort. There are some significant drawbacks and difficulties when it comes to localization:

1. You will probably end up with a lower quality product. If you localize at a low price the result will not have the same quality as your english version (e.g. I had the phrase “play piano” translated as if it were “perform piano”). And even if you localize at an industrystandard rate, e.g. $0.1/word, the translator might not really feel your app so it might not be very accurate. You can improve the quality of the localization by having a second translator one to review it and by providing either the app or screenshots of your app to your translators. So the cost will end up being very high for something to match your app that is in English.

2. Not all countries are the same. Let’s face it, USA makes you the most money, Japan is also worth it, China will give you lots of downloads but not a very good download/revenue ratio. And then some European countries combined make a good share of your revenues. So why go crazy and localize in every language possible?

3. You might not see a big difference in revenues from countries with strong presence on the App Store. From my personal experience and by comparing the stats of my current 7 apps at http://appcamelot.com I see no big change after the localized versions rolled out. I had good revenues from Japan and lots of downloads from China even with the English only version.

4. Many countries just love English. If you give them an app in their language you just make it worse. Being Greek I know that the majority of users from some countries prefer their apps in English. It just looks odd when it is in their language. I had many translators working on my apps begging me not to change the name of it, it just sounded awful to them. Another example of a country that prefers English is Norway.

5. Get ready to sweat. The amount of work you have to do will increase depending on how many languages you target. I have some suggestions to automate some tasks. You should use Apple’s automation framework to take localized screenshots of your app. If you add text layers to your screenshots you should use data sets in photoshop. If you want details on how to perform these tasks you can contact me.

6. Maintaining your app will be harder. If you change the text in your app get ready to contact your translators and go through a process that did not exist before. When you do an update, the what’s new section will need 3 days instead of 10 seconds. That is a significant overhead. If you send out push notifications, well good luck with that!

7. App Store Optimization will be harder to do. Choosing good and common  keywords will be nearly impossible if you are not a native of the language you are localizing to.

8. Many big publishers do not localize their apps. They must know something…

I would love to see your comments and I hope that you give me some insights and I start seeing localization from a different angle.

Get rid of old simulators in Xcode

simulator

To get rid of these old simulators in your new Xcode 4.5, follow the next steps:

Step 1:
Go to /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs
and delete the SDK you no longer need, e.g. iPhoneSimulator5.0.sdk

Step 2:
Go to ~/Library/Caches/com.apple.dt.Xcode/Downloads and delete the simulator package so that Xcode does not ask you to install older simulators when you launch it, e.g. Xcode.SDK.iPhoneSimulator.5.0-5.0.1.1.dmg

Pixel, give me your RGBA

pixels

In some cases you might want to get the alpha value for a specific point/pixel in an image. Let’s say for example that you have an image of a drum music instrument, and you want to check that the user is tapping on the drum graphic and not on the transparent areas around it.

To do that check you need to use the Core Graphics API. Let’s assume that we have a IHAlphaDataView class inheriting from UIView and that it contains a UIImageView. We want to get the alpha value at a specific point on the image where the user tapped. Our IHAlphaDataView interface would be:

typedef unsigned char byte;
 
@interface IHAlphaDataView : UIView
{
    UIImageView * imageView;
}
 
- (float)alphaAtPoint:(CGPoint)point;
 
@end

And the IHAlphaDataView implementation:

- (float)alphaAtPoint:(CGPoint)point
{
    byte data = 0;
 
    CGContextRef context = CGBitmapContextCreate(&data, 1, 1, 8, 1, NULL, kCGImageAlphaOnly);
 
    UIGraphicsPushContext(context);
    [imageView.image drawAtPoint:CGPointMake(-point.x, -point.y)];
 
    UIGraphicsPopContext();
    CGContextRelease(context);
 
    return data / 255.0f;
}

Let’s go through the implementation of the alphaAtPoint method that returns the alpha value for a specific point on the image.

First we declare our byte variable that will hold the alpha value. A byte is a type definition we added to our interface and is the same with a char data type.

Then we create a bitmap context. To do that we pass the following parameters to the CGBitmapContextCreate function:

  1. a pointer to where the context will be drawn
  2. the width of the bitmap that will be drawn
  3. the height of the bitmap
  4. the bits per component (we need 8 bits=1 byte for alpha)
  5. the bytes per row (we will have one row with one pixel holding an alpha value, so that will be 1)
  6. a color space (we pass a NULL color space since we do not care about the color)
  7. other options (we pass the kCGImageAlphaOnly option there since we only want the alpha value).

Next we make the context we created active and we draw our image at the point that the method receives. Note that we use negative values, -x and -y. That is the point at which we want to draw the top left-corner of the image. In that way we are moving the image by x and y so that the context contains the point that we are interested in.

Finally we pop the context since we are done with it, release it and return our byte divided by 255.0f. We want to return values ranging from 0 to 1, that is why we do that division, the value of a byte ranges from 0 to 255.

Let’s assume that we have a IHAlphaDataView object (e.g. named myAlphaView) in a main view. We can make alpha checks in the following way:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for(UITouch * touch in touches)
    {
        CGPoint point = [touch locationInView:self];
 
        if(CGRectContainsPoint(myAlphaView.frame, point))
        {
            CGPoint convertedPoint = [self convertPoint:point toView:myAlphaView];
 
            float alpha = [myAlphaView alphaAtPoint:convertedPoint];
        }
    }
}

That is all you need to get the alpha value of a point in an image. But what about getting the RGB values? Lets see how a colorAtPoint method should work:

typedef struct {
    byte red;
    byte green;
    byte blue;
    byte alpha;
} RGBAPixel;
 
- (UIColor *)colorAtPoint:(CGPoint)point
{
    RGBAPixel pixel = {0, 0, 0, 0};
 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, sizeof(RGBAPixel), colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
 
    UIGraphicsPushContext(context);
 
    [imageView.image drawAtPoint:CGPointMake(-point.x, -point.y)];
 
    UIGraphicsPopContext();
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
 
    CGFloat red = pixel.red / 255.0f;
    CGFloat green = pixel.green / 255.0f;
    CGFloat blue = pixel.blue / 255.0f;
    CGFloat alpha = pixel.alpha / 255.0f;            
 
    UIColor * color = [UIColor colorWithRed:red / alpha green:green / alpha blue:blue / alpha alpha:alpha];
 
    return color;
}

For more convenience and code readability, we have added another typedef named RGBAPixel which holds 4 bytes, 1 byte for each component. Our method starts by definining an RGBAPixel var and initializing it with 0s.

Then we are creating a color space because we need the colors now. While creating our context we pass it that color space.

The bytes per row that we pass this time is not 1, but sizeof(RGBAPixel) which is 4 bytes, and the last difference in our context creation is that we pass the kCGImageAlphaPremultipliedLast and kCGBitmapByteOrder32Big options combined.
kCGImageAlphaPremultipliedLast means that we want to put the alpha value in the least significant bits of our RGBAPixel variable and the kCGBitmapByteOrder32Big means that the least significant bits are in the last byte (higher address). If you check our RGBAPixel typedef, the last byte is our alpha, so everything will fall into place.
For more on endianness check http://en.wikipedia.org/wiki/Endianness

Next, we draw using the context like before and release it. We are also releasing the color space.

Finally we get the individual values and return a UIColor variable. Note that we divide each color by alpha, that is because the colors we got were alpha premultiplied (kCGImageAlphaPremultipliedLast), so we are undoing this by dividing with alpha.

That is how it all works!. Here is an app I created using this technique. It is called Drum Set+ itunes.apple.com/app/id492177760. Try tapping on the drums, you can only hit them when you tap them within their shape, and not in the corners.

You can find everything I described in this post, a working example and and some more functionality on my GitHub repository https://github.com/ioshero/RGBA
One feature that has been included, is caching the bitmap data and not drawing a new context every time you want to check the RGBA values of a point.

I hope you put this knowledge into good use and create something fun!

The missing Django deployment tutorial

django

I believe that every good developer should be able to deploy a backend and work with it. A smart developer will also use an application framework to make his life easier and be more productive. Django is the application framework I love. It is tested, complete and sexy, written in beautiful Python.

The official documentation is also awesome, but it is not enough if you want help with deployment. We will be deploying Django using Apache 2.x and mod_wsgi running in daemon mode (recommended). The daemon mode will create a separate dedicated process for running your Django application that is independent from the Apache child processes.

I am assuming that you have Django already installed, if not it is never late to install the latest version.

wget http://www.djangoproject.com/download/1.3.1/tarball/
tar -xzvf Django-1.3.1.tar.gz
sudo python setup.py install

Make some love
Let’s also assume that the folder where we keep our Django projects is
/var/django and we will be working on a project named love

django-admin.py startproject love

Create the application wsgi entry point
Now we need to create a wsgi entry point for our project, the best place to put it is under /var/django/love/apache. You want to have it on its own folder, isolated for security reasons, since our web server will be accessing it. The recommended filename is django.wsgi which is just a convention.

import sys,os
 
sys.path.append("/var/django")
sys.path.append("/var/django/love")
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "love.settings")
 
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Configure Apache
Time to configure our Apache server and let it know about our project and the wsgi entry point. Edit your vhost configuration and add:

WSGIScriptAlias / /var/django/love/apache/django.wsgi
 
<Directory /var/django/love/apache>
    Order deny,allow
    Allow from all
</Directory>

Now the root url is aliased with our wsgi entry point which runs the application.

Enable wsgi daemon mode
We do not want to run wgsi in embedded mode and adding the following lines will enable daemon mode.

WSGIDaemonProcess love.me user=love group=love threads=25
WSGIProcessGroup love.me

It is recommended to create a non privileged user to run the daemon process. I created the user/group love to match our project name. We are also setting the name of the process and the process group to love.me, matching our domain name (you can pick any name you want though).

The socket permission issue
The wsgi configuration looks complete but there is still a problem. Apache might be unable to communicate with the wsgi daemon process.
That happens because the socket that is used for the communication is placed into the Apache runtime environment which is readable only by Apache and not the wsgi daemon process. To solve this edit your conf.d/wsgi.conf and add:

WSGISocketPrefix /var/run/wsgi

Let’s view it
We are done, lets create a view to see it working. Create a views.py file in your project folder and add:

from django.http import HttpResponse
 
def index(request):
    return HttpResponse("Django love!")

Then set the url configuration to point to the new view, edit url.conf and set your urls patterns to:

urlpatterns = patterns('',
     url(r'^$', 'love.views.index'),
)

^$ matches empty strings, it means match what starts and ends with nothing in between, so that will match our root url.

Visit your root URL and check, I hope it works!
If you have some kind of configuration issue check http://code.google.com/p/modwsgi/wiki/ConfigurationIssues or contact me, I might be able to help.

There are two more things that I want to include in this tutorial, serving static files and source code reloading.

Serving static files
The Apache configuration will be entirely complete and ready to go by adding some more directives for serving the static files of your project, edit your vhost configuration and add:

Alias /robots.txt /var/django/love/static/robots.txt
Alias /favicon.ico /var/django/love/static/favicon.ico
 
Alias /static/ /var/django/love/static/
<Directory /var/django/love/static>
    Order deny,allow
    Allow from all
</Directory>
 
Alias /media/ /var/django/love/media/
<Directory /var/django/love/media>
    Order deny,allow
    Allow from all
</Directory>
 
AliasMatch ^/([^/]*\.css) /var/django/love/static/styles/$1

Remember to create the static, media and styles folders in your Django project and then edit settings.py and change the following parameters:

MEDIA_ROOT = '/var/django/love/media/'
MEDIA_URL = '/media/
STATIC_ROOT = '/var/django/love/static/'
STATIC_URL = '/static/'

Source code reloading
Every time a file is changed Apache has to be restarted to load the changes when running in the default embedded mode.
When running in daemon mode, which is our case, the daemon process and therefore the code will be reloaded everytime the django.wsgi file is changed. So a quick way to force a code reload would be to touch the file. But that is not convenient enough and you should not poke that file all the time.

To enable proper source code reloading do the following:
1. Create a monitor.py file and add the code from the section Monitoring For Code Changes as shown in http://code.google.com/p/modwsgi/wiki/ReloadingSourceCode
2. Edit the django.wsgi file and add:

import monitor

monitor.start(interval=1.0)
monitor.track(os.path.join(os.path.dirname(__file__), 'site.cf'))

Happy development! Remember to remove the source code monitoring in a production environment to avoid the extra overhead.

Special thanks to the great Django official documentation
https://docs.djangoproject.com/en/1.3/
and the amazing modwsgi documentation from Graham Dumpleton
http://code.google.com/p/modwsgi/

SSL Secure

ssl

One of my latest adventures was to configure a web server to serve content over SSL. It is quite a simple task, but the uninitiated ones might run into some problems or might get confused. I used GoDaddy as the certificate service provider, purchased a single domain SSL certificate and installed it on an Apache 2.x web server running on an Amazon EC2 instance with the Amazon Linux AMI.

There are probably better certificate providers like DigiCert or VeriSign. But I wanted something inexpensive at the time and GoDaddy has an offer for 12.99/year which is a great deal that you can get from here. Also the certificate is generated and ready to use within minutes (around 5 in my case) and it claims 99% browser compatibility.

1. Generating and downloading your certificate

After paying for a certificate, a few steps are required before it is generated. GoDaddy gives you credits that you can use to request a certificate and start its configuration process. During configuration you will have to enter the type of hosting (virtual server, dedicated server etc), the domain name and also a CSR (certificate signing request) must be provided.

The CSR has to be generated on your server. Assuming that you run a dedicated server and that you have full access to it, generate a private key first:
openssl genrsa -out ioshero.key 2048
and then using that key create the certificate signing request:
openssl req -new -key ioshero.key -out ioshero.csr

You will have to enter some information here are well, the most important one is the Common Name, which is your fully qualified domain name, e.g. ioshero.com. You can leave the optional information fields empty. After the csr file has been generated, get its contents and paste them into your certificate provider configuration page.

The provider will generate the certificate and will allow you to download it in order to transfer it to your server. You might get a second intermediate certificate as well, which is used for maximum browser compatibility taking care of older browsers. Both will be used in your web server configuration.

2. Configuring your web server

First make sure the Apache SSL module is installed, e.g. by running yum list mod_ssl. If it is, edit your vhosts.conf file and add the following entry:

NameVirtualHost *:443
<VirtualHost *:443>
        DocumentRoot /var/www/ioshero
        ServerName ioshero.net:443
 
        SSLEngine On
        #your certificate
        SSLCertificateFile /var/www/certificates/ioshero.net.crt
        #your private key
        SSLCertificateKeyFile /var/www/certificates/ioshero.key
        #your intermediate certificate
        SSLCertificateChainFile /var/www/certificates/gd_bundle.crt
</VirtualHost>

Another important change that you have to do with your Apache configuration is to edit the conf.d/ssl.conf file that was automatically generated during installation and comment out the <VirtualHost _default_:443> entry and everything in it. Otherwise your server will not return your brand new purchased certificate but a dummy localhost certificate instead.

Also remember to allow connections on port 443 in case you are running behind a firewall. Amazon EC2 instances need to have their Security Group modified for that reason. Time to service httpd graceful restart your web server.

3. Does it work?

You can run an online SSL validation test to see if all went well. The best one I found is from Qualys SSL Labs and you can run the test here: https://www.ssllabs.com/ssldb Hopefully everything should look green and you should get an A rating for your certificate. Here is an example of a domain that is properly setup.

If you get errors, it is time to start looking around for answers, or leave a comment to me, I might be able to help you. Here are some debugging things to try first:

1. check if you can establish an ssl connection with your server
openssl s_client -connect ioshero.com:443

2. check if the contents of the csr file are like you entered them
openssl req -noout -text -in ioshero.csr

3. check the contents of the certificate your provider gave you
openssl x509 -in playcloud.net.crt -text -noout

4. Try repeating the process of generating a certificate. Your provider will have an option for that, GoDaddy’s option is named Re-key, in any case do not revoke your certificate because it will go away forever and with that your money as well. You will have to purchase a new one if you revoke your certificate!

This post might not fit exactly your needs but it can be of assistance to some and a good reference. Happy, secure and safe 2012!

Bookstrapping iOS development



If you want to dive too into this exciting world here are the books I recommend:

Beginning iPhone 4 Development: Exploring the iOS SDK from David Mark and Apress I think it is the currently best selling book on iOS development. It covers pretty much anything you will need to get started and it has lots of hands on examples.

After you are done with it and you feel like going even deeper, look no further than Programming iOS 4: Fundamentals of iPhone, iPad, and iPod Touch Development from Matt Neuburg and O’Reilly. It is one of the most well written books I have read and it is very well organized. If you understand everything in this book I welcome you to Senior land!

Both books refer to iOS 4 and you will get to learn how memory management works in iOS. With iOS 5 memory management has been simplified lot (with ARC) but most of the existing code was written using the iOS 4 SDK, so it is good to know about memory management. I added links to the Kindle version of the books, because that is my preferred way of reading books. I kind of miss paper, but I think digital books are eco-friendly and modern.

Comments for your blog

disqus

Well, I guess WordPress is also something I can blog about since I am using it. I had to find a good commenting system but I have no idea what most people use. Having Facebook and Twitter login seems like a must these days, so I found Disqus. The only turn down initially was that it did not have those nice looking social network buttons, but it seems like it does. Here is what you have to do:

  1. Go to Disqus.com and setup a new free account.
  2. In the settings make sure you check the “Display login buttons with comment box“.
  3. After you are done with signing up go to your WordPress panel, in the plugins sections, look for Disqus commenting system and install it.
  4. Remember to set proper file permissions otherwise WordPress won’t be able to install the plugin and will ask for an FTP connection. Find the user you are running apache as (e.g. using phpinfo). In my case it is apache, so chmod -R apache:apache wordpress does the trick.
  5. After the filesystem handling detour and after you have installed the plugin, go through the configuration steps and you are almost set.
  6. Disqus is right there in your posts now but the login buttons are not showing yet. You have to go back to the Disqus website to the appearance settings http://YOUR_USERNAME.disqus.com/admin/settings/appearance/.
  7. Select the Narcissus theme and you are set.

Enjoy!

Hello developers world!

helloworld

As of this day, I decided to create this blog. It was something I had in my mind, but I never found the time to do it. So… what is this blog for? This blog is a way to post on the things I find out, as I am working with Apple’s exciting tools and the iOS SDK, while making cool mobile iPhone and iPad apps (no Android here sorry). Posting is good because it will helps remember things better, you know the feeling when you are trying to think of something you have done in the past and you can’t? I hate it and some repetition seems to help things a bit. Writing on this blog is also a good way to keep notes and I encourage every developer to do it. These notes can be shared through this website and I hope that the developer community finds some value in them. I would love to see your comments on the posts, and please if you see something wrong or something you do not agree with be straightforward. Welcome on-board!