Swiftで Property List をPropertyListDecoderを使ってパースする

Property List (plist)ファイルで定義した配列をUITableViewで表示したいという質問に回答しました。

私もObjective-C を使っていた時代にplistで表示項目のON/OFFの切り替えを管理していた時期を思い出しました。あの頃は PropertyListDecoder がなくて、plistファイルをNSArrayまたはNSDictionaryで管理する必要があり、データのパースには頭を悩ませました。

Swift 4にて PropertyListDecoder が追加され、これが思った以上に便利でしたので 、本記事では plistファイルをPropertyListDecoderを使ってデシリアライズする方法を紹介します。

Property Listを以下の通り定義している

まず、plistはセクションを分けて表示できるように大きく「セクション1」と「セクション2」で分けています。

f:id:ch3cooh393:20200816213556p:plain

本来であればNSArrayなりを使って自前でパースする必要があります。

モデルの型を定義する

下記のようにstruct型を定義しておきます。ItemGroup型とItemData型のプロパティで定義した名前とplist側のkey名とを一致させるようにしておいてください。

struct ItemData: Decodable {
    let name: String
    let imageName: String
}

struct ItemGroup: Decodable {
    let sectionTitle: String
    let items: [ItemData]
}

PropertyListDecoderを使って指定したplistファイルを先ほど定義したモデルにマッピングします。

if let path = Bundle.main.path(forResource: "Items", ofType:"plist" ) {
    let data = try! Data(contentsOf: URL(fileURLWithPath: path))
    items = try! PropertyListDecoder().decode([ItemGroup].self, from: data)
}

パースしたデータをUITableViewで表示する

画面側では以下のように、Items.plist を読み出して PropertyListDecoder を使ってItemGroup型へデシリアライズします。

class ViewController: UITableViewController {

    var items: [ItemGroup] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        if let path = Bundle.main.path(forResource: "Items", ofType:"plist" ) {
            let data = try! Data(contentsOf: URL(fileURLWithPath: path))
            items = try! PropertyListDecoder().decode([ItemGroup].self, from: data)
        }
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items[section].items.count
    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return items[section].sectionTitle
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = items[indexPath.section].items[indexPath.row]

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = item.name
        cell.imageView?.image = UIImage(named: item.imageName)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = items[indexPath.section].items[indexPath.row]

        print("item: \(item.name)")
    }
}

上記のコードを実行すると下図のように表示されます。

f:id:ch3cooh393:20200816213842p:plain

サンプルプロジェクト

Xcode 11.6 (iOS 13.6)時点で実行可能なサンプルプロジェクトを GitHub へアップロードしておきました。

github.com