xmlやplistを使うことも考えたが、codableを使うことで独自のモデルにも容易に対応させることができた。
環境
- Xcode 11.4.1
- Swift 5
- iOS 13.4.1
StringやIntなどは自動Codableに準拠しているので、encodeやdecodeメソッドを使う必要はないが、
今回はその他のデータ型にも対応できるようにencode、decodeメソッドを実装した。
モデル[Recipe]
- import Foundation
- import RealmSwift
- class Recipe: Object,Decodable {
- @objc dynamic var name = ""
- @objc dynamic var category = ""
- @objc dynamic var country = ""
- @objc dynamic var season = ""
- var ingredients = List<Ingredient>() //RecipeとIngredientは1対多
- override static func primaryKey() -> String? {
- return "name"
- }
- private enum CodingKeys:String,CodingKey{
- case name
- case category
- case country
- case season
- case ingredients
- }
- required convenience public init(from decoder:Decoder) throws{
- self.init()
- let container = try decoder.container(keyedBy: CodingKeys.self)
- name = try container.decode(String.self, forKey: .name)
- category = try container.decode(String.self, forKey: .category)
- country = try container.decode(String.self, forKey: .country)
- season = try container.decode(String.self, forKey: .season)
- //一度配列に格納した後、Listに変換する
- let ingredientsArray = try container.decode([Ingredient].self, forKey: .ingredients)
- ingredients = ingredientsArray.reduce(List<Ingredient>()) {$0.append($1); return $0}
- }
- }
モデル[ingredient]
- import Foundation
- import RealmSwift
- class Ingredient: Object,Decodable {
- @objc dynamic var name = ""
- let recipes = LinkingObjects(fromType: Recipe.self, property: "ingredients")
- private enum CodingKeys: String, CodingKey {
- case name
- }
- required convenience public init(from decoder: Decoder) throws {
- self.init()
- let container = try decoder.container(keyedBy: CodingKeys.self)
- name = try container.decode(String.self, forKey: .name)
- }
- }
JSONファイル
[ { "name": "トマトパスタ", "category": "パスタ", "country": "イタリアン", "season": "Any", "ingredients":[ { "name": "トマト" }, { "name": "にんにく" } ] }, { "name": "カツ丼", "category": "丼ぶり", "country": "和食", "season": "Any", "ingredients":[ { "name": "たまねぎ" }, { "name": "卵" }, { "name": "豚肉" } ] }, { "name": "ペペロンチーノ", "category": "パスタ", "country": "イタリアン", "season": "Any", "ingredients":[ { "name": "にんにく" }, { "name": "鷹の爪" } ] }, { "name": "カレー", "category": "カレー", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "じゃがいも" }, { "name": "牛肉" } ] }, { "name": "シチュー", "category": "シチュー", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "じゃがいも" }, { "name": "牛肉" } ] }, { "name": "炒飯", "category": "ご飯", "country": "中華", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "たまねぎ" }, { "name": "卵" }, { "name": "にんにく" } ] }, { "name": "炊き込みご飯", "category": "ご飯", "country": "和食", "season": "Any", "ingredients":[ { "name": "人参" }, { "name": "椎茸" }, { "name": "鶏肉" }, { "name": "大葉" } ] } ]
JSONデータの取り込み
- //JSONファイルのパスを取得
- guard let path = Bundle.main.path(forResource: "RecipesList", ofType: "json") else { return }
- let url = URL(fileURLWithPath: path)
- do {
- let realm = try! Realm()
- let data = try Data(contentsOf: url)
- let recipe_obj = try! JSONDecoder().decode([Recipe].self, from: data)
- try! realm.write {
- realm.deleteAll()
- realm.add(recipe_obj, update: true)
- //人参を使うレシピを取得・表示
- var objects: Results<Recipe>
- objects = realm.objects(Recipe.self).filter("SUBQUERY(ingredients, $ingredient, $ingredient.name = %@).@count >= 1","人参")
- print("objectsの中",objects)
- }
- } catch {
- print("エラー")
- }