It is super convenient to keep your settings or default values in a .plist file for your project. It allows you to keep your code the same across various apps while differences only residing in a configuration file. It becomes more sophisticated when you create a Settings.bundle and store your preferences there. This makes it easy to distribute settings across multiple apps.
This is all nice and dandy until you actually need to read values out of the .plist or bundle. Take the below example of a .plist file containing values you would like available for your app:
To extract these values into a `[String: AnyObject]`, you’ll have to call some awkward API’s from Apple. For convenience, below you’ll find an extension I created off the NSBundle class so you can easily get those values from any .plist file or bundle:
public extension NSBundle { /** Gets the contents of the specified plist file. - parameter plistName: property list where defaults are declared - parameter bundle: bundle where defaults reside - returns: dictionary of values */ public static func contentsOfFile(plistName: String, bundle: NSBundle? = nil) -> [String : AnyObject] { let fileParts = plistName.componentsSeparatedByString(".") guard fileParts.count == 2, let resourcePath = (bundle ?? NSBundle.mainBundle()).pathForResource(fileParts[0], ofType: fileParts[1]), let contents = NSDictionary(contentsOfFile: resourcePath) as? [String : AnyObject] else { return [:] } return contents } /** Gets the contents of the specified bundle URL. - parameter bundleURL: bundle URL where defaults reside - parameter plistName: property list where defaults are declared - returns: dictionary of values */ public static func contentsOfFile(bundleURL bundleURL: NSURL, plistName: String = "Root.plist") -> [String : AnyObject] { // Extract plist file from bundle guard let contents = NSDictionary(contentsOfURL: bundleURL.URLByAppendingPathComponent(plistName)) else { return [:] } // Collect default values guard let preferences = contents.valueForKey("PreferenceSpecifiers") as? [String: AnyObject] else { return [:] } return preferences } /** Gets the contents of the specified bundle name. - parameter bundleName: bundle name where defaults reside - parameter plistName: property list where defaults are declared - returns: dictionary of values */ public static func contentsOfFile(bundleName bundleName: String, plistName: String = "Root.plist") -> [String : AnyObject] { guard let bundleURL = NSBundle.mainBundle().URLForResource(bundleName, withExtension: "bundle") else { return [:] } return contentsOfFile(bundleURL: bundleURL, plistName: plistName) } /** Gets the contents of the specified bundle. - parameter bundle: bundle where defaults reside - parameter bundleName: bundle name where defaults reside - parameter plistName: property list where defaults are declared - returns: dictionary of values */ public static func contentsOfFile(bundle bundle: NSBundle, bundleName: String = "Settings", plistName: String = "Root.plist") -> [String : AnyObject] { guard let bundleURL = bundle.URLForResource(bundleName, withExtension: "bundle") else { return [:] } return contentsOfFile(bundleURL: bundleURL, plistName: plistName) } }
I provided several overloaded functions to read from any .plist file or bundle. Note that if you would like to use a Settings.bundle, open up the “Root.plist” file, then right-click in the contents pane, go to “Property List Type”, and select “Info.plist”. This will allow to add a dictionary of values into the “Root.plist” file.
Now with the above extension in place, you can simply use it like this:
let values = NSBundle.contentsOfFile("Settings.plist") print(values["MyString1"]) // My string value 1.
The dictionary in the previous screenshot was displaying values from a file called “Settings.plist”. The above code snippet retrieves those values and puts them into a dictionary in one line with all the safe guards in place. Enjoy 🙂
HAPPY CODING!!
My contents variable doesn’t not fill with any data. It get’s the correct path though.
Hi Travis, apologies for the late reply. I’ve updated the code to Swift 4 here if it helps: https://github.com/ZamzamInc/ZamzamKit/blob/master/Sources/Extensions/Bundle.swift. Let me know if you’re still having trouble or have a sample project I can try.
Hi,
I see that the Swift 4 version you linked to on GitHub lacks bundle support. Yet, it was exactly what I needed. So I came up with code that does the job. It returns a dictionary of those preference entries that include “Identifier” property in Root.plist, which is represented by “Key” in the dictionary. Values of “Key” entries are used as keys in the returned dictionary. In case you’re interested, here it is.
I renamed some of your original symbols to better match my purpose, but feel free to adjust. Also, I did only preliminary testing of this code.
public extension Bundle {
/**
Gets the contents of the specified plist file.
– parameter plistName: property list where defaults are declared
– parameter bundle: bundle where defaults reside
– returns: dictionary of values
*/
public static func contentsOfFile(pListName: String, bundle: Bundle? = nil) -> NSDictionary {
let fileParts = pListName.split(separator: “.”)
guard fileParts.count == 2,
let resourcePath = (bundle ?? Bundle.main).path(forResource: String(fileParts[0]), ofType: String(fileParts[1])),
let contents = NSDictionary(contentsOfFile: resourcePath)
else { return [:] }
return contents
}
/**
Gets the contents of the specified bundle URL.
– parameter bundleURL: bundle URL where defaults reside
– parameter plistName: property list where defaults are declared
– returns: dictionary of values
*/
public static func contentsOfBundle(bundleURL: URL, plistName: String = “Root.plist”) -> [String: AnyObject] {
// Extract plist file from bundle
guard let contents = NSDictionary(contentsOf: bundleURL.appendingPathComponent(plistName))
else { return [:] }
// Collect default values
guard let preferences = contents.object(forKey:”PreferenceSpecifiers”) as? NSArray
else { return [:] }
// look for all entries that contain “Key” element (maps to “Identifier” in Root.plist)
// and return them as a dictionary keyed by “Key” element’s value
var dict: [String: NSDictionary] = [:]
for preference in preferences {
if let itemDict = preference as? NSDictionary {
for key in itemDict.allKeys {
if let itemKey = key as? String, itemKey.elementsEqual(“Key”), let keyValue = itemDict[itemKey] as? String {
dict[keyValue] = itemDict
break;
}
}
}
}
return dict
}
/**
Gets the contents of the specified bundle name.
– parameter bundleName: bundle name where defaults reside
– parameter plistName: property list where defaults are declared
– returns: dictionary of values
*/
public static func contentsOfBundle(bundleName: String, plistName: String = “Root.plist”) -> [String: AnyObject] {
guard let bundleURL = Bundle.main.url(forResource:bundleName, withExtension: “bundle”)
else { return [:] }
return contentsOfBundle(bundleURL: bundleURL, plistName: plistName)
}
/**
Gets the contents of the specified bundle.
– parameter bundle: bundle where defaults reside
– parameter bundleName: bundle name where defaults reside
– parameter plistName: property list where defaults are declared
– returns: dictionary of values
*/
public static func contentsOfBundle(bundle: Bundle, bundleName: String = “Settings”, plistName: String = “Root.plist”) -> [String: AnyObject] {
guard let bundleURL = bundle.url(forResource:bundleName, withExtension: “bundle”)
else { return [:] }
return contentsOfBundle(bundleURL: bundleURL, plistName: plistName)
}
}