2020.8.9
特定の種類のCodableデータの読み込み
Loading a specific kind of Codable data
2つの異なる種類のJSONをSwift構造体にロード
1つは宇宙飛行士用
もう1つはミッション用
project8-files
astronauts.jsonとMissions.json
astronauts.json
import Foundation extension Bundle { func decode(_ file: String) -> [Astronaut] { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else { fatalError("Failed to load \(file) from bundle.") } let decoder = JSONDecoder() guard let loaded = try? decoder.decode([Astronaut].self, from: data) else { fatalError("Failed to decode \(file) from bundle.") } return loaded } } /----------------------------------------------------------- import SwiftUI struct Astronaut: Codable, Identifiable { let id: String let name: String let description: String } struct ContentView: View { let astronauts = Bundle.main.decode("astronauts.json") var body: some View { Text("\(astronauts.count)") } }
ジェネリックを使用してあらゆる種類のCodableデータをロードする
Using generics to load any kind of Codable data
missions.json
struct CrewRole: Codable { let name: String let role: String } struct Mission: Codable, Identifiable { let id: Int let launchDate: String? let crew: [CrewRole] let description: String }
This is called a nested struct
// Bundle-Decodable.swift
// Moonshot
import Foundation extension Bundle { // ジェネリック版 func decode<T: Codable>(_ file: String) -> T { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else { fatalError("Failed to load \(file) from bundle.") } let decoder = JSONDecoder() guard let loaded = try? decoder.decode(T.self, from: data) else { fatalError("Failed to decode \(file) from bundle.") } return loaded } // 非ジェネリック版 /* func decode(_ file: String) -> [Astronaut] { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else { fatalError("Failed to load \(file) from bundle.") } let decoder = JSONDecoder() guard let loaded = try? decoder.decode([Astronaut].self, from: data) else { fatalError("Failed to decode \(file) from bundle.") } return loaded } */ }
import SwiftUI //------------------------------------ struct Astronaut: Codable, Identifiable { let id: String let name: String let description: String } //------------------------------------ struct Mission: Codable, Identifiable { struct CrewRole: Codable { let name: String let role: String } let id: Int let launchDate: String? let crew: [CrewRole] let description: String } //------------------------------------ struct ContentView: View { let astronauts: [Astronaut] = Bundle.main.decode("astronauts.json") let missions: [Mission] = Bundle.main.decode("missions.json") var body: some View { Text("\(astronauts.count)") } }
ミッションビューのフォーマット
Formatting our mission view
// Bundle-Decodable.swift import Foundation extension Bundle { // ジェネリック版 func decode<T: Codable>(_ file: String) -> T { guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else { fatalError("Failed to load \(file) from bundle.") } let decoder = JSONDecoder() let formatter = DateFormatter() formatter.dateFormat = "y-MM-dd" decoder.dateDecodingStrategy = .formatted(formatter) guard let loaded = try? decoder.decode(T.self, from: data) else { fatalError("Failed to decode \(file) from bundle.") } return loaded }
import SwiftUI //------------------------------------ struct Astronaut: Codable, Identifiable { let id: String let name: String let description: String } //------------------------------------ struct Mission: Codable, Identifiable { struct CrewRole: Codable { let name: String let role: String } let id: Int let launchDate: Date? //let launchDate: String? let crew: [CrewRole] let description: String //---------------------- var displayName: String { "Apollo \(id)" } var image: String { "apollo\(id)" } //---------------------- var formattedLaunchDate: String { if let launchDate = launchDate { let formatter = DateFormatter() formatter.dateStyle = .long return formatter.string(from: launchDate) } else { return "N/A" } } } //------------------------------------ struct ContentView: View { let astronauts: [Astronaut] = Bundle.main.decode("astronauts.json") let missions: [Mission] = Bundle.main.decode("missions.json") var body: some View { NavigationView { List(missions) { mission in NavigationLink(destination: Text("Detail view")) { Image(mission.image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 44, height: 44) VStack(alignment: .leading) { Text(mission.displayName) .font(.headline) //Text(mission.launchDate ?? "N/A") Text(mission.formattedLaunchDate) } } } .navigationBarTitle("Moonshot") } } }