

**質問内容**  
SwiftUIの勉強で、Firebaseにデータを保存し、再度するサンプルプログラムをつくっています。  
Firebaseで取り出したデータの扱い方について質問です。

FirebaseのHPのドキュメントを確認しながら、なんとかアプリ登録、保存、取得までできましたが、取得したデータはdocument単位？であり、document内にいくつかあるデータを選択的に取り出す方法が分かりません。  
どのようにすれば良いでしょうか？

わかりづらい説明で恐縮ですが、ソースコード内に、より詳しく状況を説明するためのコメント行を追記しています。  
（Firebaseの公式ドキュメントに書いてあるはずかとは思いますが、見よう見まねでやっと動いたという状況でして、まだ見つけられていません。  
よろしくお願いします。

＜追加の質問＞  
以下２箇所の記述は、言語化すると、どういった意味を持つコードなのでしょうか？  
きちんと理解したいので、教えていただけると大変助かります。  
よろしくお願いいたします。

１、docRef.getDocument { (document, error) in ... } という記述（A)  
２、document=document という記述（B)

**作成したコード**

[code]

    //Xcode version 12.4
    //Swift version 5.3.2
    //Cocoapods version 1.10.1 （pod 'Firebase/Firestore'と書いてpod install）
    
    import SwiftUI
    import Firebase
    
    struct ContentView: View {
    
        @State private var word1 = ""
        @State private var word2 = ""
    
        @State private var dataDescription = ""
    
        var body: some View {
    
            Text("Firestoreテスト")
                .padding()
    
            VStack {
                HStack {
                    Text("入力1")
                    TextField("入力したワードはFiresoreに保存", text: $word1)//あいうえお　と入力
                }
                HStack {
                    Text("入力2")
                    TextField("入力したワードはFiresoreに保存", text: $word2)//かきくけこ　と入力
                }
            }
    
            Button(action: {
                let db = Firestore.firestore()
                db.collection("collectionnameX").document("docmentnameX").setData([
                    "savedata1": word1,
                    "savedata2": word2
                ]) { err in
                    if let err = err {
                        print("Error writing document: \(err)")
                    } else {
                        print("Document successfully written!")
                    }
                }
            }){
                Text("Firesoreに保存")
                    .border(Color.green, width: 1)
            }
            Button(action: {
                let db = Firestore.firestore()
                let docRef = db.collection("collectionnameX").document("docmentnameX")
                docRef.getDocument { (document, error) in//...(A)
                    if let document = document, document.exists {//...(B)
                        dataDescription = document.data().map(String.init(describing:)) ?? "nil"
                        print("Document data: \(dataDescription)")
                    } else {
                        print("Document does not exist")
                    }
                }
            }){
                Text("Firesoreから取得")
                    .border(Color.green, width: 1)
            }
            Text("取得したデータを表示")
            //以下、質問の詳細です。
            //以下 Text("\(dataDescription)")では、
            //"Document data: ["savedata1": あいうえお, "savedata2": かきくけこ]"と表示されます。
            //このうち、savedata1の値「あいうえお」を取り出すにはどのようにすれば良いでしょうか？
            //dataDescription.savedata1や、dataDescription["savedata1"]のような書き方を試しましたが、うまくいきません。
            Text("\(dataDescription)")
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
[/code]


CC BY-SA 4.0
2021-05-20T02:03:47.983
75961
2021-05-20T13:12:15.790
2021-05-20T05:40:29.143
45415
45415
question
swift firebase swiftui
SwiftUIでFirestoreから取得したデータの扱いについて


あなたは、`dataDescription`を`String`型で宣言しているんで、その状態から一部の要素を取り出すには、文字列操作が必要になります。

FireStoreの`data()`の結果は、SwiftではOptionalの辞書型(`[String:
Any]?`)になります。`map(String.init(describing:))`なんてことはやらずに、辞書型の状態から値を取り出してください。

[code]

                let db = Firestore.firestore()
                let docRef = db.collection("collectionnameX").document("docmentnameX")
                docRef.getDocument { (document, error) in
                    if let document = document, document.exists {
                        if let docDict = document.data(),
                           let savedata1 = docDict["savedata1"] as? String,
                           let savedata2 = docDict["savedata2"] as? String {
                            dataDescription = "savedata1->\(savedata1), savedata2->\(savedata2)"
                        } else {
                            dataDescription = "cannot retrieve data"
                        }
                        print("Document data: \(dataDescription)")
                    } else {
                        print("Document does not exist")
                    }
                }
    
[/code]

あるいは、`String`型ではなく適切なstructを定義して、その型の`@State`変数に結果を渡した方が、使い回しやすいですし、コード全体もスッキリするでしょう。  
ただ、`FirebaseFirestoreSwift`を使うと、`Codable`なstructへの変換まで一括でやってくれるメソッドが使えるようになるので、その方が簡単になるかもしれません。(まだベータ版の扱いでしたが。)

* * *

＜追加の質問＞について。

このような言語機構そのものの疑問は、「きちんとした言語そのものの教科書」を手に入れて解決することをお勧めしておきます。

いわゆる「Swift本」と呼ばれる次の書籍は、英語しか無い上に色々な他の言語の知識があること前提に書かれている部分があるのですが、Swiftでのプログラミングを行なっていくのであれば必読と言って良いでしょう。

[The Swift Programming Language](https://docs.swift.org/swift-book/)

(Web版の他、iBooks版も無料で入手できます。)

あいにく日本語の良い教科書は知らないのですが、書評などをみて、良いもの(大抵良いものは分厚いです)を選ばれると良いでしょう。日本語で良いサイト…は一つも見たことがありません。(部分的にならよく書けているサイトもありましたが。)

# １

> docRef.getDocument { (document, error) in ... } という記述（A)
[code]

        docRef.getDocument(completion: { (document, error) in ... })
    
[/code]

と言う、関数呼び出しの「末尾クロージャ(Trailing Closure)」と呼ばれる省略表記です。`{ (document, error) in in
...
}`の部分がクロージャ式ですが、それを`completion:`という引数に渡した上で`getDocument`と言う関数を呼び出している形になります。

# ２

> document=document という記述（B)

これは、前の`let`、さらにその前の`if`も合わせて解釈してやらないといけません。真ん中の`=`記号は等価や代入の意味はなく単に区切り記号だと思ってください。if-
letの他、guard-letや(頻度は少ないですが)while-letなんて使われ方もします。(Optional
Bindingと呼ばれていますが、良い訳語を知りません。)

これは`=`の右辺の`document`がOptional型の時、値をnilか非nilかチェックした上で「非nilの場合のみ左辺の`let
document`と言う変数を定義した上で条件成立とする」と言う、条件判定と変数宣言を同時に行うものです。この変数はif文他の条件成立部の中だけで有効です。  
(一部の言語では、外側の変数と同名の変数の再定義は禁止してたりしますが、SwiftではOptional Bindingの場合にはよく見られます。)

「Optional型」とそれを安全に扱う機構については、Swiftの特徴の筆頭にあげられるものでもっとも他の言語に影響を与えたと言える部分でもあります。ネット上の記事も豊富ですが、良いものは一握りなので、出来るだけ良い記事を探し出して勉強してみてください。


CC BY-SA 4.0
2021-05-20T02:47:53.737
75962
2021-05-20T13:12:15.790
2021-05-20T13:12:15.790
13972
13972
75961
answer