Did my Regattakalender App explode in size – due to using Swift?

My App Regattakalender used a slim ~10MB in size on your iPhone in 2015. I wrote the app in 2013 or so and did not change much all the years. This year I wanted to change the data input format, I added new parts to the app and I decided to write the new app parts in Swift rather than Objective-C.

I did not imagine any bad consequences. So I was really shocked, when the archive that I need to upload to apple for submitting the app was suddenly 156MB in size. Uploading the app took really long. A few days later I submitted an Objective-C only app – which uploaded in just about three seconds.

That was the point that triggered my interest and I compared the sizes. I had a look inside the big archive and could see about 72MB being used for Swift Libraries. Strange.

Then I downloaded my own app from the store and checked my App size. 9MB on my iPhone. OK. That means all is OK for my customers and all I have to accept is, that Apple needs an archive with some libraries being uploaded. That’s OK for me.

So for me Swift, again, was very “exciting” to use. 😉

Crash in NSLog when used in swift – and how to avoid it

As a senior iOS programmer, I have learned to use and love a logging feature method called NSLog(). It is usually called with a format string and some arguments. If I have a function callback that comes with two parameters called response and error and I want to print them out, I can do that in Objective-C with the following line:

NSLog(@”response %@, error %@”, response, error);

In that line, the pattern %@ is replaced by the arguments added behind the format string. This also means, it is not so easy to print the pattern %@, because that has a special meaning. But back to my todays problem.

In swift, the same method is very similar:

NSLog(“response %@, error %@”, response, error)

But due to a nice feature in the string class of swift which allows it to include variables in strings, if they are surrounded by backspace and parenthesis, e.g. “\(variable)”, I was writing the above line in the following way:

NSLog(“response \(response), error \(error)”)

It always worked – until today – where it crashed. Why? Because the first string given to NSLog is the format string. This means, that if the content of \(response) creates a special meaning string, e.g. %@ then NSLog tries to interpret that according to the rules. So one safe way to print the same line is, by giving a format string:

NSLog(“%@”, “response \(response), error \(error)”)

This prevents the real string to be interpreted as a format string, because now it is an argument to the format string.

Of course, I had a distant memory, that I had made a similar error when using printf in my first year(s) as a C programmer. By changing to swift, I just had forgotten about checking for this side effect, that I am aware now for so long. A nice example of risk management and how to forget about the risks of a situation when you change the environment.

Appside Down: Should my App Work Upside Down?

When creating an iPhone app, many app developers lean towards disallowing their app being used upside down. Reasons given are usually:

  • The big companies (Apple, Google) have most of their apps only in portrait mode, not in upside down portrait mode
  • When allowing upside down and a phone call comes in, the user is confused, which would not have happened, if the user had to use the phone in portrait mode only

While I do see the reasons behind this, there are situations, where this portrait mode only is just not convenient:

  • When the app is used on a bike, in a rain-shelter but a cable needs to go outside (either phone or charging / lightning). If you are lucky, your bike app shelter knows where the cable is. Else you wish, you could use your navigation app upside down.
  • When the app is used in a car and you want to place the iPhone holder on the windshield in a phone holder as low as possible – then you either are 2-3 cm (or 1 inch) over the windshield due to the charging / lightning cable going out below, or you can turn your iPhone upside down and put the cable in on top, allowing you to lower the phone a little more.
  • When you are charging your phone in a car where there is no phone holder (e.g. rental car), then the cup holders are helpful (sometimes). Being able to put a phone into the cup holder upside down and still being able to see the content of your app helps you save the money for the car phone holder.

So when designing an app that is used over a longer time, e.g. could be used with the lightning cable being plugged in, then the option of using the app upside down suddenly may make sense for your customers.

What do you think?

ToDo before publishing an Android App

Assume your app is ready. What do you need to do to get it into the Google play store?

  1. Generate a signed Apk
    1. Check that the version has a unique version code
    2. Check the applicationId to be unique and starts with your company name, e.g. “com.smallapps.”
  2. Create a new entry in your Google Play Store
    1. Decide which language is the default language
    2. Give it a title up to 30 char long
    3. Give it a short description up to 80 char long
    4. Give it a long description up to 4000 char long
    5. Upload at least 2 screen shots, maximum 8 Screen shots per Type of each: Telephone, Tablet, Android TV and Android Wear – of course only if it is applicable
    6. Upload a “high resolution” symbol, e.g. the icon if your app in 512×512 pixels. But could also be a nicer version. Will be shown in the google play store entry.
    7. Upload a “Funktionsgrafik” (sorry, need to check the english name): Another image of size 1024×500 pixels. Will also be shown in the play store entry.
    8. Optional: Upload an ad picture 180×120 pixels, a TV banner or a video
    9. Select the App-type: App or Game
    10. Select the Category: Learning, Work, Comics, …
    11. Choose a content rating (age rating)
    12. Answer some questions to result in an age rating
    13. Enter an email Address, where you can be contacted
    14. Enter a link to your privacy policy (or click “will be supplied later”)
    15. Enter price or “for free”. This decision is forever!
    16. Decide in which countries to distribute the app
    17. Answer if the app contains ads
    18. Answer if you and your app agree to the android content policy
    19. Answer if you know about export control
    20. Upload the Apk into Alpha or Beta test or upload it to the final store
    21. Decide if it is a open or closed test phase
  3. Then wait until the App is published
    1. If it is a alpha or beta test, you may share the link to find the Apk in the  App store

That’s it already, have fun!

boolForKey:

When storing objects in the NSUserDefaults or in some other key-value store, convenience methods are often used to store and retrieve keys.

boolForKey: is such a method, but since the BOOL data types have only two values, the the information, if the key was in the store or not, is lost in this method. Because it returns NO if the value NO was stored and it also returns NO if no information was stored at all.
This is the reason, why I prefer to store “default-off” values in the user defaults. I rather check for disclaimerWasRead than for shouldShowDisclaimer. I rather check for values which can be off or non-existing by default and will be YES for exactly one decision than having distinguish all three states.

[Edit:] A similar situation arises if you use integerForKey:. The value 0 does not inform you, if the key existed at all, or if it existed as 0. If you use objectForKey: first and then transform the result to a integer value, than you still can distinguish if you received nil from the first call.[End Edit]

[object compare: weHaveAProblem]

When using Objective-C Methods like compare: and similar methods like caseInsensitiveCompare: you should be aware of one possible problem:

  • Compare returns NSOrderedSame when two objects are considered equal
  • NSOrderedSame is also returned, if you call compare: on a nil-pointer!
  • Why is that?

    Because any method being called on a nil pointer returns nil.
    Which is the same as 0 (zero).
    Which is the same as NSOrderedSame.
    Ouch!

    Assume we have:
    NSString *stringa = @"huhu";
    NSString *stringb = @"huhu";
    BOOL result = [stringa compare:stringb] == NSOrderedSame;

    And print it out like this:
    NSLog(@"[@\"%@\" compare:@\"%@\"] == NSOrderedSame is %@ ",
    stringa, stringb, result ? @"yes":@"no");

    Then we get:
    [@"huhu" compare:@"huhu"] == NSOrderedSame is yes
    If we call instead
    stringb = @"not huhu";
    result = [stringa compare:stringb] == NSOrderedSame;

    We will get:
    [@"huhu" compare:@"not huhu"] == NSOrderedSame is no
    Of course, nobody warns us when we use a nil pointer instead:
    stringa = nil;
    result = [stringa compare:stringb] == NSOrderedSame;

    Which of course results to:
    [(null) compare:@"not huhu"] == NSOrderedSame is yes
    Luckily, writing the code this way:
    result = [stringb compare:stringa] == NSOrderedSame;
    Results in the correct value again:
    [not huhu compare:@"(null)"] == NSOrderedSame is no

    Learnings?
    So we learned: We need to check the left object, the method receiver, for not being nil to prevent strange results. And in cases, where we compare against a constant object, we might as well put the constant on the left hand side, resulting in e.g.
    result = [@"huhu" compare:objectb] == NSOrderedSame;
    to prevent stumbling over nil pointers.

    Xcode Asset Catalog

    When using the asset catalog, I usually name the image set in the same way as the image file. E.g. I create an image set leftIcon with files leftIcon@2x.png and leftIcon@3x.png in it.

    Today I decided different.

    I created an image set named button_pressed_background and used a file called grey_background.png.

    When fetching the icon with UIImageNamed:@"...", I was not sure which name to use. It turned out, that I need to use the asset catalog image set name, e.g.
    UIImageNamed:@button_pressed_background", not the filename (which returns nil when used).

    Last question I had, was: Since I used a small grey icon to fill the complete button, is the grey on a 3x-Device the same as on the 2x device? Or do I need a 2x and 3x version of the image file, although it only contains grey pixels? I assumed no, since it looked OK. So hopefully the customers will agree…