こんばんは.iOSAdventCalendarも,もう21日目ですね.
よく見かける横スクロールのメニューを作ってみた話をします.
ちょっとおしゃれなUIを実装する時,ついついライブラリに頼りがちだったりすると思うのですが,
自前で実装してみたら案外簡単だったのでご紹介します.
完成品はこんな感じです.(画質悪くてすいません・・・)
適当にスクロールしたときでも,指定したいタブが自動的に中心に来る,といったメニューです.
こういうの難しそうーって思ってる方向けのお話です.
概要
横スクロールのメニュー実装はいくつかあるかもなのですが,今回はUIScrollViewとUILabelを使いました.
まずはじめに,メニューの見た目を作って,その後いい場所で止まるスクロールを実装してみたいと思います.
見た目を作ってみよう
まずはStoryboardにぺたっと
メニューを置きたいところにUIScrollViewを置きましょう.
基本的には幅は画面の横幅と合わせるといいと思います.
縦はお好みで.私は60にしました.
そしたら,コードの方にUIScrollViewを紐付けといてください.
つぎは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です.
いよいよ本題
それでは,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)
})
}
}
まとめ
こういうアニメーションを作るには座標の計算とかちょっと頭を使いますが,やってみると意外と簡単です.
ちょっとの努力でアプリのおしゃれ度がぐっと増すと思うので,ぜひやってみてください.
最後に
時間越えちゃいましたね.ごめんなさい.
急ぎで書いてしまったので分かりにくいとこもあると思いますが,
ソースコード全体を載せましたので,そこから読み取ってください(土下座)
ではでは,みなさん楽しいクリスマスを〜