Basem Emara

Mobile Architect / iOS Jedi

  • About
  • Portfolio
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Twitter

Reading values from any plist file or bundle in Swift

March 8, 2016 By Basem Emara 4 Comments

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:

plist-values

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:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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:

Swift
1
2
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!!

Filed Under: Swift Tagged With: swift, xcode

Subscribe and stay tuned!

The launch of this blog was inspired from uncovering the joys, pains, and realities of Swift mobile development. Many of the posts dissect open-source apps and other real-world projects. Thank you for coming along for the journey; I hope you find it as fun and exciting as I do!

Comments

  1. Travis says

    July 31, 2017 at 3:20 pm

    My contents variable doesn’t not fill with any data. It get’s the correct path though.

    Reply
    • Basem Emara says

      October 12, 2017 at 7:39 pm

      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.

      Reply
      • Ilya says

        November 17, 2017 at 9:18 am

        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)
        }

        }

        Reply

Trackbacks

  1. Full Stack iOS and WordPress in Swift says:
    January 26, 2017 at 10:05 am

    […] to store configuration values. I cover how we can read values from a plist file in my previous post. In the end, our plist configuration file will look something like […]

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

My Life Philosophy

Simplicity is the key to elegance.

Post Series

  • Building a Scalable iOS App
  • Swift Utility Belt

Popular Posts

  • Creating Thread-Safe Arrays in Swift
  • Creating Cross-Platform Swift Frameworks for iOS, watchOS, and tvOS via Carthage and CocoaPods
  • Memory Leaks and Resource Management in Swift and iOS
  • So Swift, So Clean Architecture for iOS
  • Reading values from any plist file or bundle in Swift

Mobile App

Download the Basem Emara Blog app

Find It