Swift で縦スライダーバーを実装する


Xcode のデフォルトの部品に SliderBar がありますが、
これを縦にしたやつの実装が必要になったので、その実装方法を残しておきます。

# 完成イメージ
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/b39fec7d-b117-3020-3e44-54e86c68b43f.gif)


# 開発環境
Xcode: 10.2.1
iOS: 12.2
Swift: Swift5

# 実装

実際に実装していきたいと思います

## Storyboard

今回は以下のように View を配置しています
<img width="574" alt="スクリーンショット 2019-07-15 1.59.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/865bac73-25b4-56fe-564a-eaecd06f8df6.png">
- baseView
    - スライダーの「つまみ」の可動域となる部分
- sliderView
    - スライダーの「つまみ」となる部分

ポイントとしては、baseView の中に sliderView が入っていない点です
baseView の中に sliderView が入ってしまっていると、この後のドラッグ処理実装の際に
座標の計算がおかしくなってしまうので注意してください


## ViewController

```swift
import UIKit

class ViewController: UIViewController {
    /// スライダーの可動域
    @IBOutlet weak var baseView: UIView!
    /// スライダーのつまみ部分
    @IBOutlet weak var sliderView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSlider()
    }
    
    /// スライダーのつまみをドラッグしたのを検知
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first,
            let movedView = touch.view else {
                return
        }
        // ドラッグした view がつまみ（sliderView）ではなければ何もしない
        guard movedView == sliderView else {
            return
        }
        moveSliderView(to: touch.location(in: view))
    }
    
    /// スライダーの描画設定
    private func setupSlider() {
        let baseViewHalfWidth = baseView.frame.width / 2.0
        let sliderViewHalfWidth = sliderView.frame.width / 2.0
        // baseView の角を丸めたり、つまみを円形にするための処理
        baseView.layer.cornerRadius = baseViewHalfWidth
        sliderView.layer.cornerRadius = sliderViewHalfWidth
    }
    
    /// つまみを移動させる処理
    private func moveSliderView(to point: CGPoint) {
        // 今回は縦方向のみに動かしたいため、x 座標は sliderView.center.x に固定する
        var movePoint = CGPoint(x: sliderView.center.x,
                                y: point.y)
        // sliderView の center の位置を変更することでドラッグの動きに合わせて移動させる
        sliderView.center = movePoint
        
    }
}
```
このようにすると、上下の動きが実装できるかと思います！
ただ、今の状態だと可動域を超えてもドラッグできてしまうので、この部分を修正します

## 可動域を修正
```swift
class ViewController: UIViewController {
    
    ...

    /// スライダー可動域の Y 座標の上限値
    private var baseViewMaxY: CGFloat! //  <--- 追加
    /// スライダー可動域の Y 座標の下限値
    private var baseViewMinY: CGFloat! //  <--- 追加
    
    ...
    
    /// スライダーの描画設定
    private func setupSlider() {
        let baseViewHalfWidth = baseView.frame.width / 2.0
        let sliderViewHalfWidth = sliderView.frame.width / 2.0
        /* 
         上限値を baseView.frame.maxY or minY で取得しており、
         sliderView を動かす際は sliderView.center の値を変更して動かしているため、
         可動域ぴったりに収まるよう調節
         */
        baseViewMaxY = baseView.frame.maxY - baseViewHalfWidth //  <--- 追加
        baseViewMinY = baseView.frame.minY + sliderViewHalfWidth //  <--- 追加
        baseView.layer.cornerRadius = baseViewHalfWidth
        sliderView.layer.cornerRadius = sliderViewHalfWidth
    }
    
    /// つまみを移動させる処理
    private func moveSliderView(to point: CGPoint) {
        var movePoint = CGPoint(x: sliderView.center.x,
                                y: point.y)
        //  <--- ここから追加 --->
        if movePoint.y > baseViewMaxY {
            // 上限値を超えた場合は上限値以上にならないようにする
            movePoint.y = baseViewMaxY
        }
        
        if movePoint.y < baseViewMinY {
            // 加減値より小さくなった場合は下限値以下にならないようにする
            movePoint.y = baseViewMinY
        }
        //  <--- ここまで追加 --->
        sliderView.center = movePoint
    }
}
```
上記のように修正することで、可動域をはみ出ないように制御することが可能になりました！


# サンプル
以下に今回実装したコードを上げていますので、良かったら参考にしてください
https://github.com/nwatabou/VerticalSlider