【SwiftUI】Firebaseからデータを読み取り、ListViewのRowを更新する

# はじめに
SwiftUIでFirebaseからデータを読み取り、これまで作成したカード内容を更新することを目的とする。

前回までの記事は以下を参考ください。

**参考記事**
[【SwiftUi】TabViewとListの実装とViewのフォルダ管理](https://qiita.com/m37335/items/00e08d9c34b17a3a38b7)

# 開発環境
OSX 10.15.7 (Catalina) 
Xcode 12.2.0
CocoaPods 1.10.0

# Firestoreへのドキュメントの追加
FirebaseのFirestoreデータベースは以下のように設定しました。
今回は書き込みの機能は設定していないため、直接入力しています。
今後はユーザーが投稿できるよう機能を実装します。
![Haiku_-_Cloud_Firestore_-_Firebase_コンソール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/b99df523-fe93-1b82-91b3-4c24ee7a0fe9.png)

mapImageとuserImageはFireBaseのStorageに保存してある画像のURLを入力しています。
それぞれのファイルをStorageにアップロードします。
必要なURLは"ファイルの場所"にある"アクセストークン"をクリックし、内容をコピーし、Firestoreのそれぞれのフィールドに貼り付けます。
![Haiku_-_Storage_-_Firebase_コンソール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/135e5215-3df1-3013-0ad0-b94f0acecf11.png)

NoSQLの知識が乏しいため、データの構造化がうまくありません。この点は今後学習とともに、現在のデータ構造と変更点が出てくると思います。
現状はFirebaseの使用方法と、SwiftUIの実装方法に焦点を当てます。

**参考資料**
[データベースの構造化](https://firebase.google.com/docs/database/ios/structure-data?hl=ja)
[脱RDB脳！Firebase Databse導入のために考えた4つのポイント](https://qiita.com/gonta616/items/4078b43e63a47c6e1a33)



# Firestoreからのデータの読み取り
## ```datatype```structの実装
Firebaseのデータベースと読み取りと書き出しを行うため、```struct```を実装します。
ここで実装した```Struct```は```getData```クラスにおいて呼び出されます。
記載内容については読み取り時に、投稿日時を元に並び替えを行う場合は```createdDate```のみで大丈夫です。
今回は過去の俳人の俳句を投稿しているため、並び替えのために```now```を定義しました。


```swift
struct dataType : Identifiable {

    var id : String
    var user : String
    var haiku : String
    var place : String
    var likes : String
    var mapImage : String
    var userImage : String
    var stamp : Date
    var createdDate : String
    var now :Date
}

```

##```getData```クラスの実装
Firebaseデータベースからドキュメントを読み取るための部分です。

特にエラーが出て、実装に時間がかかった部分はTimestampのデータの取り扱いです。

```terminal
Timestamp firebase Cannot convert value of type 'Timestamp' to expected argument type 'String'
```

Timestampのデータの変換時にエラーが出てしまい、適切にデータベースからの読み取りができませんでした。
実装にあたっては以下のサイトを参考にしました。

**参考資料**
[Convert timestamp from Firebase to readable date](https://developer.apple.com/forums/thread/110924)
[Firebase FirestoreのTimestamp型のdateへの変換](https://qiita.com/kboy/items/4d1e7d181af122a78fef)


```swift
class getData: ObservableObject {
    
    @Published var datas = [dataType]()
    
    init() {
        
        let db = Firestore.firestore()
        
        db.collection("ikku").order(by: "now", descending: true).addSnapshotListener{ (snap, err) in
            
            if err != nil{
                
                print((err?.localizedDescription)!)
                return
            }
            
            for i in snap!.documentChanges{
                
                if i.type == .added{
                    
                    let id = i.document.documentID
                    let user = i.document.get("userName") as! String
                    let haiku = i.document.get("haiku") as! String
                    let place = i.document.get("place") as! String
                    let likes = i.document.get("likes") as! String
                    let mapImage = i.document.get("mapImage") as! String
                    let userImage = i.document.get("userImage") as! String
                    
                    let now = i.document.get("now") as! Timestamp
                    let stamp = i.document.get("createdDate") as! Timestamp
                    
                    let formatterDate = DateFormatter()
                    formatterDate.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                    let createdDate = formatterDate.string(from: stamp.dateValue())
                    
                    DispatchQueue.main.async {
                        self.datas.append(dataType(id: id, user: user, haiku: haiku, place: place, likes: likes, mapImage: mapImage, userImage: userImage, stamp: stamp.dateValue(), createdDate: createdDate, now: now.dateValue() ))
                        
                    }
                }
            }
        }
    }
}

```

##ContentRowViewへの実装とデータベースからの情報更新
Firebaseからデータを読み取りにあたって、以下の点を変更しました。

変更前の実装については以下を参考ください。
[【SwiftUi】経過時間の表示とListViewのセルデザイン](https://qiita.com/m37335/items/93c9bcbede1c8ac64bb6)

**変更点**
**1.ContentRowViewにおけるSDWebImageSwiftUIのインポートとvarで定義した変数を空にする。**

```swift:ContentRowView.swift
import SwiftUI
import Firebase

import SDWebImageSwiftUI

struct ContentRowView: View {
    
    var user = ""
    var userImage = ""
    var haiku = ""
    var mapImage = ""
    var place = ""
    var likes = ""
    // ○秒、○日、○年前を表示
    var createdDate = ""

    static let formatter = RelativeDateTimeFormatter()
    
    var body: some View {
        
        ContentRowView.formatter.locale = Locale(identifier: "ja_JP")
        
        let fmt = ISO8601DateFormatter()
        let date1 = fmt.date(from: createdDate)!
        let components = Calendar.current.dateComponents(
            [.day, .year, .month, .minute, .second],
            from: Date(),
            to: date1
        )
        let timeAgo = ContentRowView.formatter.localizedString(from: components)
        
        return VStack {
            VStack {
                AnimatedImage(url: URL(string: mapImage)!)
                    .resizable()
                    //.aspectRatio(contentMode: .fit)
                    .cornerRadius(12.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
                HStack {
                    Spacer()
                    Text(place).font(.caption).foregroundColor(.gray)
                        .padding(.bottom, 5)
                }
            }
            Text(haiku).font(.title3).fontWeight(.bold)
            
            HStack {
                Image(systemName: "heart").font(.headline).foregroundColor(Color("pinkColor"))
                Text(likes).font(.headline).foregroundColor(Color("pinkColor"))
                Spacer()
            }.padding(.top, 5)

            
            HStack {
                AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())
                
                HStack {
                    Text(user).font(.headline).fontWeight(.light)
                    + Text("・").font(.headline).fontWeight(.light)
                    + Text(timeAgo).font(.headline).fontWeight(.light)
                }
                Spacer()
                HStack {
                    
                    Image(systemName: "text.bubble").font(.title).foregroundColor(.gray)
                    Image(systemName: "heart.fill").font(.title).foregroundColor(Color("pinkColor"))
                }
            }
            Spacer()
        }.padding(.top, 8).frame(height: 391)
        
    }
}
```

**2.SDWebImageSwiftUIのインポート**
→URLを画像に変換するためのフレームワークを追加

```swift
AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())
```

**3.HomeViewにおけるObservedObjectの実装**
→Firebaseのデータベースからドキュメントを受け取り、ListのそれぞれのRowの情報を更新する。

```swift:home.swift
struct Home: View {
    
    @ObservedObject var observedData = getData()
    
    var body: some View {
        
        List(observedData.datas) { i in
            ContentRowView(user: i.user, userImage: i.userImage, haiku: i.haiku, mapImage: i.mapImage, place: i.place, likes: i.likes, createdDate: i.createdDate)
        }.environment(\.defaultMinListRowHeight, 391)
    }
}
```

![iPhone_11_–_14_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/867981/ac34d536-e758-73f4-d2fb-b920f9170eea.png)




# 今後実装予定のもの
1.カード部分のLike機能とComment機能の実装
2.mapTabやその他のTab機能の実装
3.Post機能の実装
4.ユーザー登録機能の実装
5.俳人の俳句の登録

以上です。