219

I am playing around with Apple's new Swift programming language and have some problems...

Currently I'm trying to read a plist file, in Objective-C I would do the following to get the content as a NSDictionary:

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:filePath];

How do I get a plist as a Dictionary in Swift?

I assume I can get the path to the plist with:

let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")

When this works (If it's correct?): How do I get the content as a Dictionary?

Also a more general question:

Is it OK to use the default NS* classes? I think so...or am I missing something? As far as I know the default framework NS* classes are still valid and alright to use?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Sebastian
  • 3,379
  • 2
  • 23
  • 39

27 Answers27

305

You can still use NSDictionaries in Swift:

For Swift 4

 var nsDictionary: NSDictionary?
 if let path = Bundle.main.path(forResource: "Config", ofType: "plist") {
    nsDictionary = NSDictionary(contentsOfFile: path)
 }

For Swift 3+

if let path = Bundle.main.path(forResource: "Config", ofType: "plist"),
   let myDict = NSDictionary(contentsOfFile: path){
    // Use your myDict here
}

And older versions of Swift

var myDict: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
    myDict = NSDictionary(contentsOfFile: path)
}
if let dict = myDict {
    // Use your dict here
}

The NSClasses are still available and perfectly fine to use in Swift. I think they'll probably want to shift focus to swift soon, but currently the swift APIs don't have all the functionality of the core NSClasses.

Alex Nolasco
  • 18,750
  • 9
  • 86
  • 81
Connor Pearson
  • 63,902
  • 28
  • 145
  • 142
142

This is what I do if I want to convert a .plist to a Swift dictionary:

if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
  if let dict = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
    // use swift dictionary as normal
  }
}

Edited for Swift 2.0:

if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist"), dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
    // use swift dictionary as normal
}

Edited for Swift 3.0:

if let path = Bundle.main.path(forResource: "Config", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
        // use swift dictionary as normal
}
pheedsta
  • 2,058
  • 2
  • 16
  • 11
  • 3
    I think this is "the most correct" answer of the bunch until there is a native swift way to do this. – DudeOnRock Nov 05 '14 at 02:45
  • 1
    This answer is outdated. In Swift 3 you **should not** use `NSArray/NSDictionary` at all to read property list data. `PropertyListSerialization` (and in Swift 4 alternatively the `Codable` protocol) is the appropriate API. It provides modern error handling and the data can be converted directly to native Swift collection types. – vadian Jan 09 '19 at 09:15
71

Swift 4.0

You can now use the Decodable protocol to Decode a .plist into a custom struct. I will go over a basic example, for more complicated .plist structures I recommend reading up on Decodable/Encodable (a good resource is here: https://benscheirman.com/2017/06/swift-json/).

First setup your struct into the format of your .plist file. For this example I will consider a .plist with a root level Dictionary and 3 entries: 1 String with key "name", 1 Int with key "age", and 1 Boolean with key "single". Here is the struct:

struct Config: Decodable {
    private enum CodingKeys: String, CodingKey {
        case name, age, single
    }

    let name: String
    let age: Int
    let single: Bool
}

Simple enough. Now the cool part. Using the PropertyListDecoder class we can easily parse the .plist file into an instantiation of this struct:

func parseConfig() -> Config {
    let url = Bundle.main.url(forResource: "Config", withExtension: "plist")!
    let data = try! Data(contentsOf: url)
    let decoder = PropertyListDecoder()
    return try! decoder.decode(Config.self, from: data)
}

Not much more code to worry about, and its all in Swift. Better yet we now have an instantiation of the Config struct that we can easily use:

let config = parseConfig()
print(config.name) 
print(config.age)
print(config.single) 

This Prints the value for the "name", "age", and "single" keys in the .plist.

ekreloff
  • 911
  • 10
  • 8
  • 1
    That's the best answer for Swift 4. But why not `Bundle.main.url(forResource: "Config", withExtension: "plist")` and get rid of `URL(fileURLWithPath`? And as the file is required to exist (at design / compile time) all values can be force unwrapped. The code must not crash if everything is designed properly. – vadian Jan 07 '18 at 18:30
  • 1
    @vadian Sure you can use `url(forResource: "Config", withExtension: "plist")` I was just matching what the OP did in their code as a point of comparison. As far as force unwrapping everything, I try to err on the side of caution. I think that is a fundamental question for Swift in general. I'd rather know exactly what my code will do in any situation than crash. – ekreloff Jan 08 '18 at 18:48
  • 1) Please don't adopt bad habits if there is a more appropriate API. 2) That's one of the few cases where a forced crash discovers a design error. Any file in the bundle is required to be present at compile time and cannot be changed at runtime because all files are code-signed. Once again: *The code must not crash if everything is designed properly*. – vadian Jan 08 '18 at 19:22
  • Yea you know your right. Didn't realize that was the case with Bundle resources. – ekreloff Jan 09 '18 at 16:42
  • One other thing to note: for completeness I added the CodingKeys enum, however since this case is pretty simple where var names match the plist names you can omit it if you wish. – ekreloff Feb 20 '18 at 19:56
  • 2
    @NaveenGeorgeThoppan if you use this example as your dictionary then it would simply be `decoder.decode([Config].self, from: data)`. (Notice the brackets around [Config]) – ekreloff Aug 11 '18 at 23:40
  • @ekreloff those brackets were just what I needed to make my deserializing work. Man I tried everything else. – Andrew Duncan Nov 01 '18 at 05:28
  • It looks like reading Info.plist with this will get variable substitutions in info.plist. For example, CFBundleDisplayName = $(PRODUCT_NAME) will be read exactly as $(PRODUCT_NAME), not the value behind the variable. – undsoft Apr 15 '19 at 11:51
62

In swift 3.0 Reading from Plist.

func readPropertyList() {
        var propertyListFormat =  PropertyListSerialization.PropertyListFormat.xml //Format of the Property List.
        var plistData: [String: AnyObject] = [:] //Our data
        let plistPath: String? = Bundle.main.path(forResource: "data", ofType: "plist")! //the path of the data
        let plistXML = FileManager.default.contents(atPath: plistPath!)!
        do {//convert the data to a dictionary and handle errors.
            plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as! [String:AnyObject]

        } catch {
            print("Error reading plist: \(error), format: \(propertyListFormat)")
        }
    }

Read More HOW TO USE PROPERTY LISTS (.PLIST) IN SWIFT.

Ky -
  • 30,724
  • 51
  • 192
  • 308
Ashok R
  • 19,892
  • 8
  • 68
  • 68
22

This answer uses Swift native objects rather than NSDictionary.

Swift 3.0

//get the path of the plist file
guard let plistPath = Bundle.main.path(forResource: "level1", ofType: "plist") else { return }
//load the plist as data in memory
guard let plistData = FileManager.default.contents(atPath: plistPath) else { return }
//use the format of a property list (xml)
var format = PropertyListSerialization.PropertyListFormat.xml
//convert the plist data to a Swift Dictionary
guard let  plistDict = try! PropertyListSerialization.propertyList(from: plistData, options: .mutableContainersAndLeaves, format: &format) as? [String : AnyObject] else { return }
//access the values in the dictionary 
if let value = plistDict["aKey"] as? String {
  //do something with your value
  print(value)
}
//you can also use the coalesce operator to handle possible nil values
var myValue = plistDict["aKey"] ?? ""
Tommie C.
  • 12,895
  • 5
  • 82
  • 100
19

I have been working with Swift 3.0 and wanted to contribute an answer for the updated syntax. Additionally, and possibly more importantly, I am using the PropertyListSerialization object to do the heavy lifting, which is a lot more flexible than just using the NSDictionary as it allows for an Array as the root type of the plist.

Below is a screenshot of the plist I am using. It is a little complicated, so as to show the power available, but this will work for any allowable combination of plist types.

Sample plist file As you can see I am using an Array of String:String dictionaries to store a list of website names and their corresponding URL.

I am using the PropertyListSerialization object, as mentioned above, to do the heavy lifting for me. Additionally, Swift 3.0 has become more "Swifty" so all of the object names have lost the "NS" prefix.

let path = Bundle.main().pathForResource("DefaultSiteList", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)

After the above code runs plist will be of type Array<AnyObject>, but we know what type it really is so we can cast it to the correct type:

let dictArray = plist as! [[String:String]]
// [[String:String]] is equivalent to Array< Dictionary<String, String> >

And now we can access the various properties of our Array of String:String Dictionaries in a natural way. Hopefully to convert them into actual strongly typed structs or classes ;)

print(dictArray[0]["Name"])
Nick
  • 341
  • 3
  • 4
11

Swift 5

If you want to fetch specific value for some key then we can use below extension which uses infoDictionary property on Bundle.

Bundle.main.infoDictionary can be used to get all info.plist values in the form dictionary and so we can directly query using object(forInfoDictionaryKey: key) method on Bundle

extension Bundle {
    static func infoPlistValue(forKey key: String) -> Any? {
        guard let value = Bundle.main.object(forInfoDictionaryKey: key) else {
           return nil
        }
        return value
    }
}

Usage

guard let apiURL = Bundle.infoPlistValue(forKey: "API_URL_KEY") as? String else { return }
Anatoly
  • 20,799
  • 3
  • 28
  • 42
Suhit Patil
  • 11,748
  • 3
  • 50
  • 60
8

It is best to use native dictionaries and arrays because they have been optimized for use with swift. That being said you can use NS... classes in swift and I think this situation warrants that. Here is how you would implement it:

var path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
var dict = NSDictionary(contentsOfFile: path)

So far (in my opinion) this is the easiest and most efficient way to access a plist, but in the future I expect that apple will add more functionality (such as using plist) into native dictionaries.

67cherries
  • 6,931
  • 7
  • 35
  • 51
8

Swift - Read/Write plist and text file....

override func viewDidLoad() {
    super.viewDidLoad()

    let fileManager = (NSFileManager .defaultManager())
    let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]

    if (directorys != nil){
        let directories:[String] = directorys!;
        let dictionary = directories[0]; //documents directory


        //  Create and insert the data into the Plist file  ....
        let plistfile = "myPlist.plist"
        var myDictionary: NSMutableDictionary = ["Content": "This is a sample Plist file ........."]
        let plistpath = dictionary.stringByAppendingPathComponent(plistfile);

        if !fileManager .fileExistsAtPath(plistpath){//writing Plist file
            myDictionary.writeToFile(plistpath, atomically: false)
        }
        else{            //Reading Plist file
            println("Plist file found")

            let resultDictionary = NSMutableDictionary(contentsOfFile: plistpath)
            println(resultDictionary?.description)
        }


        //  Create and insert the data into the Text file  ....
        let textfile = "myText.txt"
        let sampleText = "This is a sample text file ......... "

        let textpath = dictionary.stringByAppendingPathComponent(textfile);
        if !fileManager .fileExistsAtPath(textpath){//writing text file
            sampleText.writeToFile(textpath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
        } else{
            //Reading text file
            let reulttext  = String(contentsOfFile: textpath, encoding: NSUTF8StringEncoding, error: nil)
            println(reulttext)
        }
    }
    else {
        println("directory is empty")
    }
}
el.severo
  • 2,202
  • 6
  • 31
  • 62
8

Swift 2.0 : Accessing Info.Plist

I have a Dictionary named CoachMarksDictionary with a boolean value in Info.Plist . I want to access the bool value and make it true.

let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
  let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]

  if let CoachMarksDict = dict["CoachMarksDictionary"] {
       print("Info.plist : \(CoachMarksDict)")

   var dashC = CoachMarksDict["DashBoardCompleted"] as! Bool
    print("DashBoardCompleted state :\(dashC) ")
  }

Writing To Plist:

From a Custom Plist:- (Make from File-New-File-Resource-PropertyList. Added three strings named : DashBoard_New, DashBoard_Draft, DashBoard_Completed)

func writeToCoachMarksPlist(status:String?,keyName:String?)
 {
  let path1 = NSBundle.mainBundle().pathForResource("CoachMarks", ofType: "plist")
  let coachMarksDICT = NSMutableDictionary(contentsOfFile: path1!)! as NSMutableDictionary
  var coachMarksMine = coachMarksDICT.objectForKey(keyName!)

  coachMarksMine  = status
  coachMarksDICT.setValue(status, forKey: keyName!)
  coachMarksDICT.writeToFile(path1!, atomically: true)
 }

The method can be called as

self.writeToCoachMarksPlist(" true - means user has checked the marks",keyName: "the key in the CoachMarks dictionary").
Alvin George
  • 14,148
  • 92
  • 64
8

Converted into a convenience extension via Nick's answer:

extension Dictionary {
    static func contentsOf(path: URL) -> Dictionary<String, AnyObject> {
        let data = try! Data(contentsOf: path)
        let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)

        return plist as! [String: AnyObject]
    }
}

usage:

let path = Bundle.main.path(forResource: "plistName", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let dict = Dictionary<String, AnyObject>.contentsOf(path: url)

I'd be willing to bet that it would also work to create a similar extension for Arrays

mredig
  • 1,736
  • 1
  • 16
  • 29
  • This is a decent approach, except for the crashing. The original ObjC method on NSDictionary simply returned nil if decoding failed, if I recall correctly. – Bryan Aug 30 '20 at 21:29
7

Since this answer isn't here yet, just wanted to point out you can also use the infoDictionary property to get the info plist as a dictionary, Bundle.main.infoDictionary.

Although something like Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) may be faster if you're only interested in a specific item in the info plist.

// Swift 4

// Getting info plist as a dictionary
let dictionary = Bundle.main.infoDictionary

// Getting the app display name from the info plist
Bundle.main.infoDictionary?[kCFBundleNameKey as String]

// Getting the app display name from the info plist (another way)
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String)
Scott Marchant
  • 3,447
  • 2
  • 22
  • 29
5

can actually do it in 1 line

    var dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Config", ofType: "plist"))
KennyVB
  • 745
  • 2
  • 9
  • 28
5

You can read plist in SWIFT Language in this way:

let path = NSBundle.mainBundle().pathForResource("PriceList", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path)

Read Single Dictionary value:

let test: AnyObject = dict.objectForKey("index1")

If you want to get full multi-dimensional dictionary in plist:

let value: AnyObject = dict.objectForKey("index2").objectForKey("date")

Here is the plist:

<plist version="1.0">
<dict>
<key>index2</key>
<dict>
    <key>date</key>
    <string>20140610</string>
    <key>amount</key>
    <string>110</string>
</dict>
<key>index1</key>
<dict>
    <key>amount</key>
    <string>125</string>
    <key>date</key>
    <string>20140212</string>
</dict>
</dict>
</plist>
imti
  • 1,283
  • 1
  • 11
  • 12
3

in my case I create a NSDictionary called appSettings and add all needed keys. For this case, the solution is:

if let dict = NSBundle.mainBundle().objectForInfoDictionaryKey("appSettings") {
  if let configAppToken = dict["myKeyInsideAppSettings"] as? String {

  }
}
jose920405
  • 7,982
  • 6
  • 45
  • 71
3

Step 1 : Simple and fastest way to parse plist in swift 3+

extension Bundle {

    func parsePlist(ofName name: String) -> [String: AnyObject]? {

        // check if plist data available
        guard let plistURL = Bundle.main.url(forResource: name, withExtension: "plist"),
            let data = try? Data(contentsOf: plistURL)
            else {
                return nil
        }

        // parse plist into [String: Anyobject]
        guard let plistDictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: AnyObject] else {
            return nil
        }

        return plistDictionary
    }
}

Step 2: How to use:

Bundle().parsePlist(ofName: "Your-Plist-Name")
Bhuvan Bhatt
  • 3,276
  • 2
  • 18
  • 23
2

You can use that, I create a simple extension for Dictionary in github https://github.com/DaRkD0G/LoadExtension

extension Dictionary {
    /**
        Load a Plist file from the app bundle into a new dictionary

        :param: File name
        :return: Dictionary<String, AnyObject>?
    */
    static func loadPlistFromProject(filename: String) -> Dictionary<String, AnyObject>? {

        if let path = NSBundle.mainBundle().pathForResource("GameParam", ofType: "plist") {
            return NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject>
        }
        println("Could not find file: \(filename)")
        return nil
    }
}

And you can use that for load

/**
  Example function for load Files Plist

  :param: Name File Plist
*/
func loadPlist(filename: String) -> ExampleClass? {
    if let dictionary = Dictionary<String, AnyObject>.loadPlistFromProject(filename) {
        let stringValue = (dictionary["name"] as NSString)
        let intergerValue = (dictionary["score"] as NSString).integerValue
        let doubleValue = (dictionary["transition"] as NSString).doubleValue

        return ExampleClass(stringValue: stringValue, intergerValue: intergerValue, doubleValue: doubleValue)
    }
    return nil
}
YanSte
  • 10,661
  • 3
  • 57
  • 53
2

Here is a bit shorter version, based on @connor 's answer

guard let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"),
    let myDict = NSDictionary(contentsOfFile: path) else {
    return nil
}

let value = dict.value(forKey: "CLIENT_ID") as! String?
Bence Pattogato
  • 3,752
  • 22
  • 30
2

Swift 3.0

if let path = Bundle.main.path(forResource: "config", ofType: "plist") {
    let dict = NSDictionary(contentsOfFile: path)

    // use dictionary
}

The easiest way to do this in my opinion.

quemeful
  • 9,542
  • 4
  • 60
  • 69
2

I've created a simple Dictionary initializer that replaces NSDictionary(contentsOfFile: path). Just remove the NS.

extension Dictionary where Key == String, Value == Any {

    public init?(contentsOfFile path: String) {
        let url = URL(fileURLWithPath: path)

        self.init(contentsOfURL: url)
    }

    public init?(contentsOfURL url: URL) {
        guard let data = try? Data(contentsOf: url),
            let dictionary = (try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]) ?? nil
            else { return nil }

        self = dictionary
    }

}

You can use it like so:

let filePath = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
let preferences = Dictionary(contentsOfFile: filePath)!
UserDefaults.standard.register(defaults: preferences)
Shmidt
  • 16,436
  • 18
  • 88
  • 136
Jordan H
  • 52,571
  • 37
  • 201
  • 351
2

Swift 4.0 iOS 11.2.6 list parsed and code to parse it, based on https://stackoverflow.com/users/3647770/ashok-r answer above.

<?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">
<array>
  <dict>
    <key>identity</key>
    <string>blah-1</string>
    <key>major</key>
    <string>1</string>
    <key>minor</key>
    <string>1</string>
    <key>uuid</key>
    <string>f45321</string>
    <key>web</key>
    <string>http://web</string>
</dict>
<dict>
    <key>identity</key>
    <string></string>
    <key>major</key>
    <string></string>
    <key>minor</key>
    <string></string>
    <key>uuid</key>
    <string></string>
    <key>web</key>
    <string></string>
  </dict>
</array>
</plist>

do {
   let plistXML = try Data(contentsOf: url)
    var plistData: [[String: AnyObject]] = [[:]]
    var propertyListFormat =  PropertyListSerialization.PropertyListFormat.xml
        do {
            plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as! [[String:AnyObject]]

        } catch {
            print("Error reading plist: \(error), format: \(propertyListFormat)")
        }
    } catch {
        print("error no upload")
    }
Ky -
  • 30,724
  • 51
  • 192
  • 308
user3069232
  • 8,587
  • 7
  • 46
  • 87
0

Here's the solution I found:

let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let test: AnyObject = levelBlocks.objectForKey("Level1")
println(test) // Prints the value of test

I set the type of test to AnyObject to silence a warning about an unexpected inference that could occur.

Also, it has to be done in a class method.

To access and save a specific value of a known type:

let value = levelBlocks.objectForKey("Level1").objectForKey("amount") as Int
println(toString(value)) // Converts value to String and prints it
TheAppleMan
  • 11
  • 1
  • 5
0

I use swift dictionaries but convert them to and from NSDictionaries in my file manager class like so:

    func writePlist(fileName:String, myDict:Dictionary<String, AnyObject>){
        let docsDir:String = dirPaths[0] as String
        let docPath = docsDir + "/" + fileName
        let thisDict = myDict as NSDictionary
        if(thisDict.writeToFile(docPath, atomically: true)){
            NSLog("success")
        } else {
            NSLog("failure")
        }

    }
    func getPlist(fileName:String)->Dictionary<String, AnyObject>{
        let docsDir:String = dirPaths[0] as String
        let docPath = docsDir + "/" + fileName
        let thisDict = NSDictionary(contentsOfFile: docPath)
        return thisDict! as! Dictionary<String, AnyObject>
    }

This seems the least troubling way to read and write but let's the rest of my code stay as swift as possible.

e.w. parris
  • 1,093
  • 10
  • 11
0

Plist is a simple Swift enum I made for working with property lists.

// load an applications info.plist data

let info = Plist(NSBundle.mainBundle().infoDictionary)
let identifier = info["CFBundleIndentifier"].string!

More examples:

import Plist

// initialize using an NSDictionary
// and retrieve keyed values

let info = Plist(dict)
let name = info["name"].string ?? ""
let age = info["age"].int ?? 0


// initialize using an NSArray
// and retrieve indexed values

let info = Plist(array)
let itemAtIndex0 = info[0].value


// utility initiaizer to load a plist file at specified path
let info = Plist(path: "path_to_plist_file")

// we support index chaining - you can get to a dictionary from an array via
// a dictionary and so on
// don't worry, the following will not fail with errors in case
// the index path is invalid
if let complicatedAccessOfSomeStringValueOfInterest = info["dictKey"][10]["anotherKey"].string {
  // do something
}
else {
  // data cannot be indexed
}

// you can also re-use parts of a plist data structure

let info = Plist(...)
let firstSection = info["Sections"][0]["SectionData"]
let sectionKey = firstSection["key"].string!
let sectionSecret = firstSection["secret"].int!

Plist.swift

Plist itself is quite simple, here's its listing in case you to refer directly.

//
//  Plist.swift
//


import Foundation


public enum Plist {

    case dictionary(NSDictionary)
    case Array(NSArray)
    case Value(Any)
    case none

    public init(_ dict: NSDictionary) {
        self = .dictionary(dict)
    }

    public init(_ array: NSArray) {
        self = .Array(array)
    }

    public init(_ value: Any?) {
        self = Plist.wrap(value)
    }

}


// MARK:- initialize from a path

extension Plist {

    public init(path: String) {
        if let dict = NSDictionary(contentsOfFile: path) {
            self = .dictionary(dict)
        }
        else if let array = NSArray(contentsOfFile: path) {
            self = .Array(array)
        }
        else {
            self = .none
        }
    }

}


// MARK:- private helpers

extension Plist {

    /// wraps a given object to a Plist
    fileprivate static func wrap(_ object: Any?) -> Plist {

        if let dict = object as? NSDictionary {
            return .dictionary(dict)
        }
        if let array = object as? NSArray {
            return .Array(array)
        }
        if let value = object {
            return .Value(value)
        }
        return .none
    }

    /// tries to cast to an optional T
    fileprivate func cast<T>() -> T? {
        switch self {
        case let .Value(value):
            return value as? T
        default:
            return nil
        }
    }
}

// MARK:- subscripting

extension Plist {

    /// index a dictionary
    public subscript(key: String) -> Plist {
        switch self {

        case let .dictionary(dict):
            let v = dict.object(forKey: key)
            return Plist.wrap(v)

        default:
            return .none
        }
    }

    /// index an array
    public subscript(index: Int) -> Plist {
        switch self {
        case let .Array(array):
            if index >= 0 && index < array.count {
                return Plist.wrap(array[index])
            }
            return .none

        default:
            return .none
        }
    }

}


// MARK:- Value extraction

extension Plist {

    public var string: String?       { return cast() }
    public var int: Int?             { return cast() }
    public var double: Double?       { return cast() }
    public var float: Float?         { return cast() }
    public var date: Date?         { return cast() }
    public var data: Data?         { return cast() }
    public var number: NSNumber?     { return cast() }
    public var bool: Bool?           { return cast() }


    // unwraps and returns the underlying value
    public var value: Any? {
        switch self {
        case let .Value(value):
            return value
        case let .dictionary(dict):
            return dict
        case let .Array(array):
            return array
        case .none:
            return nil
        }
    }

    // returns the underlying array
    public var array: NSArray? {
        switch self {
        case let .Array(array):
            return array
        default:
            return nil
        }
    }

    // returns the underlying dictionary
    public var dict: NSDictionary? {
        switch self {
        case let .dictionary(dict):
            return dict
        default:
            return nil
        }
    }

}


// MARK:- CustomStringConvertible

extension Plist : CustomStringConvertible {
    public var description:String {
        switch self {
        case let .Array(array): return "(array \(array))"
        case let .dictionary(dict): return "(dict \(dict))"
        case let .Value(value): return "(value \(value))"
        case .none: return "(none)"
        }
    }
}
Benzi
  • 2,439
  • 18
  • 25
0

Swift 3.0

if you want to read a "2-dimensional Array" from .plist, you can try it like this:

if let path = Bundle.main.path(forResource: "Info", ofType: "plist") {
    if let dimension1 = NSDictionary(contentsOfFile: path) {
        if let dimension2 = dimension1["key"] as? [String] {
            destination_array = dimension2
        }
    }
}
Sandu
  • 480
  • 5
  • 13
0

If you have Info.plist, then use

Bundle.main.infoDictionary
macio.Jun
  • 9,647
  • 1
  • 45
  • 41
-2

Simple struct to access plist file (Swift 2.0)

struct Configuration {      
  static let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
  static let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]

  static let someValue = dict["someKey"] as! String
}

Usage:

print("someValue = \(Configuration.someValue)")
n0_quarter
  • 538
  • 1
  • 10
  • 23