SwiftUI

[SwiftUI] JSONファイルをSwiftUIでDecodeし、Viewで活用する方法 (ObservableObject)

SwiftUI

以下の3つの整合性がしっかり合っていないと、プレビューがクラッシュする上、どこに問題があるのかデバッグが難しいので、念入りに確認するのがよい。

  • JSONファイル(.jsonファイル) 今回はteam.jsonというファイル
  • JSONデータ構造定義(.swiftファイル) 今回はPlayer.swiftというファイル
  • JSON Decode処理(.swiftファイル) 今回はModelData.swiftというファイル

選手データとして、「ポジション」「背番号」「名前」の3つを持ち、その選手データを配列として持つチームデータ、というデータ構造を例にする。

環境:Xcode: 12.4, Swift: 5

JSONファイル (team.json)

[ads]

team.jsonの内容。チーム名として、teamNameを1つもち、チームのプレーヤー情報として、playersとして配列構造でデータを持つ。playerはポジション、背番号、名前をそれぞれ持つ。(position, number, name)

なお、JSONフォーマットに従っていることが大前提なので、実績のないデータであれば、念の為JSON Validatorなどで確認するのがよい。https://jsonlint.com/
実際、SwiftUI側の処理が正しかったのに、JSONデータでカッコが一つ足りなかったりでクラッシュして少しハマってしまったりもあった。その際も、”JSONフォーマット不正”のような親切なエラーメッセージは出てこないので焦らないことが大事。

{
    "teamName": "ホームチーム",
    "players": [
        {
            "position": "GK",
            "number": "1",
            "name": "キーバー 太郎"
        }, {
            "position": "DF",
            "number": "2",
            "name": "ディフェンス 太郎"
        }, {
            "position": "MF",
            "number": "8",
            "name": "中盤 太郎"
        }, {
            "position": "FW",
            "number": "11",
            "name": "オフェンス 太郎"
        }
    ]
}

JSONデータ構造を定義

[ads]

まずは、Player.swiftというファイルを新規に作成し、以下の様にJSONファイルの内容に従って、JSONデータ構造を定義する。Hashableは必要に必ずしも必要ではないが、とりあえずつけておくと便利。playersはPlayerの配列なので、[]をつける。





JSON Decode処理を追加 (ModelData.swift)

ModelData.swiftの内容。
本コードは、Apple公式のチュートリアルを流用。また、ObservableObjectとしてModelDataクラスを定義し、その中にTeam構造体をロードしておく。こうすることで、別ファイルで定義したViewでこのロードしたデータを呼び出すことができる。

import Foundation

final class ModelData: ObservableObject {
    @Published var team: Team = load("team.json")
}

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

参考:SwiftUIチュートリアル(Step9付近)
https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation

これで、JSONファイルを読み込む準備は完了。次にViewで表示する準備をする。

Viewで表示

[ads]

トップレベルファイル ([プロジェクト名]App.swift)

@StateObjectとすることで、ObservableObjectをインスタンス化する。
また、environmentObjectモディファイヤ−をContentViewの呼び出しに加えることで、ObservableObjectにアクセスできるようになる。

import SwiftUI

@main
struct sampleApp: App {
    @StateObject private var modelData = ModelData()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(modelData)
        }
    }
}

Viewで表示する準備 (ContentView.swift)

ContentViewにも同様にObservableObjectにアクセスできるように修正を入れる。まずは、Preview側を修正することで、ビルドエラーが消えるのでそちらから始めるのがよい。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var teamData = ModelData().team

    static var previews: some View {
        ContentView()
            .environmentObject(ModelData())
    }
}

これで、JSONファイルを読み込んだ状態でビルドできるようになったはず。次はJSONファイルの内容を表示する部分。

Viewで表示する ContentView.swift

例えば、単純にJSONファイルからチーム名を表示する場合は以下の様にすればよい。

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData

    var body: some View {
        Text(modelData.team.teamName)
            .padding()
    }
}

配列構造部分も表示する例は以下の通り。

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData

    var body: some View {
        VStack {
            Text(modelData.team.teamName)
                .padding()
            ForEach(modelData.team.players, id: \.hashValue) {
                player in
                HStack() {
                    Text(player.position)
                    Text(player.number)
                    Text(player.name)
                }
            }
        }
    }
}

画面上の表示はこのようになる。

まとめ

[ads]

Xcodeは中々親切なエラーメッセージは出力してくれないので、複雑なデータを扱う際は、局所的に初めて、少しずつデータを拡張していくアプローチがよさそう。

Ads