実現したいこと
ここに実現したいことを箇条書きで書いてください。
- Swift UIで、Firestoreから取得したデータを2次元配列に格納して、List表示する
- @Observableを利用して、取得データを適切に画面反映する
前提
Itemという構造体を定義し、Firestore上にそのデータを保持しています。Itemは、アイテム名の他に、カテゴリー名と紐づくID(Int)を持っています。
アプリ側では、「Item配列」としてデータを取得した後、カテゴリーIDごとにアイテムを並び替え、セクション名=カテゴリー名となるようにリスト表示します。
FirestoreデータはListService内で読み込む関数を定義し(func readItems())、そのコールバックの呼び出し元をListViewModelに定義しています。
構成
- ListView : Itemを表示するSwift UI View
- ListViewModel: データの加工や並び替えを主に行うクラス
- ListService: データ取得を行うクラス
- Item: 構造体クラス
ターゲット
- iOS17.2
発生している問題・エラーメッセージ
ListViewModel内で、リスト表示用の2次元配列を生成し、ListViewで表示しようとしていますが、どうしてもデータ取得完了前に画面描画が完了してしまうようで、リストが表示されません。
(2次元配列へのデータ格納はできていることを確認済みです)
ListViewModelを@Observationで定義し、ListView内で@Stateでインスタンス化することで、反映させようとしていますがうまく動作してくれません。
ForEachで2次元配列を表示する部分については問題なさそうなので、データの画面反映の部分の問題だと考えております。
2、3日情報を集めたり修正したりしましたが、うまく突破できず、お力をお貸しいただけると本当に助かります。
どうぞよろしくお願いいたします。
エラーメッセージ
該当のソースコード
以下が、該当コードです。投稿時に変数名を修正したので一部間違えている可能性がありますがご容赦ください・・
Item.swift
1 2import Foundation 3import FirebaseFirestoreSwift 4 5struct Item: Codable, Identifiable, Hashable { 6 7 @DocumentID var id: String? 8 9 var brandid : String 10 var brandname : String 11 var categoryid : Int 12 var categoryname : String 13 var createdAt : Date //作成日 14 var itemid : String 15 var memo : String 16 var name : String 17 var subcategoryid : Int 18 var subcategoryname : String 19 var updatedAt : Date //更新日 20}
ItemService.swift
1 2import Foundation 3import FirebaseFirestore 4import FirebaseFirestoreSwift 5 6struct ItemService { 7 8 func readItems(list:List, completion: @escaping([Item]) -> Void) { 9 10 var items = [Item]() 11 12 //Listの"listitems"から、itemidだけを抜き出した配列を作る 13 let listItems:[listItem] = List.listitems ?? [] 14 let itemIds = listItems.map ( { (st) -> String in 15 return st.itemid 16 }) 17 18 let docRef = Firestore.firestore().collection("users").document("userid10001").collection("item") 19 20 //itemIdsに入っているitemidを使って、リストに含まれるアイテムの詳細情報を読む 21 docRef.whereField("itemid", in: itemIds).getDocuments() { querySnapshot, error in 22 if let error = error { 23 print("Error getting document: \(error)") 24 } else { 25 for documentChange in querySnapshot!.documentChanges { 26 if documentChange.type == .added { 27 do { 28 let item = try documentChange.document.data(as: Item.self) 29 items.append(item) 30 } catch { 31 print(error.localizedDescription) 32 } 33 } 34 } 35 } 36 completion(items) 37 } 38 } 39
ListViewModel.swift
1 2import Foundation 3import FirebaseFirestore 4import Observation 5import Collections 6 7@Observable class ListViewModel { 8 9 private var list : List = List(listid: "1", listname: "", lengthof: "", howto: "", startdate: Date(), baseweight: 0, consumableweight: 0, wornweight: 0, totalweight: 0, listitems: [listItem]()) 10 11 private let itemservice = ItemService() 12 13 var listData: [[Item]] = [] 14 15 /////////////////////////// クラス初期化(引数なし) /////////////////////// 16 init() { 17 } 18 19 /////////////////////////// クラス初期化(引数あり) /////////////////////// 20 init(list: List) { 21 self.list = List 22 readAndSortItems(list: List) 23 } 24 25 func readAndSortItems(list: List) -> Void { 26 27 itemservice.readItems(list: List) { (items) in 28 29 let items = items 30 31 //1.categoryID(Int)だけを取り出して新しい配列を作成 32 let categoryIds = items.map { (item) -> Int in 33 return item.categoryid 34 } 35 36 //2.重複削除 37 let uniqueCategoryIds = OrderedSet(categoryIds) 38 39 //3.categoryIDの小さい順に並び替え 40 let sortedCategoryIds = uniqueCategoryIds.sorted{ $0 < $1 } 41 42 //List表示用の2次元配列を作成(要素はItemクラス) 43 //sortedCategoryIdsの中のカテゴリーID順に、itemArrayを検索して、当てはまるものを順番に2次元配列に入れる 44 for (_, categoyIdNum) in sortedCategoryIds.enumerated() { 45 let itemArray = items.filter( {$0.categoryid == categoyIdNum} ) 46 self.listData.append(itemArray) 47 } 48 } 49 } 50
ListView.swift
1 2import SwiftUI 3import Observation 4 5struct ListView: View { 6 7 var list: List 8 //viewModelプロパティに@Stateを付与して値監視 9 @State private var listVM = ListViewModel() 10 @Binding var path: [List] 11 12 init(list: List, path: Binding<[List]>) { 13 self.list = list 14 self._path = path 15 self.ListVM = ListViewModel(list: List) 16 } 17 18 19 var body: some View { 20 let _ = Self._printChanges() 21 VStack { 22 23 ZStack { 24 Color.init(uiColor: UIColor.secondarySystemBackground).edgesIgnoringSafeArea(.all) 25 26 List() { 27 28 ForEach(ListVM.listData.indices, id: \.self) { index in 29 Section(header: Text(ListVM.listData[index][0].categoryname)) { 30 ForEach(ListVM.listData[index].indices, id: \.self) { index2 in 31 //Text(listData[index].count) 32 Text(ListVM.listData[index][index2].name) 33 34 } 35 } 36 } 37 } 38 } 39 } 40 } 41 42} 43
追記:Firestore記述を削除したコード全体
code全体(修正版)
1 2// ContentView.swift 3import SwiftUI 4struct ContentView: View { 5 let gearList = 6 GearList( 7 gearlistid: "gearlistid", 8 listname: "gearlistname", 9 lengthofstay: "2day", 10 howtostay: "Hotel", 11 startdate: Date(), 12 baseweight: 100, 13 consumableweight: 100, 14 wornweight: 100, 15 totalweight: 300 16 ) 17 var body: some View { 18 ListView(gearList: gearList) 19 } 20} 21 22#Preview { 23 ContentView() 24} 25 26 27// Item.swift 28import Foundation 29struct Item: Codable, Identifiable, Hashable { 30 var id: String? 31 var brandid: String 32 var brandname: String 33 var categoryid: Int 34 var categoryname: String 35 var createdAt: Date 36 var itemid: String 37 var memo: String 38 var name: String 39 var subcategoryid: Int 40 var subcategoryname: String 41 var updatedAt: Date 42} 43 44// GearList.swift 45import Foundation 46struct GearList: Codable, Identifiable, Hashable { 47 var id: String? 48 var gearlistid : String 49 var listname : String 50 var lengthofstay : String 51 var howtostay : String 52 var startdate : Date 53 var baseweight : Int 54 var consumableweight : Int 55 var wornweight : Int 56 var totalweight : Int 57} 58 59 60// ItemService.swift 61import Foundation 62struct ItemService { 63 func readItems(gearList:GearList, completion: @escaping([Item]) -> Void) { 64 let items: [Item] = [ 65 Item(brandid: "brandid", 66 brandname: "brandname", 67 categoryid: 11, 68 categoryname: "categoryname", 69 createdAt: Date(), 70 itemid: "itemid", 71 memo: "memo", 72 name: "name\(Int.random(in: 0..<100))", 73 subcategoryid: 12, 74 subcategoryname: "subcategoryname", 75 updatedAt: Date()), 76 Item(brandid: "brandid", 77 brandname: "brandname", 78 categoryid: 11, 79 categoryname: "categoryname", 80 createdAt: Date(), 81 itemid: "itemid", 82 memo: "memo", 83 name: "name\(Int.random(in: 0..<100))", 84 subcategoryid: 12, 85 subcategoryname: "subcategoryname", 86 updatedAt: Date()), 87 ] 88 completion(items) 89 } 90} 91 92// ListViewModel.swift 93import Foundation 94import Observation 95 96@Observable class ListViewModel { 97 private let itemservice = ItemService() 98 private var gearList : GearList 99 var listData: [[Item]] = [] 100 init() { 101 print("init") 102 self.gearList = GearList(gearlistid: "", listname: "", lengthofstay: "", howtostay: "", startdate: Date(), baseweight: 0, consumableweight: 0, wornweight: 0, totalweight: 0) 103 } 104 init(gearList:GearList) { 105 self.gearList = gearList 106 readAndSortItems(gearList:gearList) 107 } 108 func readAndSortItems(gearList:GearList) -> Void { 109 listData = [] 110 itemservice.readItems(gearList:gearList) { item in 111 print("completion") 112 self.listData.append(item) 113 } 114 } 115} 116 117 118// ListView.swift 119import SwiftUI 120import Observation 121struct ListView: View { 122 var gearList: GearList 123 @State private var listVM = ListViewModel() 124 125 init(gearList: GearList) { 126 self.gearList = gearList 127 self.listVM = ListViewModel(gearList: gearList) 128 } 129 130 var body: some View { 131 Button("tap", action: action) 132 List { 133 ForEach(listVM.listData.indices, id: \.self) { index in 134 Section(header:Text(listVM.listData[index][0].categoryname)) { 135 ForEach(listVM.listData[index].indices, id: \.self) { index2 in 136 Text(listVM.listData[index][index2].name) 137 } 138 } 139 } 140 } 141 } 142 func action() { 143 listVM.readAndSortItems(gearList: gearList) 144 } 145} 146 147
試したこと
- @Observation宣言の有無や、@Stateも試してみたもののうまくいかずでした
補足情報(FW/ツールのバージョンなど)
xcode 15.2
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2024/02/13 14:50
2024/02/15 14:33