【SwiftUI】スライダーのつまみを二つにして範囲指定する方法

SwiftUI

今回はSliderをオリジナルで作成して、範囲指定できるようにする方法を紹介します。

普通のSliderはボリュームの指定など、範囲の中から一つの値を選ぶものでした。

しかし、今回紹介するのは、料金や年齢などの検索等で下限と上限を指定できるような仕様になっています。

絞り込み検索などで非常に便利なのでぜひ使ってみてください!

つまみが二つのスライダーのコード全体

まずはコード全体を紹介します。

コピペで簡単に表示できるので、是非一度プレビューしてみてください。

今回のコードでは、0〜100で範囲を指定できる様になっています。

var totalWidth = UIScreen.main.bounds.width - 200

struct ContentView: View {
    @State var valL: Int = 0
    @State var valH: Int = 100

    @State var widthL: CGFloat = 0
    @State var widthH: CGFloat = totalWidth - 36

    var body: some View {
        VStack {
            Text("\(String(valL)) 〜 \(String(valH))")
            SliderIntoItem(valL: $valL, valH: $valH, widthL: $widthL, widthH: $widthH)
        }
        .padding(100)
    }
}

struct SliderIntoItem: View {
    @Binding var valL: Int
    @Binding var valH: Int

    @Binding var widthL: CGFloat
    @Binding var widthH: CGFloat

    var body: some View {
        ZStack(alignment: .leading) {
            Rectangle()
                .fill(Color.orange.opacity(0.2))
                .frame(height: 6)
            Rectangle()
                .fill(Color.orange)
                .frame(width: self.widthH - self.widthL, height: 6)
                .offset(x: self.widthL + 18)
            HStack(spacing: 0) {
                Circle()
                    .fill(Color.orange)
                    .frame(width: 18, height: 18)
                    .offset(x: self.widthL)
                    .gesture(
                        DragGesture()
                            .onChanged({(value) in
                                if value.location.x >= 0 && value.location.x <= self.widthH {
                                    self.widthL = value.location.x
                                    self.valL = self.getValue(val: self.widthL / (totalWidth - 36))
                                }
                            })
                    )
                Circle()
                    .fill(Color.orange)
                    .frame(width: 18, height: 18)
                    .offset(x: self.widthH)
                    .gesture(
                        DragGesture()
                            .onChanged({(value) in
                                if value.location.x <= totalWidth - 36 && value.location.x >= self.widthL {
                                    self.widthH = value.location.x
                                    self.valH = self.getValue(val: (self.widthH / (totalWidth - 36)))
                                }
                            })
                    )
            }
        }
    }

    func getValue(val: CGFloat) -> Int {
        let result = Double(val) * 100
        return Int(round(result))
    }
}
つまみが二つで下限と上限で範囲指定できるスライダー

コードの解説

Sliderを構成する要素は四つで、以下の通りです。

  • 全体の範囲を示す薄いオレンジ色の線:Rectangle
  • 有効な範囲を示すオレンジ色の線:Rectangle
  • 下限を示すオレンジのつまみ:Circle
  • 上限を示すオレンジのつまみ:Circle

二つの線とつまみをZStackを使用することで重ねて表示しています。

つまみ同士はHStackを使い横並びで表示することで重ならないようにしています。

全体の範囲を示す薄いオレンジ色の線:Rectangle

Rectangle()
    .fill(Color.orange.opacity(0.2))
    .frame(height: 6)

Rectangleは直訳すると矩形で、.frame(width: , height: )を使用することで、任意の四角形を表示することが、可能です。

そして、.fill()を使用して色をつけていますが、任意の色に.opacity(0.2)を使用することで、不透明度を指定することができます。

有効な範囲を示すオレンジ色の線:Rectangle

Rectangle()
    .fill(Color.orange)
    .frame(width: self.widthH - self.widthL, height: 6)
    .offset(x: self.widthL + 18)

有効な範囲のオレンジ色の線は、下限のつまみから上限のつまみの間となるので、長さは.frame()widthself.widthH - self.widthLで指定します。

.offset()は使用することで位置を決定することができます。

今回はx: self.widthL + 18とすることで、左側を下限のつまみの位置+18空けて配置しています。

18というのはつまみの直径になっています。

下限を示すオレンジのつまみ:Circle

Circle()
    .fill(Color.orange)
    .frame(width: 18, height: 18)
    .offset(x: self.widthL)
    .gesture(
        DragGesture()
            .onChanged({(value) in
            if value.location.x >= 0 && value.location.x <= self.widthH {
                self.widthL = value.location.x
                self.    valL = self.getValue(val: self.widthL / (totalWidth - 36))
            }
        })
    )

Circleで簡単に円を描画することができます。

.fill().frame()で色とサイズを指定します。

@State var widthL: CGFloat = 0で初期位置を定義し、.offset()で位置を指定しています。

.gesture(DragGesture(.onChanged({})))でを使用することで、つまみを動かした際の挙動を制御できます。

つまみが動かされるとself.getValue(val: )を使用して位置から値を計算し直します。

またif文を使用することで線より左に、上限のつまみより右に行かないように制御しています。

上限を示すオレンジのつまみ:Circle

Circle()
    .fill(Color.orange)
    .frame(width: 18, height: 18)
    .offset(x: self.widthH)
    .gesture(
        DragGesture()
            .onChanged({(value) in
                if value.location.x <= totalWidth - 36 && value.location.x >= self.widthL {
                    self.widthH = value.location.x
                    self.valH = self.getValue(val: (self.widthH / (totalWidth - 36)))
                }
            })
    )

if文の内容が線より右に、下限より左に行かない様に変わっていることを除けば、下限のつまみと大きな違いはありません。

つまみの位置を値に変更する

func getValue(val: CGFloat) -> Int {
    let result = Double(val) * 100
    return Int(round(result))
}

この関数では受け取った値を綺麗な整数に直して返しているだけです。

また、今回は上限を100にしているため、100をかけています。

self.getValue(val: (self.widthL/ (totalWidth - 36)))で渡している値は、つまみの位置 / 線の長さとなっています。

線の一番右をMAXとして、つまみの位置の値を取るようになっています。

また、つまみの直径が18なので、二つ分の36を引いています。

まとめ

今回は条件で検索する際などに使える下限と上限を指定できるつまみが二つのスライダーを紹介しました。

意外と使い所があると思うので、是非コピペでプレビューしていただき、そこからお好みでカスタムしていただけたらと思います。

SwiftUI勉強中の方へ

SwiftUIのプログラミングを独学で勉強している人にはこちらの書籍もおすすめです!

基礎からしっかり学びたい方はスクールもおすすめです!

無料体験もあるため、気軽に申し込むのもありです。

コメント

タイトルとURLをコピーしました