Preferences specifier plist: Difference between revisions

From iPhone Development Wiki
(→‎Root level: Clarification on title key.)
m (Unnecessary change inconsistent with standard)
Line 1: Line 1:
This document provides the specification of the dictionaries that specify the layout of an iOS preference pane. These can be supplied statically via .plist files or dynamically via the <code>_specifiers</code> ivar in a subclass of [[PSViewController]].
This document provides the specification of the dictionaries that specify the layout of an iOS preference pane. These can be supplied statically via .plist files or dynamically via the <code>_specifiers</code> ivar in a subclass of [[PSViewController]].


= Root level =
== Root level ==


The root level of the plist may contain these keys:
The root level of the plist may contain these keys:
Line 18: Line 18:
The ''title'' key is used only for the root plist, otherwise, the ''label'' value from the specifier that pushed the new pane is used.
The ''title'' key is used only for the root plist, otherwise, the ''label'' value from the specifier that pushed the new pane is used.


= Specifier Keys and Values =
== Specifier Keys and Values ==


You can use any keys that your controller recognizes in the plist for further customization.
You can use any keys that your controller recognizes in the plist for further customization.
Line 24: Line 24:
''General to-do: Add explanations of each specifier and potentially a screenshot of it (gif for the spinner?).''
''General to-do: Add explanations of each specifier and potentially a screenshot of it (gif for the spinner?).''


== Cell types ==
=== Cell types ===


Must be one of the following:
Must be one of the following:
Line 46: Line 46:
The cell type is determined from the class method {{ObjcCall|PSTableCell|ClassMethod=1|cellTypeFromString:}}.
The cell type is determined from the class method {{ObjcCall|PSTableCell|ClassMethod=1|cellTypeFromString:}}.


== General keys ==
=== General keys ===


{| class="wikitable"
{| class="wikitable"
Line 95: Line 95:
* <code>-(id)readPreferenceValue:(PSSpecifier*)specifier;</code>
* <code>-(id)readPreferenceValue:(PSSpecifier*)specifier;</code>


== PSButtonCell ==
=== PSButtonCell ===


{| class="wikitable"
{| class="wikitable"
Line 125: Line 125:
''To do: confirm this last table. Tests suggest "prompt" is the title, "title" is the okTitle, "okTitle" is MIA, cancelTitle works as stated. Investigate history of "isDestructive" key.''
''To do: confirm this last table. Tests suggest "prompt" is the title, "title" is the okTitle, "okTitle" is MIA, cancelTitle works as stated. Investigate history of "isDestructive" key.''


== PSEditTextCell & PSSecureEditTextCell ==
=== PSEditTextCell & PSSecureEditTextCell ===


{| class="wikitable"
{| class="wikitable"
Line 164: Line 164:
''To do: Check "prompt", "okTitle" and "cancelTitle" as they appear to not work on iOS 6. Are these in the correct place?<br>Check what "suffix" does. Find "bestGuess" selector signature. Solve the mystery of "isEmailAddressing".''
''To do: Check "prompt", "okTitle" and "cancelTitle" as they appear to not work on iOS 6. Are these in the correct place?<br>Check what "suffix" does. Find "bestGuess" selector signature. Solve the mystery of "isEmailAddressing".''


== PSEditTextViewCell ==
=== PSEditTextViewCell ===


This specifier doesn't show a label so it is recommended to use a previous specifier to indicate what this specifier represents.
This specifier doesn't show a label so it is recommended to use a previous specifier to indicate what this specifier represents.
Line 172: Line 172:
Saved value is HTML text, <tt>&lt;div&gt;</tt> being the root tag block.
Saved value is HTML text, <tt>&lt;div&gt;</tt> being the root tag block.


== PSGiantCell ==
=== PSGiantCell ===


This specifier doesn't show a label so it is recommended to use a specifier before this one to indicate what this specifier represents.
This specifier doesn't show a label so it is recommended to use a specifier before this one to indicate what this specifier represents.


== PSGiantIconCell ==
=== PSGiantIconCell ===


This specifier uses the ''label'' key as text content.
This specifier uses the ''label'' key as text content.
Line 184: Line 184:
''To do: Find out the actual font and icon sizes.''
''To do: Find out the actual font and icon sizes.''


== PSGroupCell ==
=== PSGroupCell ===


{| class="wikitable"
{| class="wikitable"
Line 207: Line 207:
''To do: Research "isStaticText" interaction with "PSStaticTextCell".''
''To do: Research "isStaticText" interaction with "PSStaticTextCell".''


== PSLinkCell ==
=== PSLinkCell ===


{| class="wikitable"
{| class="wikitable"
Line 232: Line 232:
If none of these keys are specified, the framework will try to load a plist using the label text as name.
If none of these keys are specified, the framework will try to load a plist using the label text as name.


== PSListItemCell ==
=== PSListItemCell ===


This specifier makes use of the <tt>get</tt> key to get the value in the runtime.
This specifier makes use of the <tt>get</tt> key to get the value in the runtime.


== PSLinkListCell & PSSegmentCell ==
=== PSLinkListCell & PSSegmentCell ===


{| class="wikitable"
{| class="wikitable"
Line 263: Line 263:
  detail = PSListItemsController;
  detail = PSListItemsController;


== PSSliderCell ==
=== PSSliderCell ===


{| class="wikitable"
{| class="wikitable"
Line 292: Line 292:
''To Do: figure out if the step is a constant or a proportion (1/10? 1/16?) of the slider range.''
''To Do: figure out if the step is a constant or a proportion (1/10? 1/16?) of the slider range.''


== PSSpinnerCell ==
=== PSSpinnerCell ===


PSSpinnerCell is a simple cell with an animating spinner (UIActivityIndicatorView) inside it. Available only in iOS 7 (or higher)<sup>[citation needed]</sup>. There is no way to start or stop the animation manually, it is always running. Therefore, this cell is meant to be dynamically inserted and deleted from a PSListController subclass.
PSSpinnerCell is a simple cell with an animating spinner (UIActivityIndicatorView) inside it. Available only in iOS 7 (or higher)<sup>[citation needed]</sup>. There is no way to start or stop the animation manually, it is always running. Therefore, this cell is meant to be dynamically inserted and deleted from a PSListController subclass.


== PSStaticTextCell ==
=== PSStaticTextCell ===


This specifier uses the ''label'' key as text content.
This specifier uses the ''label'' key as text content.


== PSSwitchCell ==
=== PSSwitchCell ===


{| class="wikitable"
{| class="wikitable"
Line 317: Line 317:
See [[Preferences_specifier_plist#PSSwitchCell_2|PSSwitchCell]] in the Recipes section for solving an issue on iOS 7.
See [[Preferences_specifier_plist#PSSwitchCell_2|PSSwitchCell]] in the Recipes section for solving an issue on iOS 7.


== PSTitleValueCell ==
=== PSTitleValueCell ===


This specifier makes use of the <tt>get</tt> key to get the value in the runtime.
This specifier makes use of the <tt>get</tt> key to get the value in the runtime.


== Miscellaneous ==
=== Miscellaneous ===


{| class="wikitable"
{| class="wikitable"
Line 332: Line 332:
''To do: check which specifiers support this key.''
''To do: check which specifiers support this key.''


= <tt>PSSpecifier</tt> runtime properties of plist keys =
== <tt>PSSpecifier</tt> runtime properties of plist keys ==


The table below only shows the keys recognized by <tt>SpecifiersForPlist</tt> when translating the plist into an array of <tt>PSSpecifier</tt>s. They may correspond to the actual properties of the specifier. If you would like to generate a <tt>PSSpecifier</tt> in runtime, some actions may differ:
The table below only shows the keys recognized by <tt>SpecifiersForPlist</tt> when translating the plist into an array of <tt>PSSpecifier</tt>s. They may correspond to the actual properties of the specifier. If you would like to generate a <tt>PSSpecifier</tt> in runtime, some actions may differ:
Line 367: Line 367:
|}
|}


= Recipes =
== Recipes ==


{{main|PreferenceBundles}}
{{main|PreferenceBundles}}
Line 385: Line 385:
Using <tt>@selector(setPreferenceValue:specifier:)</tt> as setter and <tt>@selector(readPreferenceValue:)</tt> as getter will ease the load on the work as the specifier will use its properties to make itself work, namely "defaults", "key", "default", "PostNotification".
Using <tt>@selector(setPreferenceValue:specifier:)</tt> as setter and <tt>@selector(readPreferenceValue:)</tt> as getter will ease the load on the work as the specifier will use its properties to make itself work, namely "defaults", "key", "default", "PostNotification".


== PSButtonCell ==
=== PSButtonCell ===


The red delete button in VPN is in fact very easy to implement. All you need to do is add the following code:
The red delete button in VPN is in fact very easy to implement. All you need to do is add the following code:
Line 430: Line 430:
Note that <code>UIPreferencesDeleteTableCell</code> no longer exists as of iOS 6.0. It appears to be replaced by a boolean on PSButtonCell, <code>isDestructive</code>.
Note that <code>UIPreferencesDeleteTableCell</code> no longer exists as of iOS 6.0. It appears to be replaced by a boolean on PSButtonCell, <code>isDestructive</code>.


== PSEditTextCell & PSSecureEditTextCell ==
=== PSEditTextCell & PSSecureEditTextCell ===


To make the <tt>Return</tt> key close the keyboard add the following method to your PSListController subclass:
To make the <tt>Return</tt> key close the keyboard add the following method to your PSListController subclass:
Line 441: Line 441:
</source>
</source>


== PSLinkCell ==
=== PSLinkCell ===


PSLinkCell is useful for linking to sub-preference-panes. The simplest example just needs 2 keys:
PSLinkCell is useful for linking to sub-preference-panes. The simplest example just needs 2 keys:
Line 470: Line 470:
The key thing is when you place MyListController inside your bundle, its bundle property will return your bundle which <tt>My Awesome Pane.plist</tt> can be found.
The key thing is when you place MyListController inside your bundle, its bundle property will return your bundle which <tt>My Awesome Pane.plist</tt> can be found.


== PSSwitchCell ==
=== PSSwitchCell ===


Since iOS 7.0 and above the "alternateColors" key does not work. To be able to change the color of a switch, you must subclass PSSwitchTableCell in a [[PreferenceBundles|PreferenceBundle]] as follows:  
Since iOS 7.0 and above the "alternateColors" key does not work. To be able to change the color of a switch, you must subclass PSSwitchTableCell in a [[PreferenceBundles|PreferenceBundle]] as follows:  
Line 506: Line 506:
A detailed tutorial about this has been written on [http://sharedroutine.com/?p=143 sharedroutine.com].
A detailed tutorial about this has been written on [http://sharedroutine.com/?p=143 sharedroutine.com].


== PSTitleValueCell ==
=== PSTitleValueCell ===


To display a value like <code>Preferences -> General -> About</code>, add the cell to your plist:
To display a value like <code>Preferences -> General -> About</code>, add the cell to your plist:
Line 524: Line 524:
</source>
</source>


== Custom cells ==
=== Custom cells ===


Making a custom cell, header or footer is useful because it allows you to customize the style, add an image, etc.
Making a custom cell, header or footer is useful because it allows you to customize the style, add an image, etc.
Line 579: Line 579:
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.


== Constructing a PSLinkCell at runtime ==
=== Constructing a PSLinkCell at runtime ===


If you want to dynamically add a specifier for a PSLinkCell that lazy-loads a bundle, you can do it like this:
If you want to dynamically add a specifier for a PSLinkCell that lazy-loads a bundle, you can do it like this:
Line 598: Line 598:
</source>
</source>


== Constructing a PSLinkListCell at runtime ==
=== Constructing a PSLinkListCell at runtime ===


If you want to dynamically add a specifier for a PSLinkListCell you can do it like this:
If you want to dynamically add a specifier for a PSLinkListCell you can do it like this:
Line 621: Line 621:
</source>
</source>


== Making an editable PSListController ==
=== Making an editable PSListController ===


With this you can perform a "swipe-to-delete" on the rows.  
With this you can perform a "swipe-to-delete" on the rows.  
Line 689: Line 689:
</source>
</source>


== Custom Settings Libraries ==
=== Custom Settings Libraries ===


* https://github.com/mlnlover11/SettingsKit/wiki
* https://github.com/mlnlover11/SettingsKit/wiki
Line 695: Line 695:
* http://ibraveheartdev.co/PrefUp
* http://ibraveheartdev.co/PrefUp


= Plist Only =
== Plist Only ==


''To do: add more difficult recipes, such as using the PSGroupCell properly, and making sub-preferences using the PSLinkCell''
''To do: add more difficult recipes, such as using the PSGroupCell properly, and making sub-preferences using the PSLinkCell''
Line 701: Line 701:
These will work as an independent plist, when put into "/Library/PreferenceLoader/Preferences". You need no Objective C or anything for these, just the plist file. This can be useful for adding preferences to things like [[Cydget]]s. Other examples from tweaks that use just the plist can be found in that same location.
These will work as an independent plist, when put into "/Library/PreferenceLoader/Preferences". You need no Objective C or anything for these, just the plist file. This can be useful for adding preferences to things like [[Cydget]]s. Other examples from tweaks that use just the plist can be found in that same location.


== Required Structure ==
=== Required Structure ===


This is the required outline to add a custom plist to the Preferences app.
This is the required outline to add a custom plist to the Preferences app.
Line 731: Line 731:
The various cells and items are put between the array tags.
The various cells and items are put between the array tags.


== Example with PSEditTextCell ==
=== Example with PSEditTextCell ===


Though there is nothing all that special about this cell, it is a good example of how to make other cells.
Though there is nothing all that special about this cell, it is a good example of how to make other cells.
Line 755: Line 755:
This would make an input text cell for numbers. Use the keys specified above to change its properties and appearance. This goes for any cell.
This would make an input text cell for numbers. Use the keys specified above to change its properties and appearance. This goes for any cell.


= External links =
== External links ==


* https://code.google.com/p/networkpx/wiki/PreferencesSpecifierPlistFormat
* https://code.google.com/p/networkpx/wiki/PreferencesSpecifierPlistFormat


[[Category:Preferences]]
[[Category:Preferences]]

Revision as of 03:19, 11 April 2016

This document provides the specification of the dictionaries that specify the layout of an iOS preference pane. These can be supplied statically via .plist files or dynamically via the _specifiers ivar in a subclass of PSViewController.

Root level

The root level of the plist may contain these keys:

key type meaning depends
entry dictionary Specifier-like dictionary. -
title string localizable string Title of the preference pane. entry ≠ nil
items array ... of specifiers Array of specifier dictionaries. -

The title key is used only for the root plist, otherwise, the label value from the specifier that pushed the new pane is used.

Specifier Keys and Values

You can use any keys that your controller recognizes in the plist for further customization.

General to-do: Add explanations of each specifier and potentially a screenshot of it (gif for the spinner?).

Cell types

Must be one of the following:

The cell type is determined from the class method +[PSTableCell cellTypeFromString:].

General keys

key type meaning depends
cell string <cell type> Specifier cell type. -
label string localizable string Label of specifier. -
defaults string bundle ID User defaults associated with this specifier. -
key string Key of the user defaults. defaults ≠ nil
default any Default value of control. -
id string Specifier ID. -
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
get string selector Getter. -
set string selector Setter. -
enabled boolean Whether the control is enabled by default. -
height float Height of the cell. -
PostNotification string Darwin notification string to be posted when a change happens. -
detail string class name Detail controller class. -
cellClass string class name Customized cell class -
requiredCapabilities array ... of <capability>s Required capabilities of the device such that this specifier can be shown. -
dontIndentOnRemove boolean Prevents the cell from being indented when the table view is in editing mode. -

Investigate hasIcon key dependency since all but PSGroupCell display an icon if supplied.

Keys apply to all cells unless otherwise stated in their respective subsection.

Signature and default methods for the set and get selectors:

  • -(void)setPreferenceValue:(id)value forSpecifier:(PSSpecifier*)specifier;
  • -(id)readPreferenceValue:(PSSpecifier*)specifier;

PSButtonCell

key type meaning depends
action string selector Action.
Signature: -(void)speciferPerformedAction:(PSSpecifier*)specifier;
or -(void)speciferPerformedAction;
-
confirmation dictionary <confirmation> Definitions of the confirmation sheet before action is performed. -
isDestructive boolean Whether the action to be performed is destructive. The OK button will be in red if true if less than iOS7. confirmation ≠ nil

The confirmation value is a dictionary containing the following fields:

key type meaning
prompt string localizable string Content of confirmation sheet.
title string localizable string Title of confirmation sheet.
okTitle string localizable string Title of the OK button.
cancelTitle string localizable string Title of the cancel button.

To do: confirm this last table. Tests suggest "prompt" is the title, "title" is the okTitle, "okTitle" is MIA, cancelTitle works as stated. Investigate history of "isDestructive" key.

PSEditTextCell & PSSecureEditTextCell

key type meaning depends
keyboard string {"numbers", "phone"} Type of keyboard. nil or invalid values uses a normal keyboard. -
autoCaps string {"sentences", "words", "all"} Autocapitalization type for cells that requires a keyboard. keyboard = nil
placeholder string localizable string Placeholder. -
suffix string localizable string Suffix. -
bestGuess string selector Initial value of text field. -
noAutoCorrect boolean Disable auto-correction. -
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). -
isDecimalPad boolean Input field intended for entering decimal numbers (Use DecimalPad keyboard). -
isEmail boolean Input field intended for entering e-mail (Use EmailAddress keyboard). -
isEmailAddressing boolean ? -
prompt string localizable string Setup prompt. -
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

To do: Check "prompt", "okTitle" and "cancelTitle" as they appear to not work on iOS 6. Are these in the correct place?
Check what "suffix" does. Find "bestGuess" selector signature. Solve the mystery of "isEmailAddressing".

PSEditTextViewCell

This specifier doesn't show a label so it is recommended to use a previous specifier to indicate what this specifier represents.

The text area expands to fit the whole cell, so setting a height key will provide more space to write.

Saved value is HTML text, <div> being the root tag block.

PSGiantCell

This specifier doesn't show a label so it is recommended to use a specifier before this one to indicate what this specifier represents.

PSGiantIconCell

This specifier uses the label key as text content.

The font in this cell is bigger than normal.

To do: Find out the actual font and icon sizes.

PSGroupCell

key type meaning depends
footerText string Text displayed in a small font after this specifier. -
footerAlignment integer 0:left; 1:center; 2:right; defaults to 1. -
headerCellClass string The class name to use for the header. -
footerCellClass string The class name to use for the footer. -
isStaticText boolean Whether the cells in this group have static text. -

If you want to make a Image Header read this. Hides the group title (label key).

isStaticText is used in conjunction with PSStaticTextCell.

To do: Research "isStaticText" interaction with "PSStaticTextCell".

PSLinkCell

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
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
customControllerClass string class name Custom controller class to use when the view become visible -
lazy-bundle NSBundle A bundle. This bundle will be loaded for additional resources. -

This specifier is useful for loading extra resources and custom code.

If none of these keys are specified, the framework will try to load a plist using the label text as name.

PSListItemCell

This specifier makes use of the get key to get the value in the runtime.

PSLinkListCell & PSSegmentCell

key type meaning depends
validValues array ...of strings List of values to choose from. -
validTitles array ...of localizable strings Titles corresponding to the list of values. -
shortTitles array ...of localizable strings Short titles corresponding to the list of values, displayed in the link cell. -
valuesDataSource string selector Selector to call to get the list of values dynamically. validValues = nil
titlesDataSource string selector Selector to call to get the list of titles dynamically. validTitles = nil
staticTextMessage string localizable string The message to be added below the cells list of PSListItemsController. 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;

In order to make a PSLinkListCell actually work like a list, you must supply the key-value pair

	detail = PSListItemsController;

PSSliderCell

key type meaning depends
min float Minimum value of slider. -
max float Maximum value of slider. -
showValue boolean Show the value. -
isContinuous boolean Change the value by steps. Possibly deprecated. -
isSegmented boolean Change the value by steps. -
segmentCount integer Steps count. -
leftImage string filename Image displayed next to the slider on the left. -
rightImage string filename Image displayed next to the slider on the right. -

This specifier doesn't show a label so it is recommended to use a previous specifier to indicate what this specifier represents.

Image files must reside in the same directory. They can be symlinks to files elsewhere though. If showValue is set to true it might overlap with the right image.

To Do: figure out if the step is a constant or a proportion (1/10? 1/16?) of the slider range.

PSSpinnerCell

PSSpinnerCell is a simple cell with an animating spinner (UIActivityIndicatorView) inside it. Available only in iOS 7 (or higher)[citation needed]. There is no way to start or stop the animation manually, it is always running. Therefore, this cell is meant to be dynamically inserted and deleted from a PSListController subclass.

PSStaticTextCell

This specifier uses the label key as text content.

PSSwitchCell

key type meaning depends
alternateColors boolean Use an orange switch instead of blue. iOS < 7
control-loading boolean Show an activity indicator view instead of switch (e.g. VPN cell) -
negate boolean Invert the value displayed. -

You can dynamically set the UISwitch's state by returning YES or NO in the get method. When the switch's state changes, the set method is called.

See PSSwitchCell in the Recipes section for solving an issue on iOS 7.

PSTitleValueCell

This specifier makes use of the get key to get the value in the runtime.

Miscellaneous

key type meaning depends
alignment integer Text alignment. 0:left; 1:center; 2:right; defaults to 1. cell ∈ {"PSGroupCell", "PSStaticTextCell"}

To do: check which specifiers support this key.

PSSpecifier runtime properties of plist keys

The table below only shows the keys recognized by SpecifiersForPlist when translating the plist into an array of PSSpecifiers. They may correspond 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.
enabled Must be set to YES (as NSNumber) to make the specifier interactive.

Recipes

Use the following snippet to create a PSSpecifier in the runtime:

	PSSpecifier* specifier = [PSSpecifier preferenceSpecifierNamed:@"title"
								target:self
								   set:@selector(setPreferenceValue:specifier:)
								   get:@selector(readPreferenceValue:)
								detail:Nil
								  cell:<cell type>
								  edit:Nil];
	[specifier setProperty:@YES forKey:@"enabled"];

Using @selector(setPreferenceValue:specifier:) as setter and @selector(readPreferenceValue:) as getter will ease the load on the work as the specifier will use its properties to make itself work, namely "defaults", "key", "default", "PostNotification".

PSButtonCell

The red delete button in VPN is in fact very easy to implement. All you need to do is add the following code:

@interface UITableCell : UIView
@end

@interface UIImageAndTextTableCell : UITableCell
-(id)titleTextLabel;
@end

@interface UIPreferencesTableCell : UIImageAndTextTableCell
-(void)setTarget:(id)target;
-(void)setAction:(SEL)action;
@end

@interface UIPreferencesControlTableCell : UIPreferencesTableCell
@end

@interface UIPreferencesDeleteTableCell : UIPreferencesControlTableCell
@end

@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 add the following key-value pair to the button specifier:

	cellClass = PSDeleteTableCell;

Note that UIPreferencesDeleteTableCell no longer exists as of iOS 6.0. It appears to be replaced by a boolean on PSButtonCell, isDestructive.

PSEditTextCell & PSSecureEditTextCell

To make the Return key close the keyboard add the following method to your PSListController subclass:

- (void)_returnKeyPressed:(NSConcreteNotification *)notification {
	[self.view endEditing:YES];
	[super _returnKeyPressed:notification];
}

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. In the example 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.

PSSwitchCell

Since iOS 7.0 and above the "alternateColors" key does not work. To be able to change the color of a switch, you must subclass PSSwitchTableCell in a PreferenceBundle as follows:

@interface PSTableCell : UITableViewCell
@end
@interface PSControlTableCell : PSTableCell
- (UIControl *)control;
@end
@interface PSSwitchTableCell : PSControlTableCell
- (id)initWithStyle:(int)style reuseIdentifier:(id)identifier specifier:(id)specifier;
@end

@interface SRSwitchTableCell : PSSwitchTableCell
@end

@implementation SRSwitchTableCell

-(id)initWithStyle:(int)style reuseIdentifier:(id)identifier specifier:(id)specifier { //init method
	self = [super initWithStyle:style reuseIdentifier:identifier specifier:specifier]; //call the super init method
	if (self) {
		[((UISwitch *)[self control]) setOnTintColor:[UIColor redColor]]; //change the switch color
	}
	return self;
}

@end

Then set this key-value pair for the specifier:

	cellClass = SRSwitchTableCell;

A detailed tutorial about this has been written on sharedroutine.com.

PSTitleValueCell

To display a value like Preferences -> General -> About, add the cell to your plist:

{
	cell = PSTitleValueCell;
	label = Version;
	get = "valueForSpecifier:";
}

And add the getter method to your controller:

- (NSString *)valueForSpecifier:(PSSpecifier *)specifier {
	return @"1.0";
}

Custom cells

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 PSTableCell : UITableViewCell
@end

@protocol PreferencesTableCustomView
- (id)initWithSpecifier:(PSSpecifier *)specifier;
- (CGFloat)preferredHeightForWidth:(CGFloat)width;
@end

@interface CustomCell : PSTableCell <PreferencesTableCustomView> {
	UILabel *_label;
}
@end

@implementation CustomCell
- (id)initWithSpecifier:(PSSpecifier *)specifier {
	self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell" specifier:specifier];
	if (self) {
		_label = [[UILabel alloc] initWithFrame:[self 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;
}

- (CGFloat)preferredHeightForWidth:(CGFloat)width {
	// 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.

Constructing a PSLinkCell at runtime

If you want to dynamically add a specifier for a PSLinkCell that lazy-loads a bundle, you can do it like this:

PSSpecifier* specifier = [PSSpecifier preferenceSpecifierNamed:@"title"
                                                        target:self
                                                           set:NULL
                                                           get:NULL
                                                        detail:Nil
                                                          cell:PSLinkCell
                                                          edit:Nil];

[specifier setProperty:@"/System/Library/PreferenceBundles/prefs.bundle" forKey:@"lazy-bundle"];
[specifier setAction:@selector(lazyLoadBundle:)];

// Now add the specifier to your controller.

Constructing a PSLinkListCell at runtime

If you want to dynamically add a specifier for a PSLinkListCell you can do it like this:

PSSpecifier* specifier = [PSSpecifier preferenceSpecifierNamed:@"title"
                                                        target:self
                                                           set:NULL
                                                           get:NULL
                                                        detail:NSClassFromString(@"PSListItemsController")
                                                          cell:PSLinkListCell
                                                          edit:Nil];

[specifier setProperty:@YES forKey:@"enabled"];
[specifier setProperty:@"0" forKey:@"default"];		
specifier.values = [NSArray arrayWithObjects:@"0",@"1",@"2",nil];
specifier.titleDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Title 1",@"Title 2",@"Title 3",nil] forKeys:specifier.values];
specifier.shortTitleDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"T1",@"T@",@"T3",nil] forKeys:specifier.values];
[specifier setProperty:@"kListValue" forKey:@"key"];

// Now add the specifier to your controller.

Making an editable PSListController

With this you can perform a "swipe-to-delete" on the rows.

You just have to use a subclass of PSEditableListController (which is a subclass of PSListController) for your List Controller. For example:

@interface PSViewController : UIViewController
@end
@interface PSListController : PSViewController
@end
@interface PSEditableListController : PSListController
@end
@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:NULL
									       get:NULL
									    detail:Nil
									      cell:PSTitleValueCell
									      edit:Nil];
		_specifiers = [[NSArray arrayWithObjects:testSpecifier, nil] retain];
	}
	return _specifiers;
}
@end

Please note, you will only be able to delete a specifier, if its class is either PSLinkListCell, PSListItemCell or PSTitleValueCell

However, you can make all specifier's deletable by adding the function

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
	return UITableViewCellEditingStyleDelete;
}

To perform a custom action when the specifier gets deleted, you have to implement a method in your PSEditableListController subclass like this:

-(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
*/

[testSpecifier setProperty:NSStringFromSelector(@selector(removedSpecifier:)) forKey:PSDeletionActionKey];

Custom Settings Libraries

Plist Only

To do: add more difficult recipes, such as using the PSGroupCell properly, and making sub-preferences using the PSLinkCell

These will work as an independent plist, when put into "/Library/PreferenceLoader/Preferences". You need no Objective C or anything for these, just the plist file. This can be useful for adding preferences to things like Cydgets. Other examples from tweaks that use just the plist can be found in that same location.

Required Structure

This is the required outline to add a custom plist to the Preferences app.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>entry</key>
        <dict>
            <key>cell</key>
            <string>PSLinkCell</string>
            <key>icon</key>
            <string>[email protected]</string>
            <key>label</key>
            <string>Tweak Name</string>
        </dict>

        <key>items</key>
        <array>
           
        </array>
        <key>title</key>
        <string>Demo</string>
    </dict>
</plist>

The various cells and items are put between the array tags.

Example with PSEditTextCell

Though there is nothing all that special about this cell, it is a good example of how to make other cells.

<dict>
    <key>cell</key>
    <string>PSEditTextCell</string>
    <key>placeholder</key>
    <string>0.2</string>
    <key>bestGuess</key>
    <string>0.2</string>
    <key>defaults</key>
    <string>com.yourname.yourtweak</string>
    <key>isDecimalPad</key>
    <true/>
    <key>key</key>
    <string>savedName</string>
    <key>label</key>
    <string>Visible Name:</string>
</dict>

This would make an input text cell for numbers. Use the keys specified above to change its properties and appearance. This goes for any cell.

External links