Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/extensions/Variables/includes/ExtVariables.php on line 198

Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/extensions/Variables/includes/ExtVariables.php on line 198

Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/extensions/Variables/includes/ExtVariables.php on line 198

Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/extensions/Variables/includes/ExtVariables.php on line 198
PreferenceBundles: Difference between revisions - iPhone Development Wiki

PreferenceBundles: Difference between revisions

From iPhone Development Wiki
m (Added to Preferences category)
(Preferences Utilities API and iOS 8)
Line 45: Line 45:


More information about preferences can be seen [http://iphonedevwiki.net/index.php/User:Uroboro#How_2_prefs here].
More information about preferences can be seen [http://iphonedevwiki.net/index.php/User:Uroboro#How_2_prefs here].
== Loading Preferences (a better way) ==
With the release of iOS 8, it became evident that the popular plist loading method wasn't the best way to load preferences. saurik summarized it well:
<source lang=text>
<saurik> charybdis: AFAIK the idea is that the plist file on disk is simply backing a shared memory region managed by cfprefsd, which Apple has brought to iOS from OS X 10.8
<saurik> it only gets flushed when cfprefsd "feels like it"
<saurik> but if you ask cfprefsd for the value using the actual APIs you are supposed to use to access these files it should work
</source>
These "actual APIs" are documented here: [https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFPreferencesUtils/ https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFPreferencesUtils/]. Perhaps you'll end up with something like this:
<source lang=objc>
CFPreferencesAppSynchronize(CFSTR("com.aaronash.threedeeprefs"));
CFPropertyListRef value = CFPreferencesCopyAppValue(CFSTR("TDSwitcherStyle"), CFSTR("com.aaronash.threedeeprefs"));
//do something with the value
</source>
This was tested back to iOS 6, it seemed to work without problems.


== References ==
== References ==

Revision as of 23:12, 26 October 2014

Preference Bundles are bundles for extending the Settings application. Developers can build their own bundles and place them in /Library/PreferenceBundles/ for others to use.

Structure of a Preference Bundle

Preference bundles must have the extension .bundle. The principle class of the bundle should be a subclass of PSListController or PSViewController. When providing localization files, if a specifier plist is called spec.plist, there should be a corresponding localization file called spec.strings. The bundle can have a 29×29 icon, with a preferred name of icon.png.

For more information on specifiers, see Preferences Specifier Plist Format.

Issues with OS 3.2 and 4.0

PSViewController underwent a massive change after 3.1, breaking all custom subclasses on the iPad and on 4.0 - it is now a UIViewController.

Improper implementations of PSListController subclasses will fail to work properly on 4.0 and later. You must set _specifiers within the - (id)specifiers method and return it. This is because PSListController relies on _specifiers to generate specifier metadata and group indices since iOS 4.0. Example:

- (id)specifiers {
	if (!_specifiers){
		_specifiers = [[self loadSpecifiersFromPlistName: kNameOfPreferencesPlist target: self] retain];
	}
	return _specifiers;
}

Using a Preference Bundle

It is very common to load preferences in the constructor (%ctor) of your tweak.

static void loadPrefs() {
	NSMutableDictionary *settings = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/bundleID.plist"];

	logging = [settings objectForKey:@"logging_enabled"] ? [[settings objectForKey:@"logging_enabled"] boolValue] : NO;
}

%ctor {
    loadPrefs();
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)loadPrefs, CFSTR("bundleID/saved"), NULL, CFNotificationSuspensionBehaviorCoalesce);

}

It is very important to only load the preferences in the constructor and not access or modify any UI elements. If you need to do this, you can load your preferences in SpringBoard's init method.

More information about preferences can be seen here.

Loading Preferences (a better way)

With the release of iOS 8, it became evident that the popular plist loading method wasn't the best way to load preferences. saurik summarized it well:

<saurik> charybdis: AFAIK the idea is that the plist file on disk is simply backing a shared memory region managed by cfprefsd, which Apple has brought to iOS from OS X 10.8
<saurik> it only gets flushed when cfprefsd "feels like it"
<saurik> but if you ask cfprefsd for the value using the actual APIs you are supposed to use to access these files it should work

These "actual APIs" are documented here: https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFPreferencesUtils/. Perhaps you'll end up with something like this:

CFPreferencesAppSynchronize(CFSTR("com.aaronash.threedeeprefs"));
CFPropertyListRef value = CFPreferencesCopyAppValue(CFSTR("TDSwitcherStyle"), CFSTR("com.aaronash.threedeeprefs"));
//do something with the value

This was tested back to iOS 6, it seemed to work without problems.

References