Extending Bundle to add extra plist file

I imagine this is going to be simple, but I’m still learning swift.

I’ve created a Preferences.plist file that I want to pull in like Info.plist, but separate, ie:

Bundle.main.prefsDictionary

Or something like that … I have a Bundle+Extension.swift file already, that contains:

import Foundation

extension Bundle {
    var releaseVersionNumber: String? {
        infoDictionary?["CFBundleShortVersionString"] as? String
    }
}

So what I’m figuring I want to do is add a new var that references this extra plist file, but for all my searching, I cannot seem to find any suitable example to do this … a lot of stuff on how to merge / append Dictionaries together, but I want to keep this separate, just easily referenceable, just like Bundle.main.infoDictionary is …

If swift was an open source compiler, I’d pop out and find the appropriate code for ‘var infoDictionary’ and just copy / change that to reference the new file, but alas, that seems to be impossible.

I tried adding to my Bundle extension something as simple as:

   var prefsDictionary: Dictionary {
        let path = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
        return NSDictionary(contentsOfFile: path)
   }

based on examples I’d found … XCode tells me I need to add "<<#Key: Hashable#>, Any>", making it:

    var prefsDictionary: Dictionary<<#Key: Hashable#>, Any> {
        let path = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
        return NSDictionary(contentsOfFile: path)
    }

Leading to the next error:

error: Unexpected token #>, at 22:52 in XCode/app/Sources/Helpers/Bundle+Extensions.swift.

Even though that is what it told me to put … so I’m sort of going around in circles, and not even sure if I’m even close to doing it right.

A good article about this would be great … pointers always appreciated …

Thanks …

>Solution :

To fix the error add the generic types and (bridge) cast the result type

var prefsDictionary: Dictionary<String,Any> {
    let path = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
    return NSDictionary(contentsOfFile: path)! as [String:Any]
}

However the API NSDictionary(contentsOfFile is strongly discouraged in Swift. The recommended API is PropertyListSerialization and it’s also recommended to use the URL related API

var prefsDictionary: Dictionary<String,Any> {
    let url = Bundle.main.url(forResource: "Preferences", withExtension: "plist")!
    let data = try! Data(contentsOf: url)
    return try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String:Any]
}

But there is a still better way. Declare a struct where the members represent the keys for example

struct MyPrefs: Decodable {
    var versionString : String
}

and decode the data with PropertyListDecoder

var preferences: MyPrefs {
    let url = Bundle.main.url(forResource: "Preferences", withExtension: "plist")!
    let data = try! Data(contentsOf: url)
    return try! PropertyListDecoder().decode(MyPrefs.self, from: data)
}

Side note:

Force unwrapping the types is fine in this case. The code must not crash because the files in the application bundle are immutable at runtime. A crash reveals a design mistake which can be fixed instantly.

Leave a Reply