くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

SwiftUIでjsonデータを取り扱ってみる

今回はjsonデータを使って画面表示を行ってみたいと思います

APIを使って戻り値のjsonデータをパースして値を取得するというのはモバイル開発では必ずと言っていいほどやる処理だと思うので、学んでいきます!!

jsonデータの準備

今回は実際にAPIを使ってではなく、jsonのデータを用意して、そのデータを取得するようにします

今回は人間の情報をjsonで定義します

Resourceフォルダを新しく作成して、HumanData.jsonを作成しました

[
    {
        "name": "Tom",
        "age": "28",
        "city": "AAAAA",
        "sex": "M",
        "id": 1001,
        "extra": {
            "blood": "O",
            "hand": "left"
        },
    },
    {
        "name": "Nick",
        "age": "30",
        "city": "BBBBB",
        "sex": "F",
        "id": 1002,
        "extra": {
            "blood": "A",
            "hand": "left"
        },
    }
]

このjsonデータをモデルにしたものを新しく作成します

Modelフォルダを新しく作成してHuman.swiftを作りました

import SwiftUI

struct Human: Hashable, Codable {
    var id: Int
    var name: String
    var age: String
    var city: String
    var sex: String
    var extra: Extra
}

struct Extra: Hashable, Codable {
    var blood: String
    var hand: String
}

また、定義したjsonデータをモデルに変換する処理をModelフォルダ配下にData.swiftとして作成します

import UIKit
import SwiftUI
import CoreLocation

let humanData: [Human] = load("HumanData.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)")
    }
}

上記はどちらかというとSwiftの解説になるので、別の機会にできたら行いたいですが、勉強になるのでみてみるといいかもしれません

チュートリアルからダウンロードできるものを少し改良しました

ここまででデータの準備はできたので、あとは表示させるクラスを準備します

HumanRow.swiftを作成します

import SwiftUI

struct HumanRow: View {
    
    var human: Human
    
    var body: some View {
        Text(human.age)
    }
}

struct HumanRow_Previews: PreviewProvider {
    static var previews: some View {
        HumanRow(human: humanData[0])
    }
}

表示するデータであるhumanをプレビューに渡すことで表示できるようになります

f:id:kumaskun:20200808230741p:plain
プレビュー

また、下記のようにGroupで囲むことで複数表示できます

import SwiftUI

struct HumanRow: View {
    
    var human: Human
    
    var body: some View {
        Text(human.age)
    }
}

struct HumanRow_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            HumanRow(human: humanData[0])
            HumanRow(human: humanData[1])
        }
    }
}

f:id:kumaskun:20200808231645p:plain

これだと複数のプレビューができてしまいます

一般的なリストは1つのプレビューに複数のリストを表示させると思うので、これだと想像していたのとは違いました

では、リスト表示はどうすればいいのでしょうか?

これはちょっとした発想の転換ですが、Listのクラスを列を表示させれば実現できます

import SwiftUI

struct HumanList: View {
    var body: some View {
        List {
            HumanRow(human: humanData[0])
            HumanRow(human: humanData[1])
        }
    }
}

struct HumanList_Previews: PreviewProvider {
    static var previews: some View {
        HumanList()
    }
}  

f:id:kumaskun:20200808233246p:plain

ただ、これだと100個データがあったら100行書かないといけなくなり大変です

for文で回すような処理が欲しいところですね

そこで下記のようにするとクロージャーから列を取得でき、それを表示できます

import SwiftUI

struct HumanList: View {
    var body: some View {
        List(humanData, id: \.name) {human in
            HumanRow(human:human)
        }
    }
}

struct HumanList_Previews: PreviewProvider {
    static var previews: some View {
        HumanList()
    }
}

f:id:kumaskun:20200809030357p:plain

上記だとユニークなキーを元にして値を取得していますが、モデルでIdentifiableプロトコルを適用させればキーを使わずに表示させることが可能です

import SwiftUI

struct Human: Hashable, Codable, Identifiable {
    var id: Int
    var name: String
    var age: String
    var city: String
    var sex: String
    var extra: Extra
}

struct Extra: Hashable, Codable {
    var blood: String
    var hand: String
}
import SwiftUI

struct HumanList: View {
    var body: some View {
        List(humanData) {human in
            HumanRow(human:human)
        }
    }
}

struct HumanList_Previews: PreviewProvider {
    static var previews: some View {
        HumanList()
    }
}

ユニークなデータであることが前提ではありますが、これだと少し簡略にできますね!

最後にリストにNavigation Linkを付けたいと思います

import SwiftUI

struct HumanList: View {
    var body: some View {
        NavigationView {
            List(humanData) {human in
                NavigationLink(destination: HumanDetail()) {
                    HumanRow(human:human)
                }
            }
            .navigationBarTitle(Text("Human"))
        }
    }
}

struct HumanList_Previews: PreviewProvider {
    static var previews: some View {
        HumanList()
    }
}

HumanDetailというクラスを空で作った上で上記のようにすれば、Navigation Linkが追加されます

また、navigationBarTitleで上に表示するタイトルを決めることができます

これはスコープを意識しないとたまに間違えてしまうんですが、NavigationView内でListの次に書かないとうまく表示されないです

ついでに血液型も追加して表示してみたらこんな感じでした

f:id:kumaskun:20200809033357p:plain
Navigation Linkのイメージ

これでjsonの扱いはある程度できることがわかりました!!

ただ、細かいサイズの指定や場所の指定などできるのかが今の自分の学びの中では未知数なので、調べていきたいです