How to Detect an iOS App Installed or Upgraded

How to detect an iOS App installed or upgraded?

You can differentiate between the first start after installing the App, the first start after an update and other starts quite easily via saving the latest known version to standardUserDefaults. But as far as I know it is not possible do detect a re-install of the App as all App-related data are also removed when the App is deleted from the device.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString* currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
NSString* versionOfLastRun = [[NSUserDefaults standardUserDefaults] objectForKey:@"VersionOfLastRun"];

if (versionOfLastRun == nil) {
// First start after installing the app
} else if (![versionOfLastRun isEqual:currentVersion]) {
// App was updated since last run
} else {
// nothing changed
}

[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:@"VersionOfLastRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

How to detect an iOS App has been re-installed (from XCode) or upgraded (from AppStore)

The (probably) clean way of ensuring an update is using a variable in your info.plist file, as rmaddy said, but that wouldn't solve the problem of knowing precisely if it is needed or overkill.

I have honestly no idea if Apple will let it pass through their validation, but you can probably store and check the last modification date of the executable. If it's older than a certain date, then update. It seems a bit complicated to have such a crude versioning mechanism. Can't you include a version number in your binary file?

To check for the date:

NSString *exePath = [[NSBundle mainBundle] executablePath];
NSDictionary *exeAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:exePath error:nil];
NSString *lastModDate = [exeAttrs objectForKey:@"NSFileModificationDate"];

Check if my IOS application is updated

You could save a value (e.g. the current app version number) to NSUserDefaults and check it every time the user starts the app.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSString *currentAppVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSString *previousVersion = [defaults objectForKey:@"appVersion"];
if (!previousVersion) {
// first launch

// ...

[defaults setObject:currentAppVersion forKey:@"appVersion"];
[defaults synchronize];
} else if ([previousVersion isEqualToString:currentAppVersion]) {
// same version
} else {
// other version

// ...

[defaults setObject:currentAppVersion forKey:@"appVersion"];
[defaults synchronize];
}

return YES;
}

The swift-2 version looks like this:

let defaults = NSUserDefaults.standardUserDefaults()

let currentAppVersion = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String
let previousVersion = defaults.stringForKey("appVersion")
if previousVersion == nil {
// first launch
defaults.setObject(currentAppVersion, forKey: "appVersion")
defaults.synchronize()
} else if previousVersion == currentAppVersion {
// same version
} else {
// other version
defaults.setObject(currentAppVersion, forKey: "appVersion")
defaults.synchronize()
}

The swift-3 version looks like this:

let defaults = UserDefaults.standard

let currentAppVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
let previousVersion = defaults.string(forKey: "appVersion")
if previousVersion == nil {
// first launch
defaults.set(currentAppVersion, forKey: "appVersion")
defaults.synchronize()
} else if previousVersion == currentAppVersion {
// same version
} else {
// other version
defaults.set(currentAppVersion, forKey: "appVersion")
defaults.synchronize()
}

How to tell if an iOS application has been newly installed or updated?

You could save a version number to NSUserDefaults, and update it accordingly.

If that won't work, you may be able to release an intermediate version which introduces the versioning scheme.

If that's not an option, you may be able to check for traces of previous runs from files you create, or preferences which you set conditionally or lazily.

How can I detect if the currently running app was installed from the app store?

Apps downloaded from the App Store have a iTunesMetadata.plist file added by the store:

NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:@"iTunesMetadata.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// probably a store app
}

Perhaps you might want to check if this file exists.

Update:

In iOS8, the application bundle has been moved. According to @silyevsk, the plist is now one level above [the new application main bundle path], at /private/var/mobile/Containers/Bundle/Application/4A74359F-E6CD-44C9-925D-AC82E‌‌​​B5EA837/iTunesMetadata.plist, and unfortunately, this can't be accessed from the app (permission denied)

Update Nov 4th 2015:

It appears that checking the receipt name can help. It must be noted that this solution is slightly different: it doesn't return whether we're running an App Store app, but rather whether we're running a beta Testflight app. This might or might not be useful depending on your context.

On top of that, it's a very fragile solution because the receipt name could change at any time. I'm reporting it anyway, in case you have no other options:

// Objective-C
BOOL isRunningTestFlightBeta = [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"];

// Swift
let isRunningTestFlightBeta = NSBundle.mainBundle().appStoreReceiptURL?.lastPathComponent=="sandboxReceipt"

Source: Detect if iOS App is Downloaded from Apple's Testflight

How HockeyKit does it

By combining the various checks you can guess whether the app is running in a Simulator, in a Testflight build, or in an AppStore build.

Here's a segment from HockeyKit:

BOOL bit_isAppStoreReceiptSandbox(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;

BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];
return isSandboxReceipt;
#endif
}

BOOL bit_hasEmbeddedMobileProvision(void) {
BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
return hasEmbeddedMobileProvision;
}

BOOL bit_isRunningInTestFlightEnvironment(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (bit_isAppStoreReceiptSandbox() && !bit_hasEmbeddedMobileProvision()) {
return YES;
}
return NO;
#endif
}

BOOL bit_isRunningInAppStoreEnvironment(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (bit_isAppStoreReceiptSandbox() || bit_hasEmbeddedMobileProvision()) {
return NO;
}
return YES;
#endif
}

BOOL bit_isRunningInAppExtension(void) {
static BOOL isRunningInAppExtension = NO;
static dispatch_once_t checkAppExtension;

dispatch_once(&checkAppExtension, ^{
isRunningInAppExtension = ([[[NSBundle mainBundle] executablePath] rangeOfString:@".appex/"].location != NSNotFound);
});

return isRunningInAppExtension;
}

Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

A possible Swift class, based on HockeyKit's class, could be:

//
// WhereAmIRunning.swift
// https://gist.github.com/mvarie/63455babc2d0480858da
//
// ### Detects whether we're running in a Simulator, TestFlight Beta or App Store build ###
//
// Based on https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITHockeyHelper.m
// Inspired by https://stackoverflow.com/questions/18282326/how-can-i-detect-if-the-currently-running-app-was-installed-from-the-app-store
// Created by marcantonio on 04/11/15.
//

import Foundation

class WhereAmIRunning {

// MARK: Public

func isRunningInTestFlightEnvironment() -> Bool{
if isSimulator() {
return false
} else {
if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() {
return true
} else {
return false
}
}
}

func isRunningInAppStoreEnvironment() -> Bool {
if isSimulator(){
return false
} else {
if isAppStoreReceiptSandbox() || hasEmbeddedMobileProvision() {
return false
} else {
return true
}
}
}

// MARK: Private

private func hasEmbeddedMobileProvision() -> Bool{
if let _ = NSBundle.mainBundle().pathForResource("embedded", ofType: "mobileprovision") {
return true
}
return false
}

private func isAppStoreReceiptSandbox() -> Bool {
if isSimulator() {
return false
} else {
if let appStoreReceiptURL = NSBundle.mainBundle().appStoreReceiptURL,
let appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent
where appStoreReceiptLastComponent == "sandboxReceipt" {
return true
}
return false
}
}

private func isSimulator() -> Bool {
#if arch(i386) || arch(x86_64)
return true
#else
return false
#endif
}

}

Gist: GitHub - mvarie/WhereAmIRunning.swift

Update Dec 9th 2016:

User halileohalilei reports that "This no longer works with iOS10 and Xcode 8.". I didn't verify this, but please check the updated HockeyKit source (see function bit_currentAppEnvironment) at:

Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

Over time, the above class has been modified and it seems to handle iOS10 as well.

Update Oct 6th 2020:

Hockey has been deprecated/abandoned and replaced by Microsoft's AppCenter SDK.

This is their App Store / Testflight build detection class (link to repository below code):

MSUtility+Environment.h :

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#import <Foundation/Foundation.h>

#import "MSUtility.h"

/*
* Workaround for exporting symbols from category object files.
*/
extern NSString *MSUtilityEnvironmentCategory;

/**
* App environment
*/
typedef NS_ENUM(NSInteger, MSEnvironment) {

/**
* App has been downloaded from the AppStore.
*/
MSEnvironmentAppStore = 0,

/**
* App has been downloaded from TestFlight.
*/
MSEnvironmentTestFlight = 1,

/**
* App has been installed by some other mechanism.
* This could be Ad-Hoc, Enterprise, etc.
*/
MSEnvironmentOther = 99
};

/**
* Utility class that is used throughout the SDK.
* Environment part.
*/
@interface MSUtility (Environment)

/**
* Detect the environment that the app is running in.
*
* @return the MSEnvironment of the app.
*/
+ (MSEnvironment)currentAppEnvironment;

@end

MSUtility+Environment.m :

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#import "MSUtility+Environment.h"

/*
* Workaround for exporting symbols from category object files.
*/
NSString *MSUtilityEnvironmentCategory;

@implementation MSUtility (Environment)

+ (MSEnvironment)currentAppEnvironment {
#if TARGET_OS_SIMULATOR || TARGET_OS_OSX || TARGET_OS_MACCATALYST
return MSEnvironmentOther;
#else

// MobilePovision profiles are a clear indicator for Ad-Hoc distribution.
if ([self hasEmbeddedMobileProvision]) {
return MSEnvironmentOther;
}

/**
* TestFlight is only supported from iOS 8 onwards and as our deployment target is iOS 8, we don't have to do any checks for
* floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1).
*/
if ([self isAppStoreReceiptSandbox]) {
return MSEnvironmentTestFlight;
}

return MSEnvironmentAppStore;
#endif
}

+ (BOOL)hasEmbeddedMobileProvision {
BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
return hasEmbeddedMobileProvision;
}

+ (BOOL)isAppStoreReceiptSandbox {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (![NSBundle.mainBundle respondsToSelector:@selector(appStoreReceiptURL)]) {
return NO;
}
NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;

BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];
return isSandboxReceipt;
#endif
}

@end

Source: GitHub - microsoft/appcenter-sdk-apple - MSUtility+Environment.m



Related Topics



Leave a reply



Submit