こんにちは。お世話になります。
前提
SwfitとCloud Firestoreを使って、ユーザーがお気に入りのレストランを登録し、それをGoogle Map上でシェアするアプリを開発しています。
Map上のマーカーのウィンドウをタップすると画面遷移し、レストランのレビューを見ることができるようにしています。レビュー画面では投稿したユーザーが誰かも分かるようにしています。
またCloud Firestoreにはルートコレクションが2つあります。
Storesコレクションのドキュメントを全て取得し、それをfor文で回しています。各ドキュメント内のuserIdの値を取得した後に、それと一致するUserコレクションのドキュメントを参照して、そのドキュメント内にあるuserNameとuserImageを取り出して、レビュー画面に投稿したユーザー名とアイコンを反映しています。
実現したいこと・起きている問題
レビュー画面に値を反映することはできているのですが、投稿したユーザーとは関係ないユーザーの値が反映されてしまうのでそれを解決したいです。
考えられる原因
アプリビルド時のログを見ると、下記のコードで示している箇所まではfor文で実行した通りに処理が走っているのですが、Firebaseとの通信(非同期処理?)後のクロージャがfor文とは関係ない順番で処理が実行されているためレストランを投稿したユーザーと関係無いユーザーの値がレビュー画面に反映させてしまっているのだと考えています。
下のシュミレータ画面とcloud firestoreの画像をみて頂ければ分かるかと思いますが、はっぱを投稿したユーザーは値が反映されているユーザーではありません。
シュミレータ画面
アプリビルド時のログ画面
Cloud Firestoreの構造
該当のソースコード
Firebaseと通信するモデルクラス↓
Swift
1import Firebase 2 3class GetStoreInfo { 4 5 // MARK: - Properties 6 let db = Firestore.firestore() 7 8 // MARK: - Methods 9 func getStoreInfo(completion: @escaping (_ snapShot: QuerySnapshot) -> Void) { 10 let collRef = db.collection("Stores") 11 collRef.getDocuments { [weak self] (snapShot, error) in 12 if let _ = self { 13 14 if error != nil { 15 print("could not get store data..") 16 } else { 17 guard let unwrappedSnapShot = snapShot else { return } 18 completion(unwrappedSnapShot) 19 } 20 21 } 22 23 } 24 } 25 26 func getPostUserInfo(userId: String, storeName: String, storeReview: String ,completion: @escaping (_ snapShot: DocumentSnapshot) -> Void) { 27 print("getPostUserInfoメソッドだよ") 28 print(userId) 29 let docRef = db.collection("Users").document(userId) 30 // ここまではfor文で順番通りに動く⬆️ 31 32 // ここからfor文での順番通りに動かなくなり順番が狂う⬇️ 33 docRef.getDocument { [weak self] (snapShot, error) in 34 if let _ = self { 35 36 print("クロージャに入ったよ") 37 guard let unwrappedSnapShot = snapShot else { return } 38 print("アンラップしたよ") 39 completion(unwrappedSnapShot) 40 41 } 42 } 43 } 44}
ViewController↓
Swift
1import UIKit 2import GoogleMaps 3 4class MapViewController: UIViewController { 5 6 // MARK: - Properties 7 var mapView: MapView! 8 var delegate: MapViewControllerDelegate? 9 var storeModel: GetStoreInfo! 10 var storeDetailViewControllers: [StoreDetailViewController] = [] 11 var storeName: [String] = [] 12 var storeReview: [String] = [] 13 var storeImage: [String] = [] 14 var count = 0 15 var storeCount = 0 16 17 // MARK: - Helper Functions 18 override func viewDidLoad() { 19 super.viewDidLoad() 20 21 storeDetailViewControllers.removeAll() 22 storeName.removeAll() 23 storeReview.removeAll() 24 storeName.removeAll() 25 postUserNameArray.removeAll() 26 postUserIconArray.removeAll() 27 getStoresData() 28 } 29 30 func getStoresData() { 31 32 print("start get stores data..") 33 storeModel = GetStoreInfo() 34 storeModel.getStoreInfo { [weak self] (snapShot) in 35 if let self = self { 36 // for文でドキュメントを順番に取得 37 for document in snapShot.documents { 38 let storeData = document.data() 39 guard let latitude = storeData["latitude"] as? Double, let longitude = storeData["longitude"] as? Double, let storeName = storeData["storeName"] as? String, let storeImage = storeData["storeImage"] as? String, let storeReview = storeData["storeImpression"] as? String, let userId = storeData["userId"] as? String else { return } 40 41 42 self.configureMakerInMap(latitude: latitude, longitude: longitude, storeName: storeName, count: self.count, storeReview: storeReview, storeImage: storeImage, userId: userId) 43 self.count += 1 44 } 45 } 46 47 48 } 49 50 } 51 52 func configureMakerInMap(latitude: Double, longitude: Double, storeName: String, count: Int, storeReview: String, storeImage: String, userId: String) { 53 54 print("configure maker..") 55 let position = CLLocationCoordinate2DMake(latitude, longitude) 56 let marker = GMSMarker(position: position) 57 marker.title = storeName 58 // markerの識別子 59 marker.identifier = count 60 marker.map = mapView.mapView 61 // for文で順番に取得した値を配列に入れる 62 self.storeName.append(storeName) 63 self.storeReview.append(storeReview) 64 self.storeImage.append(storeImage) 65 print(userId) 66 self.storeModel.getPostUserInfo(userId: userId, storeName: storeName, storeReview: storeReview) { [weak self] (snapShot) in 67 if let self = self { 68 // クロージャから出てきたこの場所で順番がおかしくなる 69 let userInfo = snapShot.data() 70 print("0") 71 print("出てきたよ") 72 73 guard let postUserName = userInfo!["userName"] as? String, let postUserIcon = userInfo!["userImage"] as? String else { return } 74 print(postUserName) 75 print(postUserIcon) 76 77 // 取得したレストランの情報とユーザーの情報を、引数で渡してレビュー画面を作成 78 self.configureViewController(storeName: self.storeName[self.storeCount], storeReview: self.storeReview[self.storeCount], storeImage: self.storeImage[self.storeCount], count: self.count, postUserName: postUserName, postUserIcon: postUserIcon) 79 self.storeCount += 1 80 } 81 } 82 83 84 } 85 86 func configureViewController(storeName: String, storeReview: String, storeImage: String, count: Int, postUserName: String, postUserIcon: String) { 87 88 storeDetailViewControllers.append(StoreDetailViewController(storeName: storeName, storeReview: storeReview, storeImage: storeImage, postUserName: postUserName, postUserIcon: postUserIcon,count: count)) 89 90 } 91 92} 93 94// Markerに識別子を持たせるための変数 95extension GMSMarker { 96 var identifier: Int { 97 set(identifier) { 98 self.userData = identifier 99 } 100 101 get { 102 return self.userData as! Int 103 } 104 } 105} 106 107// MARK: Delegate 108extension MapViewController: GMSMapViewDelegate { 109 110 func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) { 111 print(marker.identifier) 112 let navStoreDetailViewController = UINavigationController(rootViewController: storeDetailViewControllers[marker.identifier]) 113 navStoreDetailViewController.modalPresentationStyle = .fullScreen 114 present(navStoreDetailViewController, animated: true, completion: nil) 115 print("did tap info window of maker..") 116 } 117 118} 119
試したこと
クロージャについて調べていたら、weak self
というのが出てきたのでコードを書いてみたのですが恐らく関係ありませんでした。
Firebaseとの非同期通信まで終わった後に、次のfor文内の処理が動くようにすれば解決できると思うのですが、調べても出てきませんでした。(そもそもそんなやり方が無いのかもしれませんが)
また、Firebase登録されているレストランを一つだけにしてみたらfor文は一度しか動かないので、投稿したユーザーは一致しました。逆にFirebase登録されているレストランを増やすほど、投稿したユーザーとは関係ないユーザーがばらばらに表示されてしまいます。
説明が分かりにくかったらすみません。
解決方法が分かる方がいましたら、ご回答お願いします。
また別の方法でやりたいことが実装できるのであれば、その方法を教えて頂きたいです。
追記
調べていたらforEach文というのが出てきたので、それを試してみましたがダメでした。
参考にしたサイト
補足情報(FW/ツールのバージョン)
xcode 11.3.1
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/03/09 12:48
2020/03/10 14:30