スワイプすると出てくるメニューをSwiftで作る

#環境
Xcode 8.2 , Swift3.0.2
iPhoneSEのシミュレーターを使ってます。

#メニューのライブラリはたくさんある
便利なメニューのライブラリは世の中にたくさんあります。ですが自分で実装することで勉強にもなると思ったので作ってみました。また、_再利用できる_をテーマに自分がどう考えどうやって実装したのかをまとめようとおもったので投稿しようと思いました。

#こんな感じのを作ります
(gifアニメの再生速度が遅い)
![Qiita](https://cl.ly/1Q0U450E3G3g/a.gif)


#メニューとなるViewを作る
まずはメニューとなるViewを作ります。UIViewクラスを継承したSideMenuというカスタムクラスを作ります。
メニューにはボタンを配置したいのと、メニューを出したいViewControllerの情報がほしい(後述)ので、イニシャライザーには引数としてUIImageの配列とUIViewControllerを指定します。


```swift:SideMenu.swift
class SideMenu: UIView {
    
    //サイドメニューのサイズ
    var size: CGRect?

    //イニシャライザー
    init(image: [UIImage],parentViewController: UIViewController) {
        self.size = CGRect(x:UIScreen.main.bounds.width, //画面の外に配置
                           y:0,
                           width:UIScreen.main.bounds.width*2,
                           height:UIScreen.main.bounds.height
        )
        super.init(frame: size)
        //サイドメニューの背景色
        self.backgroundColor = UIColor.darkGray
        //サイドメニューの背景色の透過度
        self.alpha = 0.8

        //ボタンをおした時にbuttonSet関数を呼び出す
        self.buttonSet(num: image.count,image: image)
        //親ViewControllerを指定
        self.parentVC = parentViewController
    }

    //UIViewを継承したクラスには必要?ここら辺よくわかりません
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //親ビューで指定した画像の数だけボタンを生成、配置
    func buttonSet(num:Int, image:[UIImage]){
        for i in 0..<num{
            let button =
                UIButton(frame:CGRect(x:10,
                                      y:50+110*i,
                                      width:90, height:90))
            //ボタンの画像
            button.setImage(image[i], for: .normal)
            //ボタンの四隅に余白をつける
            button.imageEdgeInsets = UIEdgeInsetsMake(20, 20, 20, 20)
            //ボタンの背景色
            button.backgroundColor = UIColor.yellow
            // サイズの半分の値を設定 (丸いボタンにするため)
            button.layer.cornerRadius = 45
            //ボタンにタグをつける
            button.tag = i
            //ボタンをおした時の動作
            button.addTarget(self,
                         action:#selector(self.onClickButton(sender:)),
                             for: .touchUpInside)
            self.addSubview(button)
        }
    }

}

```

#メニューを使いたいViewController

ボタンに貼り付ける画像の配列を作り、SideMenuのインスタンスを生成します。はじめはメニューを画面サイズの範囲外においておきます。

```ViewController.swift
import UIKit

class ViewController: UIViewController {
    var sideView : SideMenu!    

    override func viewDidLoad() {
        super.viewDidLoad()
        let imageArray = [UIImage(named:"0.png")!,UIImage(named:"1.png")!,UIImage(named:"2.png")!]
        sideView = SideMenu(image:imageArray, parentViewController:self)
        self.view.addSubview(sideView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
}
```

#画面の端からのスワイプを検出する
今回作るメニューは画面の端からのスワイプで出すようにします。
画面の端からのスワイプを検出するには_UIScreenEdgePanGestureRecognizer_を使います。右側からメニューを出したいので画面右からのスワイプを検出させます。

追加分のみ書いていきます。

```ViewController.swift
class ViewController: UIViewController {

 @IBOutlet var RightEdgePanGesture: UIScreenEdgePanGestureRecognizer!

 override func viewDidLoad() {  
        RightEdgePanGesture.edges = .right
    }

//スワイプを検出したときの挙動
 @IBAction func EdgePanGesture(_ sender: UIScreenEdgePanGestureRecognizer) {
        sideView.getEdgeGesture(sender: sender)
    }

}
```


#メニューを出す
SideMenuクラスを別のプロジェクトで再利用するときのことを考えると、メニューの出し入れに関することはSideMenu.swift側に書いておいたほうがいいでしょう。なのでSideMenu.swiftにメニューの出し入れを行う関数を追加していきます。

```SideMenu.swift
class SideMenu: UIView {

//省略

func getEdgeGesture(sender: UIScreenEdgePanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translation(in: parentVC.view)
        
        //画面の端からの移動量
        self.frame.origin.x += move.x
        //画面表示を更新する。
        self.layoutIfNeeded()
        
        //ドラッグ終了時の処理
        if(sender.state == UIGestureRecognizerState.ended) {
            if(self.frame.origin.x < UIScreen.main.bounds.width - parentVC.view.frame.size.width/4) {
                //ドラッグの距離が画面幅の1/3を超えた場合はメニューを出す
                UIView.animate(withDuration: 0.8,
                               animations: {
                                self.frame.origin.x = UIScreen.main.bounds.width*2/3
                },
                               completion:nil)
                //後述
                clearView.isHidden = false 

            }else {
                //ドラッグの距離が画面幅の1/3以下の場合はそのままメニューを右に戻す。
                UIView.animate(withDuration: 0.8,
                               animations: {
                                self.frame.origin.x = UIScreen.main.bounds.width
                },
                               completion:nil)
            }
        }
        //移動量をリセットする。
        sender.setTranslation(CGPoint.zero, in: parentVC.view)
   }   

}
```

#メニューを閉じる
メニュー外のところをタップしたらメニューを閉じるようにします。

```SideMenu.swift
class SideMenu:UIView{
 
    var clearView : UIView!

    init(image: [UIImage],parentViewController: UIViewController){
       //省略

       //メニュー以外の場所をタップしたときにメニューを下げる
       clearView =
            UIView(frame:CGRect(x:0,y:0,
                    width:UIScreen.main.bounds.width*2/3,
                    height:UIScreen.main.bounds.height
                    ))
        parentVC.view.addSubview(clearView)
        let tapGesture = UITapGestureRecognizer(
            target: self,
            action: #selector(self.clearViewTapped)
        )
        tapGesture.numberOfTapsRequired = 1
        clearView.isHidden = true
        clearView.addGestureRecognizer(tapGesture)
        }
     
    func clearViewTapped(){
        if clearView.isHidden == false {
            UIView.animate(withDuration: 0.8,
                           animations: {
                           self.frame.origin.x = UIScreen.main.bounds.width
            },
                           completion:nil)
        }
    }

}
```


#メニューに配置したボタンをおした時の挙動
メニューに配置したボタンをおした時の挙動はプロジェクトごとに異なるので、デリゲートを使ってボタンの挙動を他のクラスに委譲できるようにします。

```SideMenu.swift

@objc protocol SideMenuDelegate {
    func onClickButton(sender:UIButton)
}

class SideMenu : UIView{
    //デリゲートのインスタンスを宣言
    weak var delegate: SideMenuDelegate?    

    /*              省略               */

    //委譲するメソッド
    func onClickButton(sender:UIButton){
        self.delegate?.onClickButton(sender: sender)
    }
}
```

```ViewController.swift

class ViewController: UIViewController, SideMenuDelegate {

     override func viewDidLoad() {
        //追加   
        sideView.delegate = self
     }

/*  デリゲートメソッド   */
    func onClickButton(sender: UIButton) {

     //処理  

    }


}
```


#終わりに
Githubに[上記コードのまとめ](https://github.com/soufuru/SideMenu)を載せておきます。