62195


Xcode11.3  
Swift5  
macOSMojave10.14.6

Facebookのコメント投稿画面のような、タイムライン画面（TableView）から記事（セル）をタップし、遷移先画面でコメントを投稿できる機能を実装したいのですが、セルをタップした際に遷移先へそのセル番号(indexPath.row)を送る方法と、遷移先でセル番号を検知してFirebaseへ保存する方法がわかりません。ご教授願います。

遷移元はdidSelectRowAt内で遷移先へセル番号を送るのだと思いますが、以下で合っているのかどうかがわかりません。

[code]

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            // タップされたセルの行番号を出力
            print("\(indexPath.row)番目の行が選択されました。")
    
            let postData = postArray[indexPath.row]
    
            //記事画面へ遷移（セグエ）
              let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "Article") as! ArticleViewController
            //completionでSetPostDataに渡す
                self.present(nextVC, animated: true, completion: {
                    nextVC.SetPostData(postData)
                })
            performSegue(withIdentifier: "Article", sender: postArray[indexPath.row])
            //セルの選択を解除
            tableView.deselectRow(at: indexPath, animated: true)
    
        }
    
    
[/code]

遷移先ではTextFieldにコメントを入力し、UIButtonを押すとUILabelに反映されるようにしたいのですが、セル番号を受信する方法がわかりません。

[code]

    @IBAction func CommentButton(_ sender: UIButton) {
    
            // 配列からタップされたインデックスのデータを取り出す
            let postData = HomeViewController.postArray[indexPath!.row]
    
            //現在の使用者の表示名がnilじゃなかったら、displayNameに代入し、
            if let displayName = Auth.auth().currentUser?.displayName,
            //cell.textField.textがnilじゃなかったら、commentTextとする
            let commentText = self.CommentTextField.text  {
    
                //であれば、cell(PostTableViewCell)のtextFieldをプリントする
                print(self.CommentTextField.text as Any)
    
                //であれば、postData.commentsにcommentTextをappend(追加)する
                postData.comments.append("\(displayName): \(commentText)\n")
    
            }
    
            // 増えたcommentsをFirebaseに保存する
            let postRef = Database.database().reference().child(Const.PostPath).child(postData.id!)
            //comments辞書
            let commentDictionary = ["comments": postData.comments]
            //Firebaseに辞書を保存する
            postRef.updateChildValues(commentDictionary)       
        }
    
[/code]

追記  
遷移元HomeViewContoroller

[code]

    import UIKit
    import Firebase
    
    class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
        @IBOutlet weak var tableView: UITableView!
    
        var postArray: [PostData] = []
    
        // DatabaseのobserveEventの登録状態を表す
        var observing = false
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.delegate = self
            tableView.dataSource = self
    
            let nib = UINib(nibName: "PostTableViewCell", bundle: nil)
            tableView.register(nib, forCellReuseIdentifier: "Cell")
    
            // テーブル行の高さをAutoLayoutで自動調整する
            tableView.rowHeight = UITableView.automaticDimension
            // テーブル行の高さの概算値を設定しておく
            // 高さ概算値 = 「縦横比1:1のUIImageViewの高さ(=画面幅)」+「いいねボタン、キャプションラベル、その他余白の高さの合計概算(=100pt)」
            tableView.estimatedRowHeight = UIScreen.main.bounds.width + 100
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("DEBUG_PRINT: viewWillAppear")
    
            if Auth.auth().currentUser != nil {
                if self.observing == false {
                    // 要素が追加されたらpostArrayに追加してTableViewを再表示する
                    let postsRef = Database.database().reference().child(Const.PostPath)
                    postsRef.observe(.childAdded, with: { snapshot in
                        print("DEBUG_PRINT: .childAddedイベントが発生しました。")
    
                        // PostDataクラスを生成して受け取ったデータを設定する
                        if let uid = Auth.auth().currentUser?.uid {
                            let postData = PostData(snapshot: snapshot, myId: uid)
                            self.postArray.insert(postData, at: 0)
    
                            // TableViewを再表示する
                            self.tableView.reloadData()
                        }
                    })
                    // 要素が変更されたら該当のデータをpostArrayから一度削除した後に新しいデータを追加してTableViewを再表示する
                    postsRef.observe(.childChanged, with: { snapshot in
                        print("DEBUG_PRINT: .childChangedイベントが発生しました。")
    
                        if let uid = Auth.auth().currentUser?.uid {
                            // PostDataクラスを生成して受け取ったデータを設定する
                            let postData = PostData(snapshot: snapshot, myId: uid)
    
                            // 保持している配列からidが同じものを探す
                            var index: Int = 0
                            for post in self.postArray {
                                if post.id == postData.id {
                                    index = self.postArray.firstIndex(of: post)!
                                    break
                                }
                            }
    
                            // 差し替えるため一度削除する
                            self.postArray.remove(at: index)
    
                            // 削除したところに更新済みのデータを追加する
                            self.postArray.insert(postData, at: index)
    
                            // TableViewを再表示する
                            self.tableView.reloadData()
                        }
                    })
    
                    // DatabaseのobserveEventが上記コードにより登録されたため
                    // trueとする
                    observing = true
                }
            } else {
                if observing == true {
                    // ログアウトを検出したら、一旦テーブルをクリアしてオブザーバーを削除する。
                    // テーブルをクリアする
                    postArray = []
                    tableView.reloadData()
                    // オブザーバーを削除する
                    let postsRef = Database.database().reference().child(Const.PostPath)
                    postsRef.removeAllObservers()
    
                    // DatabaseのobserveEventが上記コードにより解除されたため
                    // falseとする
                    observing = false
                }
            }
        }
        //セルの数を決めるメソッド
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return postArray.count
        }
        //セルを構築する際に呼ばれるメソッド
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            // セルを取得してデータを設定する
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PostTableViewCell
            cell.setPostData(postArray[indexPath.row])
    
            // セル内のボタンのアクションをソースコードで設定する
            cell.likeButton.addTarget(self, action:#selector(handleButton(_:forEvent:)), for: .touchUpInside)
    
            return cell
        }
    
        //セルをタップしたら...のメソッド
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            // タップされたセルの行番号を出力
            print("\(indexPath.row)番目の行が選択されました。")
    
            let postData = postArray[indexPath.row]
    
            //記事画面へ遷移（セグエ）
              let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "Article") as! ArticleViewController
            //completionでSetPostDataに渡す
                self.present(nextVC, animated: true, completion: {
                    nextVC.SetPostData(postData)
                })
            performSegue(withIdentifier: "Article", sender: postArray[indexPath.row])
            //セルの選択を解除
            tableView.deselectRow(at: indexPath, animated: true)
    
        }
    
        // セル内のボタンがタップされた時に呼ばれるメソッド
        @objc func handleButton(_ sender: UIButton, forEvent event: UIEvent) {
            print("DEBUG_PRINT: likeボタンがタップされました。")
    
            // タップされたセルのインデックスを求める
            let touch = event.allTouches?.first
            let point = touch!.location(in: self.tableView)
            let indexPath = tableView.indexPathForRow(at: point)
    
            // 配列からタップされたインデックスのデータを取り出す
            let postData = postArray[indexPath!.row]
    
            // Firebaseに保存するデータの準備
            if let uid = Auth.auth().currentUser?.uid {
                if postData.isLiked {
                    // すでにいいねをしていた場合はいいねを解除するためIDを取り除く
                    var index = -1
                    for likeId in postData.likes {
                        if likeId == uid {
                            // 削除するためにインデックスを保持しておく
                            index = postData.likes.firstIndex(of: likeId)!
                            break
                        }
                    }
                    postData.likes.remove(at: index)
                } else {
                    postData.likes.append(uid)
                }
    
                // 増えたlikesをFirebaseに保存する
                let postRef = Database.database().reference().child(Const.PostPath).child(postData.id!)
                let likes = ["likes": postData.likes]
                postRef.updateChildValues(likes)
    
            }
        }
    
    }
    
    
[/code]

遷移先ArticleViewController

[code]

    import UIKit
    import Firebase
    import SVProgressHUD
    
    class ArticleViewController: UIViewController,UITextFieldDelegate {
    
        @IBOutlet weak var ArticleImage: UIImageView!
        @IBOutlet weak var ArticleLabel: UILabel!
        @IBOutlet weak var ArticleButton: UIButton!
        @IBOutlet weak var ArticleLilkeCount: UILabel!
        @IBOutlet weak var CommentLabel: UILabel!
        @IBOutlet weak var CommentTextField: UITextField!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.CommentTextField.delegate = self
            CommentTextField.returnKeyType = .done
        }
    
        func SetPostData(_ postData: PostData) {
            //イメージ画像
            self.ArticleImage.image = postData.image
            //キャプションのテキスト
            self.ArticleLabel.text = "\(postData.name!) : \(postData.caption!)"
            //いいねの数
            let likeNumber = postData.likes.count
            ArticleLilkeCount.text = "\(likeNumber)"
    
            //いいねボタン
            if postData.isLiked {
                let buttonImage = UIImage(named: "like_exist")
                self.ArticleButton.setImage(buttonImage, for: .normal)
            } else {
                let buttonImage = UIImage(named: "like_none")
                self.ArticleButton.setImage(buttonImage, for: .normal)
            }
            //allCommentは最初は空である
            var allComment = ""
    
            //postData.commentsの中から要素をひとつずつ取り出すのを繰り返す、というのがcomment
            for comment in postData.comments{
            //comment + comment = allCommentである
            allComment += comment
            //commentLabelに表示するのはallComment（commentを足していったもの）である
            self.CommentLabel.text = allComment
            }
    
        }
    
        //キーボードを閉じる
        @IBAction func textField(_ sender: Any) {
            CommentTextField.text = (sender as AnyObject).text
        }
    
        //コメント投稿ボタン
        @IBAction func CommentButton(_ sender: UIButton) {
    
            // 配列からタップされたインデックスのデータを取り出す
            let postData = HomeViewController.postArray[indexPath!.row]
    
            //現在の使用者の表示名がnilじゃなかったら、displayNameに代入し、
            if let displayName = Auth.auth().currentUser?.displayName,
            //cell.textField.textがnilじゃなかったら、commentTextとする
            let commentText = self.CommentTextField.text  {
    
                //であれば、cell(PostTableViewCell)のtextFieldをプリントする
                print(self.CommentTextField.text as Any)
    
                //であれば、postData.commentsにcommentTextをappend(追加)する
                postData.comments.append("\(displayName): \(commentText)\n")
    
            }
    
            // 増えたcommentsをFirebaseに保存する
            let postRef = Database.database().reference().child(Const.PostPath).child(postData.id!)
            //comments辞書
            let commentDictionary = ["comments": postData.comments]
            //Firebaseに辞書を保存する
            postRef.updateChildValues(commentDictionary)     
        }
    }
    
[/code]


CC BY-SA 4.0
2020-01-12T14:57:25.817
62191
2020-01-13T02:57:42.493
2020-01-13T02:57:42.493
37426
37426
question
swift firebase swift5
firebaseを用いたSNSアプリでコメント投稿の機能を実装したい


この回答は、ご質問内の **_以下で合っているのかどうかがわかりません_** および、 ** _セル番号を受信する方法がわかりません_**
と言う2つの疑問点についてのものです。

その他の部分(例えばFirebaseにデータを保存する方法やら、強制アンラップ(`!`)の使い方が適切かどうか)等については触れていませんので、ご注意ください。

また、view controller間のデータの受け渡しでは極めて重要となる情報 **_遷移はセグエです_** と言うのに全面的に依拠しています。

* * *

### データを送る側

  * 「遷移はセグエ」であれば、`prepare(for:sender:)`を実装して、その中でデータを渡します。
  * 「遷移はセグエ」なのに、`present(_:animated:completion:)`を呼んではいけません
  * `present`を使う場合でも、(普通)`completion`の中でデータの受け渡しを行うことはしません

と言う訳で、`HomeViewController`の`tableView(_:didSelectRowAt:)`の辺りは例えば、次のようになるでしょう。

[code]

    class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
        //...
    
    
        //セルをタップしたら...のメソッド
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            // タップされたセルの行番号を出力
            print("\(indexPath.row)番目の行が選択されました。")
    
            let postData = postArray[indexPath.row]
    
            //記事画面へ遷移（セグエ）→セグエで遷移するならインスタンスを作成したり、`present`を呼んだりしない
    //          let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "Article") as! ArticleViewController
    //        //completionでSetPostDataに渡す
    //            self.present(nextVC, animated: true, completion: {
    //                nextVC.SetPostData(postData)
    //            })
            postDataToSend = postData
            performSegue(withIdentifier: "Article", sender: tableView)
            //セルの選択を解除
            tableView.deselectRow(at: indexPath, animated: true)
    
        }
    
        var postDataToSend: PostData?
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "Article" {
                let nextVC = segue.destination as! ArticleViewController
                if let postData = postDataToSend {
                    nextVC.setPostData(postData)
                }
            }
        }
    
        //...
    
    }
    
[/code]

* * *

### データを受け取る側

  * データを受け取るための変数を用意します
  * その変数に設定されたデータは`viewWillAppear(_:)`の中などで画面等に反映します

これを反映すると、以下のようなコードになるでしょう。

[code]

    class ArticleViewController: UIViewController, UITextFieldDelegate {
    
        //...
    
        //前画面からデータを受け取るための変数
        var postDataReceived: PostData?
    
        func setPostData(_ postData: PostData) {
            postDataReceived = postData
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            guard let postData = postDataReceived else {
                return
            }
            //イメージ画像
            self.articleImage.image = postData.image
            //キャプションのテキスト
            self.articleLabel.text = "\(postData.name!) : \(postData.caption!)"
            //いいねの数
            let likeNumber = postData.likes.count
            articleLilkeCount.text = "\(likeNumber)"
    
            //いいねボタン
            if postData.isLiked {
                let buttonImage = UIImage(named: "like_exist")
                self.articleButton.setImage(buttonImage, for: .normal)
            } else {
                let buttonImage = UIImage(named: "like_none")
                self.articleButton.setImage(buttonImage, for: .normal)
            }
            //allCommentは最初は空である
            var allComment = ""
    
            //postData.commentsの中から要素をひとつずつ取り出すのを繰り返す、というのがcomment
            for comment in postData.comments{
                //comment + comment = allCommentである
                allComment += comment
                //commentLabelに表示するのはallComment（commentを足していったもの）である
                self.commentLabel.text = allComment
            }
    
        }
    
        //...
    
    
        //コメント投稿ボタン
        @IBAction func commentButton(_ sender: UIButton) {
    
            // 前画面から受け取ったデータを取り出す
            guard let postData = postDataReceived else {
                return
            }
    
            //...
    
        }
    
        //...
    
    }
    
[/code]

  * Swiftでは大文字スタートの識別子は型名だけに使う、と言うルールが大変よく守られています。あなたの現在のコードは非常に読みにくいので、一部書き換えてあります。
  * ネット上の記事によっては「データを受け取る側」の処理を`viewDidLoad()`で行う、と紹介しているものがありますが、ちょっとした不注意で正しく動かなくなるので、`viewWillAppear(_:)`を使用しています。
  * ご質問内では「セル番号を送る/受信する」とされていますが、`postArray`はインスタンスプロパティなので`HomeViewController.postArray`なんて形で簡単に「データを取り出す」ことができません。よって、この回答では「セル番号」(インデックス)ではなく、データそのものを渡すようにしてあります。

* * *

データの受け渡しに話を絞っているので、何かしらうまくいかない点が出てくるかもしれません。その場合にはコメント等でお知らせください。


CC BY-SA 4.0
2020-01-12T17:29:06.413
62195
2020-01-12T22:34:44.590
2020-01-12T22:34:44.590
13972
13972
62191
answer