Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, configurations and app-state.
@Preference(key: "enabled")
var enabled: Bool?
@UserDefaultsPreference(key: "my.string.pref")
var pref: String?
@MutablePreference(preferences: UserDefaults.standard, key: "enabled")
var enabled: Bool?
let userDefaults = UserDefaults.standard
if let enabled = userDefaults["enabled"] as? Bool {..}
userDefaults["mycolorkey", archive] = UIColor.blue
Preferences is not only UserDefaults
, it could be also :
Bundle
NSUbiquitousKeyValueStore
ie. any object which implement the simple protocol PreferencesType, which define key value store methods.
You can also combine multiples preferences and work with them transparently (see Composing)
The simplest implementation of PreferencesType is DictionaryPreferences
// From Dictionary
var fromDico = DictionaryPreferences(myDictionary)
// or literal
var fromDicoLiteral: DictionaryPreferences = ["myKey": "myValue", "bool": true]
// From filepath
if let fromFile = DictionaryPreferences(filePath: "/my/file/path") {..}
// ...in main bundle ##
if let fromFile = DictionaryPreferences(filename: "prefs", ofType: "plist") {..}
You can access with all methods defined in PreferencesType protocol
if let myValue = fromDicoLiteral.object(forKey: "myKey") {..}
if let myValue = fromDicoLiteral["bool"] as? Bool {..}
var hasKey = fromDicoLiteral.hasObject(forKey: "myKey")
var myValue = fromDicoLiteral.bool(forKey: "myKey")
..
If you want to access using RawRepresentable
enum
.
enum MyKey: PreferenceKey/*String*/ {
case Key1, Key2, ...
}
if let myValue = fromDicoLiteral.object(forKey: MyKey.Key1) {..}
var myValue = fromDicoLiteral.bool(forKey: MyKey.Key2)
:warning: RawRepresentableKey must be imported, see setup.
Modifiable preferences implement the protocol MutablePreferencesTypes
The simplest implementation is MutableDictionaryPreferences
var mutableFromDico: MutableDictionaryPreferences = ["myKey": "myValue"]
mutableFromDico["newKey"] = "newValue"
mutableFromDico.set("myValue", forKey: "newKey")
mutableFromDico.set(true, forKey: "newKey")
...
You can append dictionary or other PreferencesType
using operators
mutableFromDico += ["newKey": "newValue", "otherKey": true]
You can also remove one preference
mutableFromDico -= "myKey"
You can extract a MutablePreference
from any MutablePreferencesTypes
and apply operators according to its value type
var intPref: MutablePreference<Int> = aPrefs.preference(forKey: "intKey")
var intPref: MutablePreference<Int> = aPrefs <| "intKey"
intPref++
intPref--
intPref += 30
intPref -= 30
intPref *= 20
intPref %= 7
intPref /= 3
switch(intPref) {
case 1: println("one")
case 2...10: println("not one or zero but...")
default: println("unkwown")
}
var boolPref: MutablePreference<Bool> = aPrefs <| "boolKey")
boolPref &= false
boolPref |= true
boolPref != true
You can also use some methods to change value
var stringPref: MutablePreference<String> = userDefaults <| "stringKey"
stringPref.apply { value in
return value?.uppercaseString
}
or transform the value type using closures
let intFromBoolPref : MutablePreference<Int> = boolPref.transform { value in
return (value ?? false) ? 1:0
}
Before storing or accessing the value, transformation could be applied, which conform to protocol PreferenceTransformation
.
This allow to archive, to change type, return default value if nil and many more.
You can get and set value using subscript
userDefaults["aKey", myTransformation] = myObject
if let object = userDefaults["aKey", myTransformation] {...}
If you extract one preference, use transformation
property to setup the transformation
var aPref: MutablePreference<MyObject> = userDefaults <| "aKey"
aPref.transformation = myTransformation
or you can use some utility functions to specify a default value when the stored value match a condition
public var intValueMin10: MutablePreference<Int> {
get {
return userDefaults.preference(forKey: "intKey")
.whenNil(use: 100)
.ensure(when: lessThan100, use: 100)
}
set {..}
}
Archiving is particularly useful with NSUserDefaults
because NSUserDefaults
can't store all type of objects.
The following functions could help by transforming the value into an other type
You can archive into Data
using this two methods
userDefaults.set(objectToArchive: UIColor.blueColor(), forKey: "colorKey")
userDefaults["colorKey", .Archive] = UIColor.blueColor()
and unarchive using
if let color = userDefaults.unarchiveObject(forKey: "colorKey") as? UIColor {..}
if let color = userDefaults["colorKey", .Archive] as? UIColor {..}
If you extract one preference, use transformation
property to setup archive mode
var colorPref: MutablePreference<UIColor> = userDefaults <| "colorKey"
colorPref.transformation = TransformationKey.Archive
colorPref.value = UIColor.redColor()
if let color = colorPref.value as? UIColor {..}
You can also apply for all objects type an NSValueTransformer
, to transform into JSON for instance
userDefaults["colorKey", myValueTransformerToJson] = myComplexObject
if let object = userDefaults["colorKey", myValueTransformerToJson] {...}
:warning: allowsReverseTransformation
must return true
For RawRepresentable
objects like enum
you can use the computed attribute preferenceTransformation
as transformation
enum PrefEnum: String {
case One, Two, Three
}
var pref: MutablePreference<PrefEnum> = preferences <| "enumKey"
pref.transformation = PrefEnum.preferenceTransformation
pref.value = PrefEnum.Two
UserDefaults
implement PreferencesType
and can be acceded with same methods
let userDefaults = UserDefaults.standard
if let myValue = userDefaults["mykey"] as? Bool {..}
NSUserDefaults implement also MutablePreferencesType
and can be modified with same methods
userDefaults["mykey"] = "myvalue"
// with type to archive
userDefaults["mykey", .Archive] = UIColor.blueColor()
All Bundle
implement PreferencesType
, allowing to access Info.plist file.
For instance the Bundle.main
contains many useful informations about your application.
Prephirences framework come with some predefined enums described in apple documentations and defined in PropertyListKeys.swift
let bundle = Bundle.main
let applicationName = bundle[.CFBundleName] as? String
To store in iCloud, NSUbiquitousKeyValueStore
implement also PreferencesType
See composing chapter to merge and synchronize iCloud preferences with other preferences.
You can wrap an object respond to implicit protocol NSKeyValueCoding in KVCPreferences
or MutableKVCPreferences
let kvcPref = MutableKVCPreferences(myObject)
Be sure to affect the correct object type
Using ReflectingPreferences
you can easily access to a struct or swift class. Just add extension.
struct PreferenceStruct {
var color: String = "red"
var age: Int
let enabled: Bool = true
}
extension PreferenceStruct: ReflectingPreferences {}
You can then use all functions from PreferencesType
var pref = PreferenceStruct(color: "red", age: 33)
if pref["color"] as? String { .. }
You can wrap on NSManageObject
in ManageObjectPreferences
or MutableManageObjectPreferences
let managedPref = ManageObjectPreferences(myManagedObject)
There is many way to play with plist files
Plist
(with the useful write
method)DictionaryPreferences
or MutableDictionaryPreferences
with plist fileset(dictionary:
on any mutable preferencesTo store into keychain, use an instance of KeychainPreferences
KeychainPreferences.sharedInstance // default instance with main bundle id
var keychain = KeychainPreferences(service: "com.github.example")
then store String
or Data
keychain["anUserName"] = "password-encoded"
if let pass = keychain.stringForKey("anUserName") {..}
Accessibility
keychain.accessibility = .AccessibleAfterFirstUnlock
Sharing Keychain items
keychain.accessGroup = "AKEY.shared"
NSCoder
is partially supported (dictionary
is not available)
When you implementing NSCoding you can do
init?(coder decoder: NSCoder) {
self.init()
self.intVar = decoder["intVarKey"] as? Int ?? 0
// or self.intVar = decoder.integer(forKey: "intVar")
self.stringVar = decoder["stringVarKey"] as? String ?? ""
}
func encodeWithCoder(coder: NSCoder) {
coder["intVarKey"] = self.intVar
coder["stringVarKey"] = self.stringVar
}
Create a custom object that conform to PreferencesType
is very easy.
extension MyCustomPreferences: PreferencesType {
func object(forKey: String) -> Any? {
// return an object according to key
}
func dictionary() -> [String : Any] {
// return a full dictionary of key value
}
}
Only two functions are mandatory, others are automatically mapped but can be overrided for performance or readability.
MutablePreferencesType
with set
and removeObject(forKey:
methods.PreferencesAdapter
and implement func keys() -> [String]
.CollectionPreferencesAdapter
or see NSHTTPCookieStorage
implementation.Instead of using string
or string
constants, you can use an enum
to define a list of keys
First create your enum
with String
raw value
enum MyEnum: String {
case MyFirstKey
case MySecondKey
}
Then add a subscript for your key
extension PreferencesType {
subscript(key: MyEnum) -> Any? {
return self[key.rawValue]
}
}
Finally access your information
if let firstValue = bundle[.MyFirstKey] {..}
You can do the same with MutablePreferencesType
You can defined a subcategory of preferences prefixed with your own string like that
let myAppPrefs = MutableProxyPreferences(preferences: userDefaults, key: "myAppKey.")
// We have :
userDefaults["myAppKey.myKey"] == myAppPrefs["myKey"] // is true
This allow prefixing all your preferences (user defaults) with same key
Composing allow to aggregate multiples PreferencesType objects into one PreferencesType
let myPreferences = CompositePreferences([fromDico, fromFile, userDefaults])
// With array literal
let myPreferences: CompositePreferences = [fromDico, fromFile, userDefaults]
// Mutable, only first mutable will be affected
let myPreferences: MutableCompositePreferences = [fromDico, fromFile, userDefaults]
You can access or modify this composite preferences like any PreferencesType
.
MutableCompositePreferences
attribute affectOnlyFirstMutable
to false
to affect all mutable preferences, allowing you for instance to duplicate preferences in iCloudThe main goal is to define read-only preferences for your app (in code or files) and some mutable preferences (like UserDefaults
, NSUbiquitousKeyValueStore
). You can then access to one preference value without care about the origin.
If you want to use Prephirences into a framework or want to get a Preferences
without adding dependencies between classes, you can register any PreferencesType
into Prephirences
as shared instance
Prephirences.sharedInstance = myPreferences
or by providing an Hashable
key
Prephirences.register(preferences: myPreferences, forKey: "myKey")
Prephirences.instances()["myKey"] = myPreferences
Prephirences.instances()[NSStringFromClass(self.dynamicType)] = currentClassPreferences
Then you can access it anywhere
if let pref = Prephirences.instance(forKey: "myKey") {..}
if let pref = Prephirences.instances()["myKey"] {..}
By using remote preferences you can remotely control the behavior of your app.
If you use Alamofire, Alamofire-Prephirences will help you to load preferences from remote JSON or Plist
You can use framework CryptoPrephirences to encrypt/decrypt your preferences using cipher from CryptoSwift
CocoaPods is a centralized dependency manager for Objective-C and Swift. Go here to learn more.
Add the project to your Podfile.
use_frameworks!
pod 'Prephirences'
Run pod install
and open the .xcworkspace
file to launch Xcode.
Add pod 'Prephirences/CoreData'
Add pod 'Prephirences/RawRepresentableKey'
Add pod 'Prephirences/Keys'
Carthage is a decentralized dependency manager for Objective-C and Swift.
Add the project to your Cartfile.
github "phimage/Prephirences"
Run carthage update
and follow the additional steps
in order to add Prephirences to your project.
By kodlian