Skip to content

Instantly share code, notes, and snippets.

@DerAndereAndi
Last active December 15, 2015 09:19
Show Gist options
  • Save DerAndereAndi/5237483 to your computer and use it in GitHub Desktop.
Save DerAndereAndi/5237483 to your computer and use it in GitHub Desktop.
Ground Control additions, see description.md file below
// feature keys
#define kFeatureA @"FeatureA"
// Ground Control keys
#define GROUND_CONTROL_URL @"http://yourserver.com/gc.php?version=%@&bundle=%@&lang=%@"
#define kGroundControlLastCheck @"GroundControlLastCheck"
#define kGroundControlCheckInterval @"GroundControlCheckInterval"
@implementation LocationsAppDelegate {
BOOL _isFeatureAEnabled; // Example feature
NSDate *_groundControlLastCheck;
NSUInteger _groundControlCheckInterval;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
// default check interval in days
_groundControlCheckInterval = 7;
[self initializeGroundControl];
...
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
...
[self initializeGroundControl];
...
}
#pragma mark - Ground Control
- (BOOL)shouldUpdateGroundControlData {
BOOL checkForUpdate = NO;
if ([[NSUserDefaults standardUserDefaults] objectForKey:kGroundControlLastCheck]) {
_groundControlLastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:kGroundControlLastCheck];
}
if (!_groundControlLastCheck) {
_groundControlLastCheck = [NSDate distantPast];
}
NSString *testValue = nil;
testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kGroundControlCheckInterval];
if (testValue != nil) {
_groundControlCheckInterval = [[NSUserDefaults standardUserDefaults] integerForKey:kGroundControlCheckInterval];
}
NSTimeInterval dateDiff = fabs([_groundControlLastCheck timeIntervalSinceNow]);
if (dateDiff != 0)
dateDiff = dateDiff / (60*60*24);
checkForUpdate = (dateDiff >= _groundControlCheckInterval);
#if defined (CONFIGURATION_Debug)
// we always tun this check on startup, because we want to test, right?
checkForUpdate = YES;
#endif
return checkForUpdate;
}
- (void)updateGroundControlFeatures {
// update the remotely configurable features
NSString *testValue = nil;
// example feature
testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kFeatureA];
if (testValue != nil) {
_isFeatureAEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kFeatureA];
// now you would somehow react on the change of this value or use the other approaches described in Ground Control docs
}
// ground control check interval in days, app default is set to 7
testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kGroundControlCheckInterval];
if (testValue != nil) {
_groundControlCheckInterval = [[NSUserDefaults standardUserDefaults] integerForKey:kGroundControlCheckInterval];
}
}
- (void)initializeGroundControl {
[self updateGroundControlFeatures];
if (![self shouldUpdateGroundControlData]) return;
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
NSString *bundle = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
NSString *language = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
NSString *urlString = [NSString stringWithFormat:GROUND_CONTROL_URL, version, bundle, language];
NSURL *url = [NSURL URLWithString:urlString];
[[NSUserDefaults standardUserDefaults] registerDefaultsWithURL:url
success:^(NSDictionary *defaults) {
_groundControlLastCheck = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:_groundControlLastCheck forKey:kGroundControlLastCheck];
[self updateGroundControlFeatures];
}
failure:^(NSError *error) {
}];
}

Ground Control by @mattt is a great iOS library to remotely configure your iOS app by sending plists from a server to your app and then updating the user defaults values.

I wanted to extend this a bit with the following ideas in mind:

  • Define the values in a JSON file, instead of uploading an updated plist file to the server
  • Define values independently for given bundle identifiers, bundle version and language combination, so I can modify e.g. beta and debug versions independently from released apps
  • Values can be defined in a default language for a bundle version and overwritte on language specific entries like en. You can also only use default or only use specific languages
  • Return an empty plist for all unknown bundle identifiers or bundle versions
  • Don't check for config updates on every startup or every time the app becomes active, but only once a day or a remotely configurable amount of days
  • Right now the server side php script only supports boolean, integer and strings since I didn't need more. Specific data types like NSDate would be more complicated, but could be used e.g. by naming conventions for the key or using objects which include a datatype key, or anything else :)
{
"de.buzzworks.worldviewlive" : {
"294" : {
"default" : {
"FeatureA" : false,
"GroundControlCheckInterval" : 7
},
"en" : {
"FeatureA" : true
}
}
},
"de.buzzworks.worldviewlivebeta" : {
"294" : {
"default" : {
"FeatureA" : false,
"GroundControlCheckInterval" : 7
}
}
},
"de.buzzworks.worldviewlivedebug" : {
"294" : {
"default" : {
"FeatureA" : false,
"GroundControlCheckInterval" : 7
}
}
}
}
<?php
//
// plist writer library from http://www.j-schell.de/node/456
// code for the plist write is here: http://www.j-schell.de/files/n456_plist_writer.html
//
include ( 'plistwriter.inc' );
header('content-type: application/x-plist');
function finish() {
$arr = array ($key=>$value);
$x = new record_item ( ); // start plist with record
die($x); // display the property list.
}
$allowed_args = ',version,bundle,lang,';
foreach(array_keys($_GET) as $k) {
$temp = ",$k,";
if(strpos($allowed_args,$temp) !== false) { $$k = $_GET[$k]; }
}
if (!isset($version)) finish();
if (!isset($bundle)) finish();
if (!isset($lang)) finish();
// load json with data
$jsonString = file_get_contents("gc.json");
if (!$jsonString || $jsonString == "") finish();
$json = json_decode($jsonString, true);
if (!$json[strtolower($bundle)]) finish();
$jsonBundle = $json[strtolower($bundle)];
if (!$jsonBundle[$version]) finish();
$jsonVersion = $jsonBundle[$version];
if (!$jsonVersion[$lang] && !$jsonVersion["default"]) finish();
$returnData = array();
// get the default values, if present
if ($jsonVersion["default"]) {
$jsonDefaultLang = $jsonVersion["default"];
foreach ($jsonDefaultLang as $key => $val) {
$returnData[$key] = $val;
}
}
// get the language specific values and overwrite the default values
if ($jsonVersion[$lang]) {
$jsonLang = $jsonVersion[$lang];
foreach ($jsonLang as $key => $val) {
$returnData[$key] = $val;
}
}
$x = new record_item ( ); // start plist with record
foreach ($returnData as $key => $val) {
if (is_bool($val)) {
$x->add_bool($key, $val); // add bool value
} else if (is_int($val)) {
$x->add_int($key, $val); // add integer value
} else if (is_string($val)) {
$x->add_text($key, $val); // add string value
}
}
print $x; // display the property list.
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment