Wednesday, November 4, 2015


I'm moving this blog over to as the rich text editor on this site is next to useless and I got tired battling it. My apologies for the inconvenience.

See you at!

Wednesday, October 21, 2015

Observing Many, Notifying One

I've made an Objective C framework for observing (a combination of) many properties, while triggering only one notification callback, whenever the multi-observation criteria is fulfilled.

Sounds fancy, but the need is quiet simple. The canonical example is a login form with a name and a password, plus a Login button which is only enabled when the name and password are not empty.

An alternative example is a checklist with an action, that should only be triggered when all items on the list are checked.

The framework has an elegant way to express these cases. Here is how the first use case would be expressed, by multi-observing the AND combination of the relevant properties:

PIMultiObserver *multiObserver = [PIMultiObserver new];
[multiObserver observeAnd:@[
        [PIObserver observerOf:self keyPath:@"name.length"],
        [PIObserver observerOf:self keyPath@"password.length"]] 
        block:^(BOOL combinedValue) {
            self.loginButton.hidden = !combinedValue;

And here is the checklist use case, using the observeAllYes method, which only triggers the notification block when all observed properties evaluate true:

 [multiObserver observeAllYes:@[
        [PIObserver observerOf:self keyPath:@"booster"],
        [PIObserver observerOf:self keyPath@"retro"],
        [PIObserver observerOf:self keyPath@"fido"],
        [PIObserver observerOf:self keyPath@"guidance"],
        [PIObserver observerOf:self keyPath@"surgeon"]] 
        block:^(BOOL combinedValue) {
            NSLog(@"All systems go!");

Individual observers participating in the multi-observation can also map their observed properties to a BOOL by defining a mapper function.

The code is freely available on Github:

Wednesday, October 7, 2015

canOpenURL Restrictions In iOS 9

iOS 9 has introduced a lesser-known limitation: It is not possible for apps to check if they can open an arbitrary URL anymore. Instead, only specific URL schemes can be checked: those that are included in the application's whitelist.

To create the whitelist, add an new array key called LSApplicationQueriesSchemes to Info.plist, then populate the array with all the schemes the app will test. Here is an example with the "uber" URL scheme:


Note 1: The LSApplicationQueriesSchemes array items should only contain scheme names (without the trailing "://"), however canOpenURL still expects full URLs, like

BOOL uberInstalled = [app canOpenURL:[NSURL URLWithString:@"uber://"]];

Note 2: openURL is not affected by this change.

A nice writeup of the issue can be found here:

Saturday, May 23, 2015

Xcode Automated Build Numbering, Now For Watch Apps, Too

If your iOS project contains a watch extension, Xcode mandates the build numbers in the main app's and the extension's Info.plist files to match. This can be problematic if the build number is auto-generated from the number of Git changes for example.

Below is a build number generator script that ensures all extensions get the same build number as the main app.

First, replace MyApp, MyTodayWidget etc. with the actual app and extension names from your project. Then in Xcode, add the script to the main app build phases as a "New Run Script Phase", after the "Copy Bundle Resources" phase.

# Set the build number to the count of Git commits
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')

# Enlist app name and extension names

# Ensure extension build numbers are the same as the main app
plistDirs=( "$appDir" "$todayExtensionDir" "$watchKitExtensionDir"  "$watchKitAppDir" )

for dir in "${plistDirs[@]}"
    echo "Updating ${TARGET_BUILD_DIR}/$plist"
    /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${TARGET_BUILD_DIR}/${plist}"


Saturday, February 2, 2013

Still Riding The Qt Train?

(Or: Welcome, BB10!)

On popular request, I have finally added some documentation to my OAuth library "O2". Check out the new README here:

Thursday, November 1, 2012

Running Drupal on

Drupal is more or less the only CMS option if your site is hosted on It's not a world-class solution, but again, there is not much else that can run in's bare-bones PHP+MySQL environment.

Download and install it following the instructions on But then, add the following finishing touches:

  • Create the following .htaccess file in / (or replace the existing one):
# SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
# Deny from all
# Options None
# Options +FollowSymLinks

  • Create the following .htaccess file in /sites (or replace the existing one):
# Apache/PHP/Drupal settings:

# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$">
  Order allow,deny

# Don't show directory listings for URLs which map to a directory.
# Options -Indexes

# Follow symbolic links in this directory.
# Options +FollowSymLinks

# Make Drupal handle any 404 errors.
ErrorDocument 404 /index.php

# Set the default handler.
DirectoryIndex index.php index.html index.htm

# Override PHP settings that cannot be changed at runtime. See
# sites/default/default.settings.php and drupal_environment_initialize() in
# includes/ for settings that can be changed at runtime.

# PHP 5, Apache 1 and 2.
# <IfModule mod_php5.c>
#   php_flag magic_quotes_gpc                 off
#   php_flag magic_quotes_sybase              off
#   php_flag register_globals                 off
#   php_flag session.auto_start               off
#   php_value mbstring.http_input             pass
#   php_value mbstring.http_output            pass
#   php_flag mbstring.encoding_translation    off
# </IfModule>

# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
  # Enable expirations.
  ExpiresActive On

  # Cache all files for 2 weeks after access (A).
  ExpiresDefault A1209600

  <FilesMatch \.php$>
    # Do not allow PHP scripts to be cached unless they explicitly send cache
    # headers themselves. Otherwise all scripts would have to overwrite the
    # headers set by mod_expires if they want another caching behavior. This may
    # fail if an error occurs early in the bootstrap process, and it may cause
    # problems if a non-Drupal PHP file is installed in a subdirectory.
    ExpiresActive Off

# Various rewrite rules.
<IfModule mod_rewrite.c>
  RewriteEngine on

  # Block access to "hidden" directories whose names begin with a period. This
  # includes directories used by version control systems such as Subversion or
  # Git to store control files. Files whose names begin with a period, as well
  # as the control files used by CVS, are protected by the FilesMatch directive
  # above.
  # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is
  # not possible to block access to entire directories from .htaccess, because
  # <DirectoryMatch> is not allowed here.
  # If you do not have mod_rewrite installed, you should remove these
  # directories from your webroot or otherwise protect them from being
  # downloaded.
  RewriteRule "(^|/)\." - [F]

  # If your site can be accessed both with and without the 'www.' prefix, you
  # can use one of the following settings to redirect users to your preferred
  # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
  # To redirect all users to access the site WITH the 'www.' prefix,
  # ( will be redirected to
  # uncomment the following:
  # RewriteCond %{HTTP_HOST} !^www\. [NC]
  # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  # To redirect all users to access the site WITHOUT the 'www.' prefix,
  # ( will be redirected to
  # uncomment the following:
  # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
  # RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301]

  # Modify the RewriteBase if you are using Drupal in a subdirectory or in a
  # VirtualDocumentRoot and the rewrite rules are not working properly.
  # For example if your site is at uncomment and
  # modify the following line:
  # RewriteBase /drupal
  # If your site is running in a VirtualDocumentRoot at,
  # uncomment the following line:
  # RewriteBase /

  # Pass all requests not referring directly to files in the filesystem to
  # index.php. Clean URLs are handled in drupal_environment_initialize().
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteRule ^ index.php [L]

  # Rules to correctly serve gzip compressed CSS and JS files.
  # Requires both mod_rewrite and mod_headers to be enabled.
  <IfModule mod_headers.c>
    # Serve gzip compressed CSS files if they exist and the client accepts gzip.
    RewriteCond %{HTTP:Accept-encoding} gzip
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.*)\.css $1\.css\.gz [QSA]

    # Serve gzip compressed JS files if they exist and the client accepts gzip.
    RewriteCond %{HTTP:Accept-encoding} gzip
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.*)\.js $1\.js\.gz [QSA]

    # Serve correct content types, and prevent mod_deflate double gzip.
    RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1]
    RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1]

    <FilesMatch "(\.js\.gz|\.css\.gz)$">
      # Serve correct encoding type.
      Header set Content-Encoding gzip
      # Force proxies to cache gzipped & non-gzipped css/js files separately.
      Header append Vary Accept-Encoding

  • Finally, create the following .htaccess file in /sites/default/files (or replace the existing one):
# SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
# Options None
# Options +FollowSymLinks

Tested with Drupal 7.16.

Thursday, August 30, 2012

Longwire: The Photojournalist's Camera App

Longwire, the camera application for photojournalists is now available from the Nokia Store

The application shares your photographs immediately and automatically, right after taking the shot. Supported destinations are Facebook, Twitter, Flickr, Dropbox and Skydrive, with more to come.

Document events as they are happening, or just make a backup of your photos automatically - don't go for a trip without Longwire!