UIScrollViewで横スクロールのメニューを作ってみよう


こんばんは．iOSAdventCalendarも，もう21日目ですね．
よく見かける横スクロールのメニューを作ってみた話をします．

ちょっとおしゃれなUIを実装する時，ついついライブラリに頼りがちだったりすると思うのですが，
自前で実装してみたら案外簡単だったのでご紹介します．

完成品はこんな感じです．(画質悪くてすいません・・・)
適当にスクロールしたときでも，指定したいタブが自動的に中心に来る，といったメニューです．
![iphone_movie.gif](https://qiita-image-store.s3.amazonaws.com/0/68960/9f4d5ce4-5817-5f45-760d-b3c96b06bf19.gif "iphone_movie.gif")



こういうの難しそうーって思ってる方向けのお話です．

# 概要
横スクロールのメニュー実装はいくつかあるかもなのですが，今回はUIScrollViewとUILabelを使いました．
まずはじめに，メニューの見た目を作って，その後いい場所で止まるスクロールを実装してみたいと思います．

# 見た目を作ってみよう
## まずはStoryboardにぺたっと
メニューを置きたいところにUIScrollViewを置きましょう．
基本的には幅は画面の横幅と合わせるといいと思います．
縦はお好みで．私は60にしました．
そしたら，コードの方にUIScrollViewを紐付けといてください．
![スクリーンショット 2016-12-21 午後6.40.29.png](https://qiita-image-store.s3.amazonaws.com/0/68960/25994d0a-06de-24b2-8c4a-c6f081405759.png "スクリーンショット 2016-12-21 午後6.40.29.png")

## つぎはScrollViewにUILabelをぺたぺたと
つづいて，メニュー1つ1つのタブになるUILabelを用意していきましょう．
まずはscrollViewの右端に1つ目のUILabelをぺたっと．
そして，どんどん右にずらしてぺたぺたっと．
これで，とりあえずスクロールするメニューが出来ます．

	@IBOutlet weak var scrollView: UIScrollView!
  
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let titles = ["月","火","水","木","金","土","日"] //タブのタイトル
        
        //タブの横幅
        let tabLabelWidth:CGFloat = 100
        //タブの縦幅(UIScrollViewと一緒にします)
        let tabLabelHeight:CGFloat = scrollView.frame.height
        
        //タブのx座標．0から始まり，少しずつずらしていく．
        var originX:CGFloat = 0
        //titlesで定義したタブを1つずつ用意していく
        for title in titles {
            //タブになるUILabelを作る
            let label = UILabel()
            label.textAlignment = .center
            label.frame = CGRect(x:originX, y:0, width:tabLabelWidth, height:tabLabelHeight)
            label.text = title
            
            //scrollViewにぺたっとする
            scrollView.addSubview(label)
            
            //次のタブのx座標を用意する
            originX += tabLabelWidth
        }
        
        //scrollViewのcontentSizeを，タブ全体のサイズに合わせてあげる(ここ重要！)
        //最終的なoriginX = タブ全体の横幅 になります
        scrollView.contentSize = CGSize(width:originX, height:tabLabelHeight)
        
    }

        
# ぴたっといい感じに止まるスクロールを作ってみよう
つづいて，指定したいタブでぴたっといい感じに止まるスクロールを実装しましょう．

## UIScrollViewのDelegateの指定
まずは，scrollViewのDelegateを指定してあげましょう．
	
	class ViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var scrollView: UIScrollView!    
    @IBOutlet weak var weekdayLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scrollView.delegate = self
        
##タブを止める位置にラインを用意
        
そして，ぴたっと止まる目安になる青のラインを用意しましょう．
Storyboardで，scrollViewの真下・画面の中心・幅100(タブと同じ)・高さ3のViewを作ればOKです．
![スクリーンショット 2016-12-21 午後11.29.13.png](https://qiita-image-store.s3.amazonaws.com/0/68960/9039a6af-c140-649e-56df-4588c48a1f52.png "スクリーンショット 2016-12-21 午後11.29.13.png")

## いよいよ本題
それでは，UIScrollViewのDelegateを使って，ぴたっと止まるスクロールを作ります．
先ほど載せたソースコードから変更してある部分もあるので，ソースコード全体を載せます．
	
	import UIKit

	class ViewController: UIViewController, UIScrollViewDelegate {

    	@IBOutlet weak var scrollView: UIScrollView!
    
    	//一度だけメニュー作成をするためのフラグ
    	var didPrepareMenu = false
    
    	//タブの横幅
    	let tabLabelWidth:CGFloat = 100
    
    	//viewDidLoad等で処理を行うと
    	//scrollViewの正しいサイズが取得出来ません
    	override func viewDidLayoutSubviews() {
        
        	//viewDidLayoutSubviewsはviewDidLoadと違い
        	//何度も呼ばれてしまうメソッドなので
        	//一度だけメニュー作成を行うようにします
	        if didPrepareMenu { return }
	        didPrepareMenu = true
	        
	        //scrollViewのDelegateを指定
	        scrollView.delegate = self
	        
	        //タブのタイトル
	        let titles = ["月","火","水","木","金","土","日"] //タブのタイトル
	        
	        //タブの縦幅(UIScrollViewと一緒にします)
	        let tabLabelHeight:CGFloat = scrollView.frame.height
	        
	        //右端にダミーのUILabelを置くことで
	        //一番右のタブもセンターに持ってくることが出来ます
	        let dummyLabelWidth = scrollView.frame.size.width/2 - tabLabelWidth/2
	        let headDummyLabel = UILabel()
	        headDummyLabel.frame = CGRect(x:0, y:0, width:dummyLabelWidth, height:tabLabelHeight)
	        scrollView.addSubview(headDummyLabel)
	        
	        //タブのx座標．
	        //ダミーLabel分，はじめからずらしてあげましょう．
	        var originX:CGFloat = dummyLabelWidth
	        //titlesで定義したタブを1つずつ用意していく
	        for title in titles {
	            //タブになるUILabelを作る
	            let label = UILabel()
	            label.textAlignment = .center
	            label.frame = CGRect(x:originX, y:0, width:tabLabelWidth, height:tabLabelHeight)
	            label.text = title
	            
	            //scrollViewにぺたっとする
	            scrollView.addSubview(label)
	            
	            //次のタブのx座標を用意する
	            originX += tabLabelWidth
	        }
	        
	        //左端にダミーのUILabelを置くことで
	        //一番左のタブもセンターに持ってくることが出来ます
	        let tailLabel = UILabel()
	        tailLabel.frame = CGRect(x:originX, y:0, width:dummyLabelWidth, height:tabLabelHeight)
	        scrollView.addSubview(tailLabel)
	        
	        //ダミーLabel分を足して上げましょう
	        originX += dummyLabelWidth
	        
	        //scrollViewのcontentSizeを，タブ全体のサイズに合わせてあげる(ここ重要！)
	        //最終的なoriginX = タブ全体の横幅 になります
	        scrollView.contentSize = CGSize(width:originX, height:tabLabelHeight)
    	}
    
	    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
	        guard scrollView == self.scrollView else { return }
	        
	        //微妙なスクロール位置でスクロールをやめた場合に
	        //ちょうどいいタブをセンターに持ってくるためのアニメーションです
	        
	        //現在のスクロールの位置(scrollView.contentOffset.x)から
	        //どこのタブを表示させたいか計算します
	        let index = Int((scrollView.contentOffset.x + tabLabelWidth/2) / tabLabelWidth)
	        let x = index * 100
	        UIView.animate(withDuration: 0.3, animations: {
	            scrollView.contentOffset = CGPoint(x:x, y:0)
	        })
	    }
	    
	    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
	        guard scrollView == self.scrollView else { return }
	        
	        //これも上と同様に
	        
	        let index = Int((scrollView.contentOffset.x + tabLabelWidth/2) / tabLabelWidth)
	        let x = index * 100
	        UIView.animate(withDuration: 1.0, animations: {
	            scrollView.contentOffset = CGPoint(x:x, y:0)
	        })
	    }    
	}	



# まとめ
こういうアニメーションを作るには座標の計算とかちょっと頭を使いますが，やってみると意外と簡単です．
ちょっとの努力でアプリのおしゃれ度がぐっと増すと思うので，ぜひやってみてください．

# 最後に
時間越えちゃいましたね．ごめんなさい．
急ぎで書いてしまったので分かりにくいとこもあると思いますが，
ソースコード全体を載せましたので，そこから読み取ってください（土下座）
ではでは，みなさん楽しいクリスマスを〜