6

I've already used this method in Swift 2

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

But don't know how to read plist in Swift3 without using NSDictionary(contentsOfFile: path)

Karthikeyan Bose
  • 1,244
  • 3
  • 17
  • 26

3 Answers3

32

The native Swift way is to use PropertyListSerialization

if let url = Bundle.main.url(forResource:"Config", withExtension: "plist") {
   do {
     let data = try Data(contentsOf:url)
     let swiftDictionary = try PropertyListSerialization.propertyList(from: data, format: nil) as! [String:Any]
      // do something with the dictionary
   } catch {
      print(error)
   }
}

You can also use NSDictionary(contentsOf: with a type cast:

if let url = Bundle.main.url(forResource:"Config", withExtension: "plist"),
   let myDict = NSDictionary(contentsOf: url) as? [String:Any] {
   print(myDict)
}

but you explicitly wrote: without using NSDictionary(contentsOf...

Basically don't use NSDictionary without casting in Swift, you are throwing away the important type information.


Meanwhile (Swift 4+) there is still more comfortable PropertyListDecoder which is able to decode Plist directly into a model.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • So to use Dictionary in native way, I have to use **PropertyListSerialization** right – Karthikeyan Bose Nov 05 '16 at 10:22
  • 1
    @vadian I'd add that `import Foundation` is necessary for this to work under native `Swift`. – dinesharjani May 30 '17 at 18:48
  • @dinesharjani Yes, it is, but `Foundation` is necessary for `Data` and `PropertyListSerialization`, too, so we can assume it's imported anyway. – vadian May 30 '17 at 18:55
  • 1
    @vadian if you're doing a server-side project like `Perfect`, `Foundation` will not be automatically imported for you :) My thought process here is to catch the "copy-paste-doesn't work-keep googling" kind of guy (or gal) who'll go in circles because they'll miss the import. That's all. – dinesharjani May 30 '17 at 19:00
6

PropertyListDecoder can be used to decode plist file directly to Objects.

1: Sample Plist File (sample.plist)

<?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">
<dict>
    <key>key1</key>
    <string>valua for key1</string>
    <key> key2</key>
    <string>valua for key1</string>
    <key>CustomClass1</key>
    <dict>
        <key>customClass1_key</key>
        <string>customClass1_value</string>
    </dict>
    <key>CustomClass2</key>
    <dict>
        <key>customClass2_kek</key>
        <string>customClasse_value</string>
    </dict>
</dict>
</plist>

2: Corespoinding Models for plist

struct PlistConfiguration: Codable {
    var key1:String?
    var customClass1Obj: CustomClass1?
    var customClass2Obj: CustomClass2?
    
    private enum CodingKeys : String, CodingKey {
              case key1 = "key1"
              case customClass1Obj = "CustomClass1"
              case customClass2Obj = "CustomClass2"
    }
    
}

2.1: Nested Model

struct CustomClass1: Codable {
    var customClass1_key:String?
    private enum CodingKeys : String, CodingKey {
              case customClass1_key = "customClass1_key"
    }
    
}

2.2: Nested Model

struct CustomClass2: Codable {
    var customClass2_key: String?
    private enum CodingKeys : String, CodingKey {
              case customClass2_key = "customClass2_key"
    }
    
}

3: Read Plist from main app bundle

func parseConfig() -> PlistConfiguration {
        let url = Bundle.main.url(forResource: "sample", withExtension: "plist")!
        let data = try! Data(contentsOf: url)
        let decoder = PropertyListDecoder()
        return try! decoder.decode(PlistConfiguration.self, from: data)
    }
Imran
  • 3,045
  • 2
  • 22
  • 33
  • You saved my time. But .. shouldn't the return of parseConfig() be PlistConfiguration instead of SKDConfiguration? – aaru Jul 11 '20 at 08:16
1

In a modern Swift environment I use stuff like this:

import Foundation

public extension Bundle {

  func plist<As>(from resource: String) -> As? where As: Decodable {
    guard let plist = Bundle.main.url(forResource: resource, withExtension: "plist") else { return nil }
    let decoder = PropertyListDecoder()
    do {
      let data = try Data(contentsOf: plist)
      return try decoder.decode(As.self, from: data)
    } catch { return nil }
  }

}