Recently I was faced with an issue where I was writing an app that could call another app using iOS’s URL scheme. An issue I ran into was how to handle the calling app not being installed. Basically, when openURL failed, I needed to ask the user if they wanted to install the app. It is possible to use canOpenURL before calling openURL but there isn’t a need to because openURL will fail, just like canOpenURL will return NO. So save yourself the intermediate call.

AppInstaller.h

#ifndef AppInstaller_h
#define AppInstaller_h

#import <Foundation/Foundation.h>

@interface AppInstaller : NSObject

+ (void)requestInstall:(UIViewController *)controller appName:(NSString *)name appID:(NSString *)appID attemptedOpen:(BOOL)attemptedOpen;

@end

#endif /* AppInstaller_h */

First create a class that has one static function which will prompt the user to respond whether or not they want to install a given app. It’s a very good idea to ask the user before taking them to the App Store so they know what’s happening and why.

The controller is necessary because an alert asking for app installation will be shown above the controller. The app name is displayed as well as the id for passing to the App Store so the app can be installed. Finally, there is an attemptedOpen parameter which controls the message (reason for installation) presented to the user.

AppInstaller.m

#import <UIKit/UIKit.h>
#import "AppInstaller.h"

@implementation AppInstaller

+ (void)requestInstall:(UIViewController *)controller appName:(NSString *)name appID:(NSString *)appID attemptedOpen:(BOOL)attemptedOpen {
    NSString *title;
	NSString *message;
	NSString *actionTitle;

	if (attemptedOpen) {
        title       = [NSString stringWithFormat:@"Could not open %@", name];
		message     = @"";
		actionTitle = @"Install";
	} else {
        title       = [NSString stringWithFormat:@"Install %@?", name];
		message     = @"";
		actionTitle = @"Ok";
	}

    UIAlertController *alert  = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *aa_install = [UIAlertAction actionWithTitle:actionTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"itms://itunes.apple.com/us/app/apple-store/%@?mt=8", appID]]];
        });
    }];
    UIAlertAction *aa_cancel  = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

    [alert addAction:aa_install];
    [alert addAction:aa_cancel];
    [controller presentViewController:alert animated:YES completion:nil];
}

@end

All the function does is set up an alert, display it to the user, and if they choose to install, it uses openURL to open the App Store requesting the given id. There is a chance that openURL will fail (for example with the simulator) and that’s not handled here. Technically, canOpenURL could be used to verify if the App Store can be opened and the function would return some condition if it could or couldn’t. That said, the chances of a physical device failing to open the App Store are pretty much nonexistent.

iOS does have a SKStoreProductViewController object which will present an embedded App Store view within another app. This is really nice because it doesn’t switch out of the app and into the App Store. That said, when I was using it, I found that if the id is not available (for example, due to mistyping or the app is not available in the user’s country), it will hang and never run the completion block. The URL scheme will work even if the app can’t be installed. Well, it works as far as the App Store won’t hang.

If SKStoreProductViewController is really wanted, then a new function or flag could be added that uses it instead of openURL. In that case, it should be paired with an activity indicator because SKStoreProductViewController will not show until after it loads the store content. It’s very confusing if the user clicks “Install” and nothing happens for a few seconds so it’s a good idea to inform them the request is processing.