SNSブロック機能

今回はSNSのアプリをリリースする際に必要となるブロック機能を実装していきたいと思います。
まず初めにブロック機能とは、SNSで閲覧したくないものをブロックすることでタイムライン上に表示されないようにするために実装します。
サンプルとしてNCMBを用いて取得したユーザーのオブジェクトIDをブロックする対象とします。
ここでは、NCMBのデータストアにブロッククラスを作成し、タイムライン上に表示させたくない投稿の投稿者のオブジェクトIdをアップロードしブロック機能を実装します。
前提として、[SNSアプリ報告機能の実装](https://qiita.com/naoking21/items/1e4c7973b3b8119f1aff)は完了しているものとし、ViewControllerのタイムラインTableViewのカスタムセルのデリゲートメソッドの一つとしてブロック機能を実装しています。






# 完成サンプル
![Nov-30-2021 01-46-50.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/70fdeecd-2f4d-a370-9624-8638fe9d4538.gif)









**一番下**に**コードの全容**があるので、コピぺしたかったらそこまで飛んでつけてみてください！


# 目次
&emsp;&emsp;[1.&emsp;ブロック機能の理論の話](#1ブロック機能の理論の話)
&emsp;&emsp;[2.&emsp;作り方の説明！](#2作り方の説明)


## 1.&emsp;ブロック機能の理論の話
ブロック機能は
→1.非表示にしたいユーザーをブロックする作業
→2.ブロックしたユーザーの投稿を非表示にする作業
の2つの手順に分けることができます！


## 2.&emsp;作り方の説明
**1.非表示にしたいユーザーをブロックする作業**
　→1.1タイムラインのViewControlerでアラートコントローラーのアクションの設置→→ViewController 
　→1.2ブロック機能のコード→→ViewController 
**2.ブロックしたユーザーの投稿を非表示にする機能の仕組み**

####初期状態
```swift
 func didTapMenuButton(targetCell tableViewCell: UITableViewCell, targetButton 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)
        }
        //selctedPostUserは選択してる投稿の投稿者を指しています。
        let selectedPostUser = posts[tableViewCell.tag].object(forKey: "user") as! NCMBUser
        
        //投稿者がログインユーザーか否かを条件に分岐している
        if  selectedPostUser.object(forKey: "objectId") as! String == NCMBUser.current()?.objectId{
            // ログインユーザーの投稿を削除する時の一連の流れ
            let deleteAction = UIAlertAction(title: "削除する", style: .destructive) { (action) in
               //削除機能のコード
            }
            alertController.addAction(deleteAction)
            alertController.addAction(cancelAction)
        }else{
            //ログインユーザーの投稿でない場合のながれ
            let reportAction = UIAlertAction(title: "報告する", style: .destructive) { (action) in
               //報告機能のコード
            }
            alertController.addAction(reportAction)
            alertController.addAction(cancelAction)
        }
        self.present(alertController, animated: true, completion: nil)
    }
```


####1. 非表示にしたいユーザーをブロックする作業
**1.1アラートコントローラーにアクションの設置**
ここではdidTapMenuButton関数の内容を記述します。 今回はアラートコントローラーを設置し、報告機能や削除機能のようにアクションの一つとしてブロック機能を実装します。

そのため操作するのは``ViewController``です。


```swift
            let blockAction = UIAlertAction(title: "ブロックする", style: .default) { (action) in
                HUD.show(.progress, onView: self.view)
                //NCMBのBlockクラスにオブジェクトを作成する
                let object = NCMBObject(className: "Block")
                //"blockUserId"をキーとしてブロックされる側（投稿者）のオブジェクトIDを格納
                let user = self.posts[tableViewCell.tag].object(forKey: "user") as! NCMBUser
                object?.setObject(user.objectId, forKey: "blockUserId")
                //"user"をキーとしてブロックする側（現在ログインしているユーザー）をNCMBUser型として格納
                object?.setObject(NCMBUser.current(), forKey: "user")
                //NCMB上にオブジェクトを保存
                object?.saveInBackground({ (error) in
                    if error != nil {
                        HUD.show(.progress, onView: self.view)
                    } else {
                        HUD.hide(animated: true)
                        //ここで③を読み込んでいる
                        //self.getBlockUser()
                    }
                })
            }
```

上記のブロックアクションのコードをdidTapMenuButton関数のログインユーザーの投稿でない場合の流れの中に記述します。

**1.2ブロック機能のコード**

```swift
func didTapMenuButton(targetCell tableViewCell: UITableViewCell, targetButton 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)
        }
        //selctedPostUserは選択してる投稿の投稿者を指しています。
        let selectedPostUser = posts[tableViewCell.tag].object(forKey: "user") as! NCMBUser
        
        //投稿者がログインユーザーか否かを条件に分岐している
        if  selectedPostUser.object(forKey: "objectId") as! String == NCMBUser.current()?.objectId{
            // ログインユーザーの投稿を削除する時の一連の流れ（略）
        }else{
            //ログインユーザーの投稿でない場合のながれ
            let reportAction = UIAlertAction(title: "報告する", style: .destructive) { (action) in
                //報告機能のコード
            }
            
            let blockAction = UIAlertAction(title: "ブロックする", style: .default) { (action) in
                HUD.show(.progress, onView: self.view)
                //NCMBのBlockクラスにオブジェクトを作成する
                let object = NCMBObject(className: "Block")
                //"blockUserId"をキーとしてブロックされる側（投稿者）のオブジェクトIDを格納
                let user = self.posts[tableViewCell.tag].object(forKey: "user") as! NCMBUser
                object?.setObject(user.objectId, forKey: "blockUserId")
                //"user"をキーとしてブロックする側（現在ログインしているユーザー）をNCMBUser型として格納
                object?.setObject(NCMBUser.current(), forKey: "user")
                //NCMB上にオブジェクトを保存
                object?.saveInBackground({ (error) in
                    if error != nil {
                        HUD.show(.progress, onView: self.view)
                    } else {
                        HUD.hide(animated: true)
                        //ここの後で記述する関数です。
                        self.getBlockUser()
                    }
                })
            }
            alertController.addAction(reportAction)
            //alertControllerのアクションとして追加
            alertController.addAction(blockAction)
            alertController.addAction(cancelAction)
        }
        self.present(alertController, animated: true, completion: nil)
    }
```

以上で非表示にしたいユーザーをブロックする作業のコードは完了です。



###2.ブロックしたユーザーの投稿を非表示にする作業
ブロックしたユーザーをタイムラインから非表示にする方法は大きく分けて二つの手順があります。
一つ目に、下記画像の``①``のNCMBからブロックしているユーザーの情報を取得する機能
二つ目に、下記画像の``②``のNCMBからタイムラインに表示させる投稿情報を取得するときに、ブロックしているユーザーをはじく機能です。

![スクリーンショット 2021-12-02 午後6.32.41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/583218/111150bb-af9a-895d-5a2b-b67efadf6845.jpeg)

実際にコードを書いていきましょう＾-＾
**①NCMBからブロックしているユーザーの情報を取得する機能**
ここでは``getBlockUser()``という関数を定義し、``viewWillAppear``の中で呼び出します。そうすることでこの``ViewController{{が表示されるたびに、ログインユーザーがブロックしているユーザーのオブジェクトIDを``blockUserIdArray``という配列に格納します。
(今回の機能に関係のないコードは省いています。)

```swift

import UIKit
import NCMB
import PKHUD
import Kingfisher
import SwiftDate

class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate,TimeLineTableViewCellDelegate{
	
	var followings = [NCMBUser]()
	//ブロックしているユーザーのオブジェクトIDを格納する配列を宣言
	var blockUserIdArray = [String]()
	var posts = [NCMBObject]()

	override func viewDidLoad() {
		super.viewDidLoad()
	}
	
	override func viewWillAppear(_ animated: Bool) {
		getBlockUser()
		loadFollowingUsers()
		
	}
        //ログインユーザーがブロックしているユーザーのオブジェクトIDを格納する関数
	func getBlockUser(){
                //NCMBのBlockクラスのクエリを宣言
		let query = NCMBQuery(className: "Block")
                //検索結果にuserカラムの情報を含める
		query?.includeKey("user")
                //userカラムがログインユーザーと一致するレコードを取得
		query?.whereKey("user", equalTo: NCMBUser.current())
		query?.findObjectsInBackground({ (result, error) in
			if error != nil{
                 //もし保存する際にエラーがあったらその地域の言葉でエラーの内容を表示させます。
                 HUD.flash(.labeledError(title: error?.localizedDescription, subtitle: nil), delay: 1)
			}else{
				//removeAll()で初期化をし、データの重複を防ぐ
				self.blockUserIdArray.removeAll()
				for blockObject in result as! [NCMBObject]{
                      //blockUserIdArrayに取得したレコードのblockUserIdカラムの要素のみを加える
                      self.blockUserIdArray.append(blockObject.object(forKey: "blockUserId") as! String)
				}
                //タイムラインを読み込みする
                self.loadTimeline()
			}
		})
	}

}

```

**②表示する投稿情報からブロックしているユーザーをはじく機能**
ブロックしているユーザーを表示する投稿情報から弾くコードはNCMBから情報を取得するReadの関数（今回は、``loadTimeline()``）に記述します。
クエリによる検索結果を表示する投稿情報の配列（今回は``posts``）に加える一歩前で、投稿者のオブジェクトIDがブロックしているユーザーIDの配列に含まれていなければ、表示する配列に加えるというコードを記述します。

```swift
    func loadTimeline(){
        //オートログアウト
        guard let currentUser = NCMBUser.current() else{
            //ログアウト成功
            let storyboard = UIStoryboard(name: "SignIn",bundle: Bundle.main)
            let rootViewController = storyboard.instantiateViewController(withIdentifier: "RootNavigationController")
            UIApplication.shared.keyWindow?.rootViewController = rootViewController
            //ログイン情報の保持
            let ud = UserDefaults.standard
            ud.set(true, forKey: "isLogin")
            return
        }
        //"Postクラスから取ってくる"というクエリを宣言する.クエリは仕様書のイメージですね！この仕様書をもとにデータを取ってくるって感じです。
        let query = NCMBQuery(className: "Post")
        //日付の降順でとるってことを仕様書に追記してるイメージ
        query?.order(byDescending: "createDate")
        // 投稿したユーザーの情報も同時取得することを仕様書に追記してるイメージです。
        query?.includeKey("user")
        //以上の仕様書を元にデータを取ってきます。無事取れたらresultという変数に結果(Any型配列)が入る。失敗したらerrorという変数にエラー内容(NSError型)が入ります。
        query?.findObjectsInBackground({(result,error) in
            if error != nil{
                //エラーだったら、エラーの内容を表示する
                print(error)
            }else{
                //データの取得がうまくいったら、postsっていう別の変数(箱)にデータを移し変えてあげましょう！
                for postObject in result as! [NCMBObject]{
                    let user = postObject.object(forKey: "user") as! NCMBUser
                    //念のため取れてるかprintして確認できるようにしておきます。
                    print(user)
                    print(postObject)
                    if self.blockUserIdArray.firstIndex(of: user.objectId) == nil {
                        //postsっていう別の変数(箱)にデータを写変える文章
                        self.posts.append(postObject)
                    }
                    
                }
                //tableViewを再読み込みする
                self.timelineTableView.reloadData()
            }
        })
    }
```

※**``if self.blockUserIdArray.firstIndex(of: user.objectId) == nil``**に関して
firstIndexとは配列に対して利用できるもので、先頭から要素を検索し、該当する要素のインデックスを返すメソッドです。
そのため、今回の左辺は、``blockUserIdArray``(ログインユーザーがブロックしているユーザーのオブジェクトIDの配列)の中で、``user.objectId``（投稿者のオブジェクトID）は何番目の要素かというものを示しています。右辺はnil(空)を示します。したがって、該当する要素のインデックスがnilである場合**``self.posts.append(postObject)``**のコードが実行されるようになり、タイムラインに表示されるようになります。
少し複雑な内容になっているので一度で理解できなかったとしても焦らず、それぞれの変数が何を表しているのかを考えながらなんども読み込んでみてください！



うまくいかなかったらメンターに聞いてみてください！
ここで**止まったらこの後の教材もうまくいかないままになってしまいます！**





# 不備/疑問点アンケート
教材の不備を見つけた方や、わからなかったこと、もっと詳しく解説してほしいことに関しては、[こちらの学習進捗フォーム](https://forms.gle/V5bbKcPUaoeNmDMTA)に答えてください！

著者：**髙橋直輝**