search
LoginSignup
38

More than 5 years have passed since last update.

posted at

updated at

Organization

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

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

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

完成品はこんな感じです.(画質悪くてすいません・・・)
適当にスクロールしたときでも,指定したいタブが自動的に中心に来る,といったメニューです.
iphone_movie.gif

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

概要

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

見た目を作ってみよう

まずはStoryboardにぺたっと

メニューを置きたいところにUIScrollViewを置きましょう.
基本的には幅は画面の横幅と合わせるといいと思います.
縦はお好みで.私は60にしました.
そしたら,コードの方にUIScrollViewを紐付けといてください.
スクリーンショット 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

いよいよ本題

それでは,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)
        })
    }    
}   

まとめ

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

最後に

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
38