インスタ報告

#概要
この章では、SNSアプリの報告機能を実装していきます。
初期状態としては、**NCMBを利用してタイムラインで投稿と読み込みができている状態**から書いていく想定でこの記事は書いています。
長い記事なので、頑張ってついてきてください！！☺️

ここで報告機能とはどんなものかについてちょこっとお話しします！「報告機能とはモラルに反する投稿を管理者(=アプリ開の開発者)に報告して消してもらえるようにする機能です。インスタにもTwitterにもありますよね！」

今回は、サンプルとしてNCMBを用いて取得したユーザー情報を通報します。
NCMBのデータストアに通報クラスを作成し、通報したい投稿のobjectId、通報した人の情報をアップロードし開発者が閲覧できるようにします。
前提として、ViewControllerのタイムラインはTableViewのカスタムセルを利用して設計しており、デリゲートメソッドの一つとして通報機能を実装しています。通報する際のアクションは「alertController」の一つの機能として通報機能を実装します。


#完成予想図
**(例)**


![report.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/6813e163-f9ba-dccb-a979-b1b96df639a4.gif)


#目次

&emsp;&emsp;[1.&emsp;完成予想図](#完成予想図)
&emsp;&emsp;[2.&emsp;~~の理論の話](#~~の理論の話)
&emsp;&emsp;[3.&emsp;作り方の説明](#作り方の説明)
&emsp;&emsp;[4.&emsp;コードの全容](#コードの全容)
&emsp;&emsp;[5.&emsp;終わりに](#終わりに)<br>






#作り方の説明
1,初期状態
2,カスタムセルのプロトコル宣言→→カスタムセル(TimelineTableViewCell)
3,カスタムセルのストーリーボードでの関連づけ→→カスタムセル(TimelineTableViewCell)
4,デリゲートメソッドにアラートコントローラーの設置→→ViewController
5,タイムラインのViewControlerでアラートコントローラーのアクションの設置→→ViewController
6,report機能のコード→→ViewController
7,report機能の仕組み

**(例)**
###1,初期状態
最初はこの画面の想定です。(コメントアウトは消してます。)

**---ViewController---**
ViewControllerのところはTimeLineViewControllerにする人もいるかもしれないすね。名前はわかれば良いです。

```swift:ViewController.swift

import UIKit

class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,  {

    //NCMBから持ってきた投稿の情報を格納する配列
    var posts = [NCMBObject]()

    @IBOutlet var timeLineTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad() 

        //tableViewを利用する上で必ず必要な宣言文以下二つ
        timeLineTableView.dataSource = self
        timeLineTableView.delegate = self
    }

    //tableViewCellの個数を指定する関する
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return posts.count
    }
    
    //tableViewCellに表示する内容を決める関数
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TimelineTableViewCell
    
        /////////(省略)////////////
        //tableViewCellに表示する内容を記述しています。
        /////////(省略)////////////

        return cell
    }
    
}
```

***---カスタムセル---***
カスタムセルの　TimelineTableViewCell.xib ファイルの初期画面の想定です。(コメントアウトは消してます。)

```swift:TimelineTableViewCell.swift

import UIKit

class TimelineTableViewCell: UITableViewCell {

    override func awakeFromNib() {
        super.awakeFromNib()
    }
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
}
```


###2,カスタムセルのプロトコル宣言

カスタムセルのファイルであるTimelineTableViewCelでプロトコルの宣言と、カスタムセル上での関数を定義します。


```swift:TimelineTableViewCell.swift
import UIKit

//import UIKitと　TimelineTableViewCellクラスの間に記述します。
//このときにTimelineTableViewCellクラス内に記述してしまうケースが多いので気をつけましょう！
//protocol 宣言内にデリゲートメソッドを記述することで、カスタムセル上の機能を定義することができます。
protocol TimelineTableViewCellDelegate {
     //デリゲートメソッドの関数を定義します。
     //今回は　didTapMenuButtonという名前の関数を定義
    func didTapMenuButton(tableViewCell: UITableViewCell, button: UIButton)
     }

class TimelineTableViewCell: UITableViewCell {
    
    //上のプロトコル宣言をした TimelineTableViewCellDelegateクラスの変数　デリゲートを宣言します。
    var delegate: TimelineTableViewCellDelegate?
    

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

    //TimelineTableViewCellクラス内で利用できる関数 opneMenu を定義
    @IBAction func openMenu(button: UIButton) {
        //TimelineTableViewCellDelegateのデリゲートを利用することで
        //protocol TimelineTableViewCellDelegateで宣言したdidTapMenuButtonが利用できるようになる
        self.delegate?.didTapMenuButton(tableViewCell: self, button: button)
    }
    
}



```

###3,カスタムセルのストーリーボードでの関連づけ
TimelineTableViewCellのxbiファイルで、先程宣言した opneMenu と menuButton を関連づけさせます。今回は、動作の発生条件を「Touch up inside」を選択しクリックしたときに機能するようにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/20cf08cd-6b0e-b145-2271-cfc5915a979c.png)

関連付けができているかは、menuButtonを右クリックし下の図のように、「Touch Up Inside」と繋がっていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/7513d0b6-0af7-04bf-23a8-d18732a132e8.png)

これでカスタムセル側の設定は完了です。一旦お疲れ様です！！
次にViewController側の設定をします。


###4,デリゲートメソッドにアラートコントローラーの設置→→ViewController

ここからはViewControllerで作業をします。
まず初めに、必要なライブラリをimportします。(必要なものはpodファイルでinstallしましょう！！)

以下のコードを　import UIKit　の直上か直下にimport文を書いてあげましょう。

```swift:ViewController.swift
import NCMB
import Kingfisher //画像はめるときに使う
import KRProgressHUD //HUD(ハド)ライブラリ
```

HUDライブラリはタスクが進行中であることを簡潔に表示するUIのライブラリです！

次にViewController.swift内で、先程 2 で作業したプロトコルを宣言をします。(TimelineTableViewCellDelegate　を追加)
そうすることでTimelineTableCellで指定したデリゲートメソッドをViewController内で利用できるようになります。


```swift:ViewController.swift

     //TimelineTableViewCellDelegateのプロトコルを宣言
     class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, TimelineTableViewCellDelegate{

    var posts = [NCMBObject]()
    @IBOutlet var timeLineTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()        

        timeLineTableView.dataSource = self
        timeLineTableView.delegate = self

        //TableViewのカスタムセルを使用するときはnib登録をします。
        let nib = UINib(nibName: "TimelineTableViewCell", bundle: Bundle.main)
        timeLineTableView.register(nib, forCellReuseIdentifier: "Cell")

    }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //tableViewクラスのdequeueReusableCellを利用したものをTimelineTableViewCellにダウンキャストし、カスタムセルを宣言する
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TimelineTableViewCell
        //ここでcellのデリゲートをここの関数内で処理しますっていう宣言をする
        cell.delegate = self
        //後々、投稿の識別をするために必要になってくるindexPath.row（識別番号のような雰囲気）をcellのtagに代入する。
        cell.tag = indexPath.row

        /////////(省略)////////////
        //tableViewCellに表示する内容,投稿を取得する機能、その他もろもろを記述しています。
        /////////(省略)////////////

        return cell
    }
    
}
```

すると次のようなエラーが出てきますが、赤い丸ボタンを押してfixを押しましょう。
これは、先程宣言した「TimelineTableViewCellDelegateが持つプロトコルの機能が書かれていませんよ。」という感じのエラー文です。そのため、fixを押すとTimelineTableViewCellのプロトコルで宣言した関数が自動的にコードに記述されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/16fbac2b-0348-0896-8669-d75c3847905e.png)

記述された関数は、自分のコードの下の方に移動しておきましょう！（他の関数内に記述したり、ViewControllerクラスの外に書いたりしなければ、特に場所の指定はないです。）

```swift:ViewController.swift

//TimelineTableViewCellDelegateのプロトコルを宣言
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, TimelineTableViewCellDelegate{

    var posts = [NCMBObject]()
    override func viewDidLoad() {
        super.viewDidLoad() 

        timeLineTableView.dataSource = self
        timeLineTableView.delegate = self

        let nib = UINib(nibName: "TimelineTableViewCell", bundle: Bundle.main)
        timeLineTableView.register(nib, forCellReuseIdentifier: "Cell")
        /////////(省略)////////////
        //画面が立ち上がったときに一度だけ呼ばれる内容を記述しています。
        /////////(省略)////////////


    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TimelineTableViewCell
        cell.delegate = self
        cell.tag = indexPath.row

        /////////(省略)////////////
        //tableViewCellに表示する内容,投稿を取得する機能、その他もろもろを記述しています。
        /////////(省略)////////////

        return cell
    }



    func didTapMenuButton(tableViewCell: UITableViewCell, button: UIButton) {

    }
    
}
```

###5,タイムラインのViewControlerでアラートコントローラーのアクションの設置→→ViewController
ここではdidTapMenuButton関数の内容を記述します。
今回はアラートコントローラーを設置し、そのアクションの一つとして報告機能を実装します。
実際のSNSでは削除機能アクション、ブロック機能アクションなどもつけるのですが、今回は報告機能アクションとキャンセルアクションのみを追加します。

```swift:ViewController.swift

    func didTapMenuButton(tableViewCell: UITableViewCell, button: UIButton) {
        //①UIAlertControllerクラスのpreferredStyle(アラートの出現方法)が.actionSheetであるalertControllerを宣言
        //.actionSheetは下からニョキって出てくるスタイル
        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)

        //alertController内でのアクションを宣言する
        //②報告機能のアクション
        let reportAction = UIAlertAction(title: "報告する", style: .destructive) { (action) in

        }
        //③キャンセルアクション(何もしないでalertControllerを閉じる)
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (action) in
            //.dismissでalertControllerを閉じる機能
            alertController.dismiss(animated: true, completion: nil)
        }
        //.addActionでalertControllerにアクションを追加する
        alertController.addAction(reportAction)
        alertController.addAction(cancelAction)

        //実際にViewController上に表示させるコード
        self.present(alertController, animated: true, completion: nil)
        
    }

```


###6,報告機能のコード→→ViewController
次は、reportActionの機能を記述します。一つ前のコードの②の中に記述してください。
前提としてViewControllerクラスでは postsという配列に表示する投稿の情報が格納されています。

```swift:ViewController.swift

        let reportAction = UIAlertAction(title: "報告する", style: .destructive) { (action) in
            
            //クラス名がReportであるNCMBObjectをobjectを作成する。
            let object = NCMBObject(className: "Report")
            
            //ここから.setObjectメソッドを利用してobjectに情報を追加していきます。
            //objectのreportedPostIdキーにmenuボタンを押したcellの投稿のobjectIdを追加します。
            object?.setObject(self.posts[tableViewCell.tag].objectId, forKey: "reportedPostId")
            
            //objectのuserIdキーにログインしているユーザーのobjectIdを追加します。
            object?.setObject(NCMBUser.current().objectId, forKey: "userId")

            //NCMB上にobjectを作成し、保存するコード
            //CRUDのCreateの部分です。
            object?.saveInBackground({ (error) in
		        if error != nil {
                        //もし保存する際にエラーがあったらその地域の言葉でエラーの内容を表示させます。
                        SVProgressHUD.showError(withStatus: error?.localizedDescription)
		        } else {
                        //エラーがなかった場合、この投稿を報告しました。ご協力ありがとうございました。と表示します。
                        //これで報告完了です。
                    KRProgressHUD.showSuccess(withMessage: "この投稿を報告しました。ご協力ありがとうございました。")
		        }
	        })

        }

```

self.posts[tableViewCell.tag].objectIdについて、tableViewCell.tagというのは、tableViewのcellForRowAt内で定義したもので、その投稿がposts配列内で何番目であるかということを表しています。
そのため、posts[tableViewCell.tag]というのはメニューボタンを押した投稿を表しています。
今回の報告機能ではその投稿のobjectIDをNCMB上に保存しています。

報告した投稿のobjectIdは、setObjectでforkeyを”reportedPostId”としたのでreportedPostIdの列に追加されます。
同様に、報告したユーザーのuserIdは、setObjectでforkeyを”userId”としたのでuserIdの列に追加されます。
以上で完成です。実際にbuildし投稿機能を実施すると、以下の写真のようにNCMBのデータストアにReportクラスが作成され、報告した投稿のobjectIdとユーザーのuserIdが保存されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/996db4b7-151d-63b5-32bb-5aec5b0b1314.png)




#コードの全容
**(例)**

```swift:TimelineTableViewCell.swift
import UIKit

protocol TimelineTableViewCellDelegate {
    func didTapLikeButton(tableViewCell: UITableViewCell, button: UIButton)
    func didTapMenuButton(tableViewCell: UITableViewCell, button: UIButton)
    func didTapCommentsButton(tableViewCell: UITableViewCell, button: UIButton)
     }

class TimelineTableViewCell: UITableViewCell {
    
    var delegate: TimelineTableViewCellDelegate?
    @IBOutlet var userImageView: UIImageView!
    @IBOutlet var userNameLabel: UILabel!
    @IBOutlet var photoImageView: UIImageView!
    @IBOutlet var likeButton: UIButton!
    @IBOutlet var likeCountLabel: UILabel!
    @IBOutlet var postTextView: UITextView!
    @IBOutlet var timestampLabel: UILabel!
    
   
    

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code

        userImageView.layer.cornerRadius = userImageView.bounds.width/2.0
        userImageView.clipsToBounds = true
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    @IBAction func like(button: UIButton) {
        self.delegate?.didTapLikeButton(tableViewCell: self, button: button)
    }
    
    @IBAction func openMenu(button: UIButton) {
        self.delegate?.didTapMenuButton(tableViewCell: self, button: button)
    }
    
    @IBAction func showComments(button: UIButton) {
        self.delegate?.didTapCommentsButton(tableViewCell: self, button: button)
    }
    
}

```


```swift:ViewController.swift


import UIKit
import NCMB
import KRProgressHUD


class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, TimelineTableViewCellDelegate {
    
    var posts = [NCMBObject]()

    @IBOutlet var timeLineTableView: UITableView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        timeLineTableView.dataSource = self
        timeLineTableView.delegate = self
        
        
        let nib = UINib(nibName: "TimelineTableViewCell", bundle: Bundle.main)
        timeLineTableView.register(nib, forCellReuseIdentifier: "Cell")
        timeLineTableView.tableFooterView = UIView()

        
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return posts.count
    }
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TimelineTableViewCell
        
        cell.delegate = self
        cell.tag = indexPath.row


        /////////(省略)////////////
        /////////(省略)////////////

        return cell
    }
    
 
    func didTapMenuButton(tableViewCell: UITableViewCell, button: UIButton) {
        
        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (action) in
            alertController.dismiss(animated: true, completion: nil)
        }

        let reportAction = UIAlertAction(title: "報告する", style: .destructive) { (action) in
            let object = NCMBObject(className: "Report") 
            object?.setObject(self.posts[tableViewCell.tag].objectId, forKey: "reportedPostId")
            object?.setObject(NCMBUser.current().objectId, forKey: "userId")
            object?.saveInBackground({ (error) in
                if error != nil {
                    KRProgressHUD.showSuccess(withMessage: error?.localizedDescription)
                } else {
                    KRProgressHUD.showSuccess(withMessage: "この投稿を報告しました。ご協力ありがとうございました。")
                }
            })
        }

        alertController.addAction(reportAction)
        alertController.addAction(cancelAction)

        self.present(alertController, animated: true, completion: nil)
    }
        /////////(省略)////////////
        /////////(省略)////////////
   
    
}

```


#終わりに


皆さん出来ましたか？？
かなり長い説明になりましたが理解できましたでしょうか？？
報告機能自体はそこまで難しくない機能でmBaasの C(create)R(read)U(update)D(delete)のうちのCとRのみ理解できれば実装できる機能です。
しかし、SNSアプリの場合、カスタムセルと組み合わせて利用する機会が多いと思うのでデリゲートやプロトコルの部分でつまづいてしまうことが多いと思います。
もし理解できなかったこと、もっと詳しく聞きたいと思ったことなどございました、連絡してくださると助かります！