|
PreferencesSpecifierPlistFormat
(Technical Document) Preferences Specifier .plist Format
Phase-Implementation Deprecated page. Please bookmark http://www.iphonedevwiki.net/index.php?title=Preferences_specifier_plist instead, thank you.
IntroductionThis document provides the specification of the .plist file that specifies the layout of an iPhone preference pane. The specification comes from reverse engineering the SpecifiersFromPlist() and +[PSTableCell xxxx:] functions in the Preferences framework. Root levelThe root level of the plist may contain these keys:
LocalizationSome strings, e.g. the title is localizable. Suppose the plist is named MySettings.plist, then the corresponding strings file must be named MySettings.strings. Individual ItemsThe keys can also be obtained later by -[PSSpecifier propertyForKey:]. You can use anything in your controller for further customization. This table lists the internal ones: General keys
bundle-dependent keysThe following keys are meaningful when bundle is present. It is useful for loading extra resources and custom code.
If you just want to use pane without messing up the detail controller, supply the key-value pair detail = "PSDetailController"; If you want to dynamically add a specifier for a PSLinkCell linking to a bundle, do it like this: PSSpecifier* specifier = [PSSpecifier preferenceSpecifierNamed:@"title"
target:self
set:NULL
get:NULL
detail:Nil
cell:PSLinkCell
edit:Nil];
NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/PreferenceBundles/prefs.bundle"];
[specifier setProperty:bundle forKey:@"lazy-bundle"];
[specifier setProperty:@"lazyLoadBundle:" forKey:@"action"];
// Add specifier to the PSListControllerEditing cellsThese keys are specific to editing cells.
List cellsThese keys are specific to list cells.
In order to make a PSLinkListCell actually work like a list, supply the key-value pair detail = "PSListItemsController"; also. Slider and switch cellsThese keys are specific to slider and switch cells.
Miscellaneous control cellsThese keys are specific to other control cells.
Explanation on some special keysrequiredCapabilitiesCapabilities are queried from the function GSSystemGetCapability(0). The capabilities can be found from the file /System/Library/CoreServices/SpringBoard.app/<model>.plist.
The content of requiredCapabilities can be a string or a dictionary. If the value is declared as an string, the device will pass when all required capabilities are satisfied; if it is a dictionary, there is an additional requirement that the values of the capabilities are equal/findable, e.g. requiredCapabilities = (bluetooth, telephony, {unifiedIPod = 0;}, {displayIDs = "com.apple.mobilephone";});cellcell must be one of:
The cell type is actually determined from the class method +[PSTableCell cellTypeFromString:]. confirmationconfirmation itself is a dictionary containing the following fields:
ClassesisControllerIf you declare the bundle isController, the executable code must have a subclass of PSBundleController as the principle (first) class. In the controller, you should overload the message: -(NSArray*)specifiersWithSpecifier:(PSSpecifier*)spec; which returns an array of PSSpecifier, inserted right before the current specifier. The controller class will be internally called like this: // callerList & specifier are arguments of SpecifiersFromPlist PSBundleController* controller = [[MyControllerClass alloc] initWithParentListController:callerList]; [retval addObjectsFromArray:[controller specifiersWithSpecifier:specifier]]; You can refer to the member _parent to get the parent list controller if you don't want to overload initWithParentListController:. detailThe detail controller class should conform to this protocol: @protocol PSDetailController <PSViewController> -(id)initForContentSize:(CGSize)size; -(void)viewWillBecomeVisible:(PSSpecifier*)spec; -(void)handleURL:(NSURL*)url; @optional +(void)validateSpecifier:(PSSpecifier*)srcSpecifier; @end There is already a PSDetailController class in the Preferences framework which you should subclass from. The detail controller class will be sent a +(void)validateSpecifier:(PSSpecifier*) message if one exists right after the bundle is lazy-loaded: @implementation PSRootController
...
-(void)lazyLoadBundle:(PSSpecifier*)srcSpecifier {
...
if ([srcSpecifier->detailControllerClass respondsToSelector:@selector(validateSpecifier:)])
[srcSpecifier->detailControllerClass validateSpecifier:srcSpecifier];
}
...
@endWhenever a view becomes visible, a detail controller class instance will be allocated with -(id)initForContentSize:(CGSize), and then call -(void)viewWillBecomeVisible:(PSSpecifier*) to notify that the view becomes visibile. There is also some other calls to the controller but you'd better leave them unoverloaded. ...
id<PSDetailController> detailController = [[[spec->detailControllerClass alloc]
initForContentSize:someListController.view.bounds.size]
autorelease];
detailController.rootController = _parentController.rootController;
detailController.parentController = _parentController;
[detailController viewWillBecomeVisible:spec];
...paneThe edit pane class is useless unless a PSDetailController subclass is used. The class itself is invoked in -[PSDetailController viewWillBecomeVisible:]. This class must be a subclass of UIView and conforms to the protocol: @protocol PSEditingPane +(CGSize)defaultSize; +(UIColor*)defaultBackgroundColor; -(void)setDelegate:(id)del; // del usually is of type PSDetailController* @property(retain) PSSpecifier* preferenceSpecifier; @property(retain) id preferenceValue; -(BOOL)handlesDoneButton; // return NO if you don't want to save changes. -(BOOL)requiresKeyboard; @optional -(void)viewWillRedisplay; @end There is already a PSEditingPane class in the Preferences framework which you can subclass from. cellClasscellClass allows you to use customized cells. It should be a subclass of PSTableCell. customControllerClasscustomControllerClass is only used when a setup controller's view become visible. It replaces the default PSSetupListController. Using PSLinkCellPSLinkCell is useful for linking to sub-preference-panes. The simplest example just needs 2 keys: { cell = PSLinkCell;
label = "Settings-iPhone"; }The label is the important part. When user clicked on the link cell, iPhoneOS will use the unlocalized label as the file name of the plist for the next pane. For example in above, the main settings screen will appear. If you use just 2 keys, only plists inside Preferences.app can be loaded. In order to load your own plist, you must use a custom subclass of PSListController in detail: { cell = PSLinkCell;
label = "My Awesome Pane";
detail = MyListController; }MyListController can simply be an empty subclass of PSListController: @interface MyListController : PSListController {}
@end
@implementation MyListController
@endThe key thing is when you place MyListController inside your bundle, its bundle property will return your bundle which My Awesome Pane.plist can be found. Selector signaturesvaluesDataSource and titlesDataSourcevaluesDataSource and titlesDataSource are performed on the target sent from -[PSListController loadSpecifiersFromPlistName:target:]. They must return an NSArray containing the values and (localized) titles respectively. The signature should be: -(NSArray*)dataFromTarget:(id)target; get and bestGuessget and bestGuess should have signature -(NSObject*)somethingForSpecifier:(PSSpecifier*)spec; The return value depends on the type of specifier, e.g. for a text field it should return an NSString, while for a switch it should return an NSNumber with boolValue. setset should have signature -(void)setSomething:(NSObject*)sth forSpecifier:(PSSpecifier*)spec; actionaction should have signature -(void)speciferPerformedAction:(PSSpecifier*)spec; Of course, you can ignore extra parameters, so -(void)specifierPerformedAction is a valid signature too. DisassemblyThis disassembly of -[PSListController loadSpecifiersFromPlistName:target:] is necessary to understand the signature of SpecifiersFromPlist() -(NSArray*)loadSpecifiersFromPlistName:(NSString*)plistName target:(id)target {
NSBundle* curBundle = [self bundle];
NSDictionary* plist = [[NSDictionary alloc] initWithContentsOfFile:[curBundle pathForResource:plistName ofType:@"plist"]];
NSString* specifierID;
NSArray* result = SpecifiersFromPlist(
plist,
self->_specifier,
target,
plistName,
curBundle,
&self->_title,
&specifierID,
self,
&self->_bundleControllers
);
[plist release];
self.specifierID = specifierID;
[specifierID release];
return result;
}From this we see that the signature is NSArray* SpecifiersFromPlist ( NSDictionary* plist, // r0 PSSpecifier* prevSpec, // r1 id target, // r2 NSString* plistName, // r3 NSBundle* curBundle, // sp[0x124] NSString** pTitle, // sp[0x128] NSString** pSpecifierID, // sp[0x12C] PSListController* callerList, // sp[0x130] NSMutableArray** pBundleControllers // sp[0x134] ); (The 0x124 offset is due to stmdb of 8 registers and 0x104 bytes reserved for local variables.) References | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||