スライダを利用する

ある規定の区間の数値を連続的に確認しながら決定するにはスライダを使うのが便利です。

ここではカラーの色成分である Red、Green、Blue、Opacity、をスライダで決定して、Rectangle をその色で塗りつぶすというサンプルを作ってみます。

単純にスライダを並べる方法

なんの工夫もなく Slider のコードを並べる方法です。

スライダの最小値と最大値は 0.0 と 255.0 にしています。刻み値は 1.0 にしました。スライダの値として取得できるのは 0 〜255ですが、色成分は RGBA それぞれ 0.0 〜 1.0 で指定しなければなりません。28〜32行目の演算をすることで 0.0 〜 1.0 に変換します。

68、81、94、107行目の onEditingChanged では編集中( スライド中 )を示すフラグを取得しているだけですが、もし必要ならばその他の任意の処理を記述してもかまいません。

114行目の .padding の値は、画面の左右端部からの空白マージンを決定しています。ためしに .padding(0.0) にしてみたら、この値で何が変わるかわかると思います。

import SwiftUI

struct SLIDER {
    static let MIN:  Double =   0.0
    static let MAX:  Double = 255.0
    static let STEP: Double =   1.0
}

struct ContentView: View {

    @State var FlagNowEditingR = false
    @State var FlagNowEditingG = false
    @State var FlagNowEditingB = false
    @State var FlagNowEditingA = false

    @State var SliderR: Double =  32.0 * 1
    @State var SliderG: Double =  32.0 * 3
    @State var SliderB: Double =  32.0 * 5
    @State var SliderA: Double =  32.0 * 7

    var body: some View {

        Spacer()

        VStack {

            // 色成分は 0.0 〜 1.0 で指定する.
            let r = Double( SliderR )/Double( 255.0 )
            let g = Double( SliderG )/Double( 255.0 )
            let b = Double( SliderB )/Double( 255.0 )
            let a = Double( SliderA )/Double( 255.0 )
            let the_color = Color( red: r, green: g, blue: b, opacity: a )

            // 指定した色で塗りつぶした矩形を表示する.
            let RECT_W = 240.0
            let RECT_H = 160.0
            Rectangle()
                .stroke( .black, lineWidth: 2.0 )
                .background( the_color )
                .frame(width: RECT_W, height: RECT_H)

        }

        Spacer()

        VStack {

            // 0 〜 255 で表示する.
            let r = Int( SliderR.rounded() )
            let g = Int( SliderG.rounded() )
            let b = Int( SliderB.rounded() )
            let a = Int( SliderA.rounded() )
            Text( String( format: "RGBA( %d, %d, %d, %d )", r, g, b, a ) )

        }

        Spacer()

        VStack {

            Text( String( format: "R is %.1f", SliderR ))
                .foregroundColor( FlagNowEditingR ? .red : .blue )

            Slider(
                value: $SliderR,
                in: SLIDER.MIN...SLIDER.MAX,
                step: SLIDER.STEP,
                onEditingChanged: {
                        editing in
                        FlagNowEditingR = editing
                    }
                )

            Text( String( format: "G is %.1f", SliderG ))
                .foregroundColor( FlagNowEditingG ? .red : .blue )

            Slider(
                value: $SliderG,
                in: SLIDER.MIN...SLIDER.MAX,
                step: SLIDER.STEP,
                onEditingChanged: {
                        editing in
                        FlagNowEditingG = editing
                    }
                )

            Text( String( format: "B is %.1f", SliderB ))
                .foregroundColor( FlagNowEditingB ? .red : .blue )

            Slider(
                value: $SliderB,
                in: SLIDER.MIN...SLIDER.MAX,
                step: SLIDER.STEP,
                onEditingChanged: {
                        editing in
                        FlagNowEditingB = editing
                    }
                )

            Text( String( format: "A is %.1f", SliderA ))
                .foregroundColor( FlagNowEditingA ? .red : .blue )

            Slider(
                value: $SliderA,
                in: SLIDER.MIN...SLIDER.MAX,
                step: SLIDER.STEP,
                onEditingChanged: {
                        editing in
                        FlagNowEditingA = editing
                    }
                )

        }
        .padding( 30.0 )

        Spacer()

    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

同じようなコードを書くのはプログラマとしては、苦痛で退屈ですね。次にスライダの機能をまとめる方法を紹介します。

似た機能のスライダをまとめる方法

スライダの View を、MySlider という名前で再定義してそれをスライダの個数ぶんコールします。

64、69、74、79行目が極めて簡潔に記述できていることに注目してください。

import SwiftUI

struct SLIDER {
    static let MIN:  Double =   0.0
    static let MAX:  Double = 255.0
    static let STEP: Double =   1.0
}

struct ContentView: View {

    @State var FlagNowEditingR = false
    @State var FlagNowEditingG = false
    @State var FlagNowEditingB = false
    @State var FlagNowEditingA = false

    @State var SliderR: Double =  32.0 * 1
    @State var SliderG: Double =  32.0 * 3
    @State var SliderB: Double =  32.0 * 5
    @State var SliderA: Double =  32.0 * 7

    var body: some View {

        Spacer()

        VStack {

            // 色成分は 0.0 〜 1.0 で指定する.
            let r = Double( SliderR )/Double( 255.0 )
            let g = Double( SliderG )/Double( 255.0 )
            let b = Double( SliderB )/Double( 255.0 )
            let a = Double( SliderA )/Double( 255.0 )
            let the_color = Color( red: r, green: g, blue: b, opacity: a )

            // 指定した色で塗りつぶした矩形を表示する.
            let RECT_W = 240.0
            let RECT_H = 160.0
            Rectangle()
                .stroke( .black, lineWidth: 2.0 )
                .background( the_color )
                .frame(width: RECT_W, height: RECT_H)

        }

        Spacer()

        VStack {

            // 0 〜 255 で表示する.
            let r = Int( SliderR.rounded() )
            let g = Int( SliderG.rounded() )
            let b = Int( SliderB.rounded() )
            let a = Int( SliderA.rounded() )
            Text( String( format: "RGBA( %d, %d, %d, %d )", r, g, b, a ) )

        }

        Spacer()

        VStack {

            Text( String( format: "R is %.1f", SliderR ))
                .foregroundColor( FlagNowEditingR ? .red : .blue )

            MySlider( value: $SliderR, flag_now_editing: $FlagNowEditingR )

            Text( String( format: "G is %.1f", SliderG ))
                .foregroundColor( FlagNowEditingG ? .red : .blue )

            MySlider( value: $SliderG, flag_now_editing: $FlagNowEditingG )

            Text( String( format: "B is %.1f", SliderB ))
                .foregroundColor( FlagNowEditingB ? .red : .blue )

            MySlider( value: $SliderB, flag_now_editing: $FlagNowEditingB )

            Text( String( format: "A is %.1f", SliderA ))
                .foregroundColor( FlagNowEditingA ? .red : .blue )

            MySlider( value: $SliderA, flag_now_editing: $FlagNowEditingA )

        }
        .padding( 30.0 )

        Spacer()

    }

}

struct MySlider: View {

    @Binding var value: Double
    @Binding var flag_now_editing: Bool

    var body: some View {

        Slider(
            value: $value,
            in: SLIDER.MIN...SLIDER.MAX,
            step: SLIDER.STEP,
            onEditingChanged: {
                    editing in
                    flag_now_editing = editing
                }
            )

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Text のところが同じようなコードで、これまた苦痛で退屈ですね。本記事の主旨からはずれますが、テキストの機能もまとめてみましょう。

おまけ、テキストをまとめる方法

テキストの View を、MyText という名前で再定義してそれをコールします。

import SwiftUI

struct SLIDER {
    static let MIN:  Double =   0.0
    static let MAX:  Double = 255.0
    static let STEP: Double =   1.0
}

struct ContentView: View {

    @State var FlagNowEditingR = false
    @State var FlagNowEditingG = false
    @State var FlagNowEditingB = false
    @State var FlagNowEditingA = false

    @State var SliderR: Double =  32.0 * 1
    @State var SliderG: Double =  32.0 * 3
    @State var SliderB: Double =  32.0 * 5
    @State var SliderA: Double =  32.0 * 7

    var body: some View {

        Spacer()

        VStack {

            // 色成分は 0.0 〜 1.0 で指定する.
            let r = Double( SliderR )/Double( 255.0 )
            let g = Double( SliderG )/Double( 255.0 )
            let b = Double( SliderB )/Double( 255.0 )
            let a = Double( SliderA )/Double( 255.0 )
            let the_color = Color( red: r, green: g, blue: b, opacity: a )

            // 指定した色で塗りつぶした矩形を表示する.
            let RECT_W = 240.0
            let RECT_H = 160.0
            Rectangle()
                .stroke( .black, lineWidth: 2.0 )
                .background( the_color )
                .frame(width: RECT_W, height: RECT_H)

        }

        Spacer()

        VStack {

            // 0 〜 255 で表示する.
            let r = Int( SliderR.rounded() )
            let g = Int( SliderG.rounded() )
            let b = Int( SliderB.rounded() )
            let a = Int( SliderA.rounded() )
            Text( String( format: "RGBA( %d, %d, %d, %d )", r, g, b, a ) )

        }

        Spacer()

        VStack {

            MyText( color_name: "R", value: SliderR, flag_now_editing: FlagNowEditingR )
            MySlider( value: $SliderR, flag_now_editing: $FlagNowEditingR )

            MyText( color_name: "G", value: SliderG, flag_now_editing: FlagNowEditingG )
            MySlider( value: $SliderG, flag_now_editing: $FlagNowEditingG )

            MyText( color_name: "B", value: SliderB, flag_now_editing: FlagNowEditingB )
            MySlider( value: $SliderB, flag_now_editing: $FlagNowEditingB )

            MyText( color_name: "A", value: SliderA, flag_now_editing: FlagNowEditingA )
            MySlider( value: $SliderA, flag_now_editing: $FlagNowEditingA )

        }
        .padding( 30.0 )

        Spacer()

    }

}

struct MySlider: View {

    @Binding var value: Double
    @Binding var flag_now_editing: Bool

    var body: some View {

        Slider(
            value: $value,
            in: SLIDER.MIN...SLIDER.MAX,
            step: SLIDER.STEP,
            onEditingChanged: {
                    editing in
                    flag_now_editing = editing
                }
            )

    }
}

struct MyText: View {

    var color_name: String
    var value: Double
    var flag_now_editing: Bool

    var body: some View {

        Text( String( format: "%@ is %.1f", color_name, value ))
            .foregroundColor( flag_now_editing ? .red : .blue )

    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

59〜73行目の VStack で囲まれた部分が MySlider と MyText のおかげで、かなり簡略化されました。とてもいい感じです。