(Added information on PSEditableListController) |
|||
Line 392: | Line 392: | ||
... | ... | ||
A <code>cell</code> doesn't have to be specified for custom cells. | A <code>cell</code> doesn't have to be specified for custom cells. | ||
=== Making an editable PSListController === | |||
Making a PSListController editable, so you can preform "swipe-to-delete" on the rows is easy at runtime. | |||
You just have to use a subclass of <code>PSEditableListController</code> (which is a subclass of <code>PSListController</code>) instead of a subclass of <code>PSListController</code> for your List Controller. For example: | |||
<source lang="objc"> | |||
@interface MyListController : PSEditableListController {} | |||
@end | |||
@implementation MyListController | |||
- (id)specifiers | |||
{ | |||
if (!_specifiers){ | |||
//add a sample specifier to the list | |||
PSSpecifier* testSpecifier = [PSSpecifier preferenceSpecifierNamed:@"test" | |||
target:self | |||
set:nil | |||
get:nil | |||
detail:nil | |||
cell:PSTitleValueCell | |||
edit:0]; | |||
_specifiers = [NSArray arrayWithObjects:testSpecifier, nil]; | |||
} | |||
return _specifiers; | |||
} | |||
@end | |||
</source> | |||
Please note, you will only be able to delete a specifier, if it's class is either <code>PSLinkListCell</code>, <code>PSListItemCell</code> or <code>PSTitleValueCell</code> | |||
To perform a custom action when the specifier gets deleted, you have to implent a method in your PSEditableListController subclass like this one | |||
<source lang="objc"> | |||
-(void)removedSpecifier:(PSSpecifier*)specifier{ | |||
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"Removing specifier: " | |||
message:[specifier name] | |||
delegate:self | |||
cancelButtonTitle:@"OK" | |||
otherButtonTitles:nil]; | |||
[alert show]; | |||
} | |||
</source> | |||
and set the deletionAction Key of the specifier: | |||
<source lang="objc"> | |||
extern NSString* PSDeletionActionKey; | |||
/*construct the PSSpecifier* testSpecifier here | |||
.... | |||
*/ | |||
[testSpecifier setProperty:NSStringFromSelector(@selector(removedSpecifier:)) forKey:PSDeletionActionKey]; | |||
</source> | |||
== References == | == References == | ||
* http://code.google.com/p/networkpx/wiki/PreferencesSpecifierPlistFormat#valuesDataSource_and_titlesDataSource | * http://code.google.com/p/networkpx/wiki/PreferencesSpecifierPlistFormat#valuesDataSource_and_titlesDataSource |
Revision as of 13:14, 16 April 2014
This document provides the specification of the .plist file that specifies the layout of an iPhone preference pane.
Root level
The root level of the plist may contain these keys:
struct <preferences specifier plist> ::
key | type | meaning | depends | |
---|---|---|---|---|
title | string | localizable string | Title of the preference pane. | - |
items | array | ... of <entry>s | Array of specifier definitions. | - |
id | string | Specifier ID. | - |
Localization
Some strings, e.g. the title of the preference pane is localizable. Preferences.framework can localize these strings by looking for the corresponding key in localizationTable.strings in bundle. If you are writing a PreferenceBundles, the localizationTable is the name of the specifier plist and bundle is of course the PreferenceBundle itself.
For example, if the plist is named MySettings.plist, then the corresponding strings file should be named MySettings.strings.
Specifier Entries
You can use any keys that your controller recognizes in the plist for further customization. This table lists the internal ones:
struct <entry> ::
General keys
key | type | meaning | depends | |
---|---|---|---|---|
requiredCapabilities | array | ... of <capability>s | Required capabilities of the device such that this specifier can be shown. | - |
cell | string | <cell type> | Specifier cell type. | - |
label | string | localizable string | Label of specifier. | - |
id | string | Specifier ID. | - | |
get | string | selector | Getter. | - |
set | string | selector | Setter. | - |
action | string | selector | Action. | - |
enabled | boolean | Whether the control is enabled by default. | - | |
defaults | string | bundle ID | The user defaults associated with this specifier. | - |
key | string | Key of the user defaults. | defaults ≠ nil | |
default | any | Default value of control. | - | |
height | float | Height of the cell. | - | |
dontIndentOnRemove | boolean | Prevents the cell from being indented when the table view is in editing mode. | - | |
negate | boolean | If the key in the user defaults is a boolean, invert the value displayed. | - | |
PostNotification | string | Darwin Notification to post when the preference is changed. | - |
Here, cell must be one of:
- enum <cell type> ::
- PSGroupCell
- PSLinkCell
- PSLinkListCell
- PSListItemCell
- PSTitleValueCell
- PSSliderCell
- PSSwitchCell
- PSStaticTextCell
- PSEditTextCell
- PSSegmentCell
- PSGiantIconCell
- PSGiantCell
- PSSecureEditTextCell
- PSButtonCell
- PSEditTextViewCell
The cell type is actually determined from the class method +[PSTableCell cellTypeFromString:].
bundle-dependent keys
The following keys are meaningful when bundle is present. It is useful for loading extra resources and custom code.
key | type | meaning | depends | |
---|---|---|---|---|
bundle | string | filename | Bundle file name. This bundle will be loaded for additional resources. | - |
internal | boolean | Directory to search for the bundle. If true, search in /AppleInternal/Library/PreferenceBundles/. If false, search in /System/Library/PreferenceBundles/. |
bundle ≠ nil | |
isController | boolean | Whether the bundle contains a controller class. | bundle ≠ nil | |
overridePrincipalClass | boolean | Overrides the principal class by the detail controller when bundle has a controller. | isController = true | |
detail | string | class name | Detail controller class. | - |
pane | string | class name | Edit pane class. If bundle is absent, the edit pane class is obtained from the current bundle. Default value is PSEditingPane. |
detail ≠ nil |
hasIcon | boolean | Whether the specifier will have an icon. | bundle ≠ nil | |
icon | string | filename | File name of the icon to use. Default value is icon.png. The height of the icon should be 29px. | hasIcon = true |
cellClass | string | class name | Customized cell class | - |
customControllerClass | string | class name | Custom controller class to use when the view become visible | - |
get, set and action should respectively have signatures
-(id)getSomethingForSpecifier:(PSSpecifier*)spec;
-(void)setSomething:(id)something forSpecifier:(PSSpecifier*)spec;
-(void)speciferPerformedAction:(PSSpecifier*)spec;
where the type of "something" depends on the type of specifier, e.g. for a text field it should be an NSString, while for a switch it should be an NSNumber with boolean.
Of course, you can ignore extra parameters, e.g. -(void)specifierPerformedAction is a valid signature too.
Editing cells
These keys are specific to editing cells.
key | type | meaning | depends | |
---|---|---|---|---|
autoCaps | string | {"sentences", "words", "all"} | Autocapitalization type for cells that requires a keyboard. | - |
keyboard | string | {"numbers", "phone"} | Type of keyboard. | - |
prompt | string | localizable string | Setup prompt. | cell ∈ {"PSEditTextCell", "PSSecureEditTextCell"} |
okTitle | string | localizable string | Title for OK button in setup prompt. | prompt ≠ nil |
cancelTitle | string | localizable string | Title for cancel button in setup prompt. | prompt ≠ nil |
placeholder | string | localizable string | Placeholder. | cell ∈ {"PSEditTextCell", "PSSecureEditTextCell"} |
suffix | string | localizable string | Suffix. | cell ∈ {"PSEditTextCell", "PSSecureEditTextCell"} |
isIP | boolean | Input field intended for entering IP address (Use Numbers keyboard). | - | |
isURL | boolean | Input field intended for entering URL (Use URL keyboard). | - | |
isNumeric | boolean | Input field intended for entering numbers (Use NumberPad keyboard). | - | |
isEmail | boolean | Input field intended for entering e-mail (Use EmailAddress keyboard). | - | |
isEmailAddressing | boolean | ? | - | |
bestGuess | string | selector | Initial value of text field. | cell ∈ {"PSEditTextCell", "PSSecureEditTextCell"} |
noAutoCorrect | boolean | Disable auto-correction. | cell ∈ {"PSEditTextCell", "PSSecureEditTextCell"} |
List cells
These keys are specific to list cells.
key | type | meaning | depends | |
---|---|---|---|---|
validValues | array | ...of strings | List of values to choose from. | cell ∈ {"PSLinkListCell", "PSSegmentCell"} |
validTitles | array | ...of localizable strings | Titles corresponding to the list of values. | cell ∈ {"PSLinkListCell", "PSSegmentCell"} |
shortTitles | array | ...of localizable strings | Short titles. | cell ∈ {"PSLinkListCell", "PSSegmentCell"} |
valuesDataSource | string | selector | Selector to call to get the list of values dynamically. | cell ∈ {"PSLinkListCell", "PSSegmentCell"} and validValues = nil |
titlesDataSource | string | selector | Selector to call to get the list of titles dynamically. | cell ∈ {"PSLinkListCell", "PSSegmentCell"} and validTitles = nil |
staticTextMessage | string | localizable string | The message to be added below the cells list of PSLinkListCell. | cell = "PSLinkListCell" |
valuesDataSource and titlesDataSource are performed on the target sent from -[PSListController loadSpecifiersFromPlistName:target:]. They must return an NSArray containing the values and (localized) titles respectively. Their signatures should be
-(NSArray*)dataFromTarget:(id)target;
Slider and switch cells
These keys are specific to slider and switch cells.
key | type | meaning | depends | |
---|---|---|---|---|
rightImage | string | filename | Image displayed next to the slider on the right. | cell = "PSSliderCell" |
leftImage | string | filename | Image displayed next to the slider on the left. | cell = "PSSliderCell" |
min | float | Minimum value of slider. | cell = "PSSliderCell" | |
max | float | Maximum value of slider. | cell = "PSSliderCell" | |
showValue | boolean | Show the value. | cell = "PSSliderCell" | |
alternateColors | boolean | Use an orange switch instead of blue. | cell = "PSSwitchCell", iOS < 7 |
Miscellaneous control cells
These keys are specific to other control cells.
key | type | meaning | depends | |
---|---|---|---|---|
alignment | integer | Text alignment. 1 = center. | cell ∈ {"PSGroupCell", "PSStaticTextCell" | |
confirmation | dictionary | <confirmation> | Definitions of the confirmation sheet before action is performed. | cell ∈ {"PSSwitchCell", "PSButtonCell" |
isDestructive | boolean | Whether the action to be performed is destructive. The OK button will be in red if true. | confirmation ≠ nil | |
isStaticText | boolean | Whether the cells in this group has static text. Used in conjunction with PSStaticTextCell. | cell = "PSGroupCell" | |
footerText | string | Text displayed in a small font after this specifier (or, in the case of a PSGroupCell, the last specifier in the group). | - | |
headerCellClass | class | The cell class using which to render the header. | - | |
footerCellClass | class | The cell class using which to render the footer. | - |
footerAlignment: 1 centers the footer text (doesn't always look good for multiple lines)
Here, confirmation itself is a dictionary containing the following fields:
struct <confirmation> ::
key | type | meaning | |
---|---|---|---|
prompt | string | localizable string | Content of confirmation sheet. |
cancelTitle | string | localizable string | Title of the cancel button. |
okTitle | string | localizable string | Title of the OK button. |
title | string | localizable string | Title of confirmation sheet. |
PSSpecifier properties of plist keys
The tables above only shows the keys recognized by SpecifiersForPlist when translating the plist into an array of PSSpecifiers. They may be corresponds to the actual properties of the specifier. If you would like to generate a PSSpecifier in runtime, some actions may differ:
keys | corresponding action |
---|---|
cell | Use the constructor, or change the cellType ivar. |
label | Use the name declared property. |
get | Use the constructor, or change the getter ivar. |
set | Use the constructor, or change the setter ivar. |
action | Change the action ivar. |
default | Use the value property. |
icon | -[PSSpecifier setProperty:forKey:] Use the iconImage and UIImage as a key and property, or -[PSSpecifier setupIconImageWithPath:] |
autoCaps, keyboard, noAutoCorrect | -[PSSpecifier setKeyboardType:autoCaps:autoCorrection:] |
isIP, isURL, isNumeric, isEmail, isEmailAddressing | Change the textFieldType ivar. |
bestGuess | Change the bestGuess ivar of the PSTextFieldSpecifier class. |
validValues, validTitles, shortTitles | -[PSSpecifier setValues:titles:shortTitles:] |
confirmation | Create an instance of PSConfirmationSpecifier. |
Recipes
Using PSLinkListCell
In order to make a PSLinkListCell actually work like a list, you must supply the key-value pair
detail = "PSListItemsController";
also.
Using PSLinkCell
PSLinkCell 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 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
@end
The 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.
Constructing a PSLinkCell at runtime
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->action = @selector(lazyLoadBundle:);
// Add specifier to the PSListController
Making a red delete button
The red delete button in VPN is in fact very easy to implement. All you need to do is add the following code:
#import <UIKit/UIPreferencesDeleteTableCell.h>
@interface PSDeleteTableCell : UIPreferencesDeleteTableCell
@end
@implementation PSDeleteTableCell
-(void)setValueChangedTarget:(id)target action:(SEL)action userInfo:(NSDictionary*)info {
[self setTarget:target];
[self setAction:action];
}
-(UILabel*)titleTextLabel {
UILabel* res = [super titleTextLabel];
res.textColor = [UIColor whiteColor];
return res;
}
@end
and then in the specifier plist, modify your button as:
... { cell = PSButtonCell; action = nukeFromOrbit; label = "Nuke from Orbit"; ... cellClass = PSDeleteTableCell; }, ...
Note that UIPreferencesDeleteTableCell
no longer exists as of iOS 6.0. It appears to be replaced by a boolean on PSButtonCell, isDestructive
.
Making a custom cell, header or footer is useful because it allows you to customize the style, add an image, etc.
All you need to do is make a class that looks like:
@interface CustomCell : PSTableCell <PreferencesTableCustomView> {
UILabel *_label;
}
@end
@implementation CustomCell
- (id)initWithSpecifier:(PSSpecifier *)specifier
{
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell" specifier:specifier];
if (self) {
CGRect frame = [self frame];
_label = [[UILabel alloc] initWithFrame:frame];
[_label setLineBreakMode:UILineBreakModeWordWrap];
[_label setNumberOfLines:0];
[_label setText:@"You can use attributed text to make this prettier."];
[_label setBackgroundColor:[UIColor clearColor]];
[_label setShadowColor:[UIColor whiteColor]];
[_label setShadowOffset:CGSizeMake(0,1)];
[_label setTextAlignment:UITextAlignmentCenter];
[self addSubview:_label];
[_label release];
}
return self;
}
- (float)preferredHeightForWidth:(float)arg1
{
// Return a custom cell height.
return 60.f;
}
@end
Then, set the cellClass
, headerCellClass
or footerCellClass
in your specifier. For example:
... { cell = PSGroupCell; footerCellClass = CustomCell; }, ...
A cell
doesn't have to be specified for custom cells.
Making an editable PSListController
Making a PSListController editable, so you can preform "swipe-to-delete" on the rows is easy at runtime.
You just have to use a subclass of PSEditableListController
(which is a subclass of PSListController
) instead of a subclass of PSListController
for your List Controller. For example:
@interface MyListController : PSEditableListController {}
@end
@implementation MyListController
- (id)specifiers
{
if (!_specifiers){
//add a sample specifier to the list
PSSpecifier* testSpecifier = [PSSpecifier preferenceSpecifierNamed:@"test"
target:self
set:nil
get:nil
detail:nil
cell:PSTitleValueCell
edit:0];
_specifiers = [NSArray arrayWithObjects:testSpecifier, nil];
}
return _specifiers;
}
@end
Please note, you will only be able to delete a specifier, if it's class is either PSLinkListCell
, PSListItemCell
or PSTitleValueCell
To perform a custom action when the specifier gets deleted, you have to implent a method in your PSEditableListController subclass like this one
-(void)removedSpecifier:(PSSpecifier*)specifier{
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"Removing specifier: "
message:[specifier name]
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
and set the deletionAction Key of the specifier:
extern NSString* PSDeletionActionKey;
/*construct the PSSpecifier* testSpecifier here
....
*/
[testSpecifier setProperty:NSStringFromSelector(@selector(removedSpecifier:)) forKey:PSDeletionActionKey];