似た機能のボタンの定義をまとめる

画面上に同じようなボタンを HStack や VStack で並べる機会は多いと思います。

ただ、同じモディファイアのボタンを並べる個数ぶん記述するのがひどく苦痛です。この場合、ボタンの外観を変更したくなった場合、ボタンの個数ぶんの変更をしなくてはなりません。

プログラマたるもの、なんとかこの状況を解決できないでしょうか。

本記事では、

  • 改善のコード ( 簡単な例 )
  • 改善のコード ( 簡単な例 )

  • 改善のコード ( 実践的な例 )
  • 改善のコード ( 実践的な例 )

を示します。

改善前のコード ( 簡単な例 )

3個のボタンがあって、内部的に A = 20, B = 30 という値が仕込んであります。これを、加算、減算、乗算するボタンを定義してみます。

下記のコードは何の工夫もないコードです。3個のボタンの外観を変えたくなったら、いちいちモディファイアの値を書き換えないといけませんし、なにより加算、減算、乗算のロジックがそこに書いてあるというところがイマイチです。

import SwiftUI

struct ContentView: View {

    @State var ValueA: Int = 20
    @State var ValueB: Int = 30
    @State var Answer: Int = 0

    var body: some View {

        Spacer()

        VStack {
            Text( String( format: "A is %d", ValueA ) )
            Text( String( format: "B is %d", ValueB ) )
            Text( String( format: "Answer is %d", Answer ) )
        }

        Spacer()

        VStack {

            // 加算ボタン.
            Button( action: {
                Answer = ValueA + ValueB
            }){
                Text( "Button A + B" )
                    .font( .title3 )
                    .foregroundColor( .white )
            }
            .padding( 20.0 )
            .background( .blue )

            // 減算ボタン.
            Button( action: {
                Answer = ValueA - ValueB
            }){
                Text( "Button A - B" )
                    .font( .title3 )
                    .foregroundColor( .white )
            }
            .padding( 20.0 )
            .background( .blue )

            // 乗算ボタン.
            Button( action: {
                Answer = ValueA * ValueB
            }){
                Text( "Button A * B" )
                    .font( .title3 )
                    .foregroundColor( .white )
            }
            .padding( 20.0 )
            .background( .blue )

        }

        Spacer()

    }

}

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

改善後のコード ( 簡単な例 )

上記のコードを改善します。ボタンといっても実は View です。独自のビューを定義します。

39〜77行目が独自のビューです。

今回は、独自のボタンのビューを MyButton という名前にします。コンストラクタに与える引数の中に、action_type という引数を定義し、この値を評価してボタンの中で何をするか場合分けします。

また、演算結果をコール側にもどすにはどうすればいいでしょうか。コンストラクタの引数を参照渡しできれば目的が達成できます。

具体的な手法としては、45行目の @Binding がミソです。ボタンの中で演算した結果をコンストラクタ引数に戻します。コールする側は変数名の前に $ をつけてコールします。

最後に、28〜30行目の3個のボタンの記述が極めて簡略化されていることに注目してください。

import SwiftUI

struct BTN_ACTION {
    static let ADD = 0x01
    static let SUB = 0x02
    static let MUL = 0x04
}

struct ContentView: View {

    @State var ValueA: Int = 20
    @State var ValueB: Int = 30
    @State var Answer: Int = 0

    var body: some View {

        Spacer()

        VStack {
            Text( String( format: "A is %d", ValueA ) )
            Text( String( format: "B is %d", ValueB ) )
            Text( String( format: "Answer is %d", Answer ) )
        }

        Spacer()

        VStack {
            MyButton( title: "Button A + B", action_type: BTN_ACTION.ADD, a: ValueA, b: ValueB, want: $Answer )
            MyButton( title: "Button A - B", action_type: BTN_ACTION.SUB, a: ValueA, b: ValueB, want: $Answer )
            MyButton( title: "Button A * B", action_type: BTN_ACTION.MUL, a: ValueA, b: ValueB, want: $Answer )
        }

        Spacer()

    }

}

struct MyButton: View {

    let title: String
    let action_type: Int
    var a: Int
    var b: Int
    @Binding var want: Int

    var body: some View {

        Button( action: {

            if ( action_type == BTN_ACTION.ADD )
            {
                want = a + b
            }
            else if ( action_type == BTN_ACTION.SUB )
            {
                want = a - b
            }
            else if ( action_type == BTN_ACTION.MUL )
            {
                want = a * b
            }
            else
            {
                /* NOP */
            }

        }){
            Text( title )
                .font( .title3 )
                .foregroundColor( .white )
        }
        .padding( 20.0 )
        .background( .blue )

    }
}

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

こうしておけば、ボタンの外観を変更したくなった場合、69〜74行目のところのモディファイアの値を変更すれば、MyButton をコールしている側に配置したボタンの外観がイッキに変わります。

改善前のコード ( 実践的な例 )

このコードは、下記の記事のコードですが、6個のボタンにいちいちモディファイアやアクションをベタ書きしています。もしボタンに改造を入れた場合に、ヌケやモレが出る可能性が非常に高いです。

画像の等倍表示、フィット表示、フィル表示

画像を画面上で拡大縮小表示するときに scaledToFit や scaledToFill などのモディファイアが併用できます。この動作の詳細を実行例で紹介します。

import SwiftUI

struct STRUCT_RGBA {
    var R: UInt8
    var G: UInt8
    var B: UInt8
    var A: UInt8
}

struct ContentView: View {

    // 画像データ.
    let DATA_W = 256
    let DATA_H = 256
    @State var Buffer: UnsafeMutableRawPointer?
    @State var DataRGBA: UnsafeMutablePointer<STRUCT_RGBA>?

    // 画像データを画面に表示するためのグッズ.
    @State var TheCGContext: CGContext? = nil
    @State var TheUIImage: UIImage? = nil

    // 画面に出力する際の縮尺.
    @State var RateX = 1.0
    @State var RateY = 1.0

    let SCALE_MODE_NONE = 1
    let SCALE_MODE_FIT  = 2
    let SCALE_MODE_FILL = 4

    let STR_SCALE_MODE_NONE = "none"
    let STR_SCALE_MODE_FIT  = "fit"
    let STR_SCALE_MODE_FILL = "fill"

    @State var FlagScale: Int
    @State var StrScaleMode: String

    @State var UiImgW: CGFloat = 0.0
    @State var UiImgH: CGFloat = 0.0

    // ボタン関係の定数.
    let BTN_CORNER_RADIUS = 5.0
    let BTN_STROKE_LINE_WIDTH = 1.0
    let BTN_TXT_PADDING = 5.0
    let BTN_COLOR_FG = Color.white
    let BTN_COLOR_BG = Color.blue
    let BTN_FRAME_COLOR = Color.white

    // パスによる枠線描画の定数.
    let PATH_LINE_COLOR = Color.yellow
    let PATH_LINE_WIDTH = 2.0

    // 初期化コード.
    init (){
        FlagScale = SCALE_MODE_NONE
        StrScaleMode = STR_SCALE_MODE_NONE
    }

    var body: some View {

        ZStack{

            // 最背面は背景グレーで塗りつぶす.
            Rectangle()
                .foregroundColor( .gray )

            // 中層面で画像を表示する.
            if let TheUIImageUnwrapped = TheUIImage {

                let ui_img_w = CGFloat( DATA_W ) * RateX
                let ui_img_h = CGFloat( DATA_H ) * RateY

                if FlagScale == SCALE_MODE_NONE {
                    // 等倍描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else if FlagScale == SCALE_MODE_FIT {
                    // フィット描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .scaledToFit()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else if FlagScale == SCALE_MODE_FILL {
                    // フィル描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .scaledToFill()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else
                {
                    // 等倍描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .frame( width: ui_img_w, height: ui_img_h )
                }

                // 画像を囲む枠線を描画する.
                Path{
                    path in

                    let xs = 0.0
                    let ys = 0.0
                    let xe = ui_img_w
                    let ye = ui_img_h

                    let pnt0 = CGPoint( x: xs, y: ys )
                    let pnt1 = CGPoint( x: xe, y: ys )
                    let pnt2 = CGPoint( x: xe, y: ye )
                    let pnt3 = CGPoint( x: xs, y: ye )

                    // 0, 1, 2, 3, 0 で矩形枠線を描画する.
                    path.move( to: pnt0 )
                    path.addLine(to: pnt1 )
                    path.addLine(to: pnt2 )
                    path.addLine(to: pnt3 )
                    path.addLine(to: pnt0 )
                }
                .stroke( PATH_LINE_COLOR, lineWidth: PATH_LINE_WIDTH )
                .frame( width: ui_img_w, height: ui_img_h )

                // 現在の描画モードを表示する.
                Text( String( format: "%@,%.1f*%.1f", StrScaleMode, ui_img_w, ui_img_h ) )
                    .foregroundColor( .white )
                    .font( .system( size: 12.0 ) )

            }
            else
            {
                // TheUIImage が適切に生成されていなければ何も表示しない.
                Spacer()
            }

            // 最前面の画面上部にデバッグ情報を表示する.
            VStack{

                let scr_w = UIScreen.main.bounds.size.width
                let scr_h = UIScreen.main.bounds.size.height

                Text( String( format: "ScreenWH is %.1f*%.1f", scr_w, scr_h ))
                    .foregroundColor( .white )
                Text( String( format: "DataWH is %d*%d", DATA_W, DATA_H ))
                    .foregroundColor( .white )

            }
            .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .top )
            .padding( .top )

            VStack{

                HStack{

                    // 描画サイズ変更リクエストボタン.
                    Button( action : {
                        RateX = 0.5
                        RateY = 2.0
                    }) {
                        Text( "x0.5 x2.0" )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                    // 描画サイズ変更リクエストボタン.
                    Button( action : {
                        RateX = 2.0
                        RateY = 0.5
                    }) {
                        Text( "x2.0 x0.5" )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                    // 描画サイズ変更リクエストボタン.
                    Button( action : {
                        RateX = 1.0
                        RateY = 1.0
                    }) {
                        Text( "x1.0 x1.0" )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                }

                HStack{

                    // 等倍描画リクエストボタン.
                    Button( action : {
                        FlagScale = SCALE_MODE_NONE
                        StrScaleMode = STR_SCALE_MODE_NONE
                    }) {
                        Text( STR_SCALE_MODE_NONE )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                    // フィット描画リクエストボタン.
                    Button( action : {
                        FlagScale = SCALE_MODE_FIT
                        StrScaleMode = STR_SCALE_MODE_FIT
                    }) {
                        Text( STR_SCALE_MODE_FIT )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                    // フィル描画リクエストボタン.
                    Button( action : {
                        FlagScale = SCALE_MODE_FILL
                        StrScaleMode = STR_SCALE_MODE_FILL
                    }) {
                        Text( STR_SCALE_MODE_FILL )
                            .padding( BTN_TXT_PADDING )
                            .foregroundColor( BTN_COLOR_FG )
                            .background( BTN_COLOR_BG )
                    }
                    .overlay(
                        RoundedRectangle( cornerRadius: BTN_CORNER_RADIUS )
                            .stroke( BTN_FRAME_COLOR, lineWidth: BTN_STROKE_LINE_WIDTH )
                    )

                }

            }
            .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom )
            .padding( .bottom )

        } // ZStack end.
        .onAppear{

            // メモリを確保する.
            let numpix = DATA_W * DATA_H
            let size = MemoryLayout<STRUCT_RGBA>.stride * numpix
            let align = MemoryLayout<STRUCT_RGBA>.alignment
            Buffer = UnsafeMutableRawPointer.allocate( byteCount: size, alignment: align )

            // 連続メモリをRGBAの構造体の並びとして解釈する.
            DataRGBA = Buffer?.bindMemory( to: STRUCT_RGBA.self, capacity: numpix )

            // 確保したメモリに、ななめグラデーションデータを仕込む.
            SetGradDataRGBA( DataRGBA!, DATA_W, DATA_H )

            let one_scan_bytes = DATA_W * 4

            // ビットマップコンテキストを作成する.
            TheCGContext = CGContext(
                data: DataRGBA, // ここは data: Buffer, としても同じ動作になる.
                width: DATA_W,
                height: DATA_H,
                bitsPerComponent: 8,
                bytesPerRow: one_scan_bytes,
                space: CGColorSpaceCreateDeviceRGB(),
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
            )

            if TheCGContext != nil
            {
                let the_cgimage = TheCGContext!.makeImage()
                if let the_cgimage = the_cgimage
                {
                    TheUIImage = UIImage( cgImage: the_cgimage )
                }
                else
                {
                    // デバッグコンソールに出力する.
                    print( "the_cgimage is nil.")
                }
            }
            else
            {
                // デバッグコンソールに出力する.
                print( "the_cgcontext is nil." )
            }

        }
        .onDisappear {
            // メモリを破棄する.
            Buffer?.deallocate()
        }

    } // some View end.

    // 画像にデータを仕込むメソッド.
    func SetGradDataRGBA(
            _ data: UnsafeMutablePointer<STRUCT_RGBA>,
            _ width: Int,
            _ height: Int
            )->Void {

        let w = width
        let h = height

        var value: UInt8 = 0

        for j in 0 ..< h{
            var adrs = w * j
            for i in 0 ..< w{
                let tmp = i + j
                value = UInt8( tmp % 256 )
                data[ adrs ].R = 0x00;  // R
                data[ adrs ].G = 0x00;  // G
                data[ adrs ].B = value; // B
                data[ adrs ].A = 0xff;  // A
                adrs += 1
            }
        }

        return

    }

}

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

ボタンに直接ロジックが書いてあり、よくありません。また、同じようなコードを何度も書いているのが退屈で苦痛です。

改善後のコード ( 実践的な例 )

ボタンのアクションや外観を MyButton という独自のビューを定義して、それを使うことでボタンまわりのコードの共通化をはかっています。

もしボタンの外観を変えたい場合は MyButton の中のモディファイアを変更することで、すべてのボタンの外観をイッキに変更することが可能です。

import SwiftUI

struct BTN_ACTION {
    static let SCALE_MODE_NONE = 0x01
    static let SCALE_MODE_FIT  = 0x02
    static let SCALE_MODE_FILL = 0x04
    static let RATE_X10_Y10    = 0x10
    static let RATE_X05_Y20    = 0x20
    static let RATE_X20_Y05    = 0x40
}

struct BTN_TITLE {
    static let NONE    = "none"
    static let FIT     = "fit"
    static let FILL    = "fill"
    static let X10_Y10 = "x1.0 x1.0"
    static let X05_Y20 = "x0.5 x2.0"
    static let X20_Y05 = "x2.0 x0.5"
}

struct MY_CONST {
    static let SCALE_MODE_NONE = 1
    static let SCALE_MODE_FIT  = 2
    static let SCALE_MODE_FILL = 4

    static let STR_SCALE_MODE_NONE = "none"
    static let STR_SCALE_MODE_FIT  = "fit"
    static let STR_SCALE_MODE_FILL = "fill"
}

struct STRUCT_RGBA {
    var R: UInt8
    var G: UInt8
    var B: UInt8
    var A: UInt8
}

struct ContentView: View {

    // 画像データ.
    let DATA_W = 256
    let DATA_H = 256
    @State var Buffer: UnsafeMutableRawPointer?
    @State var DataRGBA: UnsafeMutablePointer<STRUCT_RGBA>?

    // 画像データを画面に表示するためのグッズ.
    @State var TheCGContext: CGContext? = nil
    @State var TheUIImage: UIImage? = nil

    // 画面に出力する際の縮尺.
    @State var RateX = 1.0
    @State var RateY = 1.0

    @State var FlagScale: Int
    @State var StrScaleMode: String

    @State var UiImgW: CGFloat = 0.0
    @State var UiImgH: CGFloat = 0.0

    // パスによる枠線描画の定数.
    let PATH_LINE_COLOR = Color.yellow
    let PATH_LINE_WIDTH = 2.0

    // 初期化コード.
    init (){
        FlagScale = MY_CONST.SCALE_MODE_NONE
        StrScaleMode = MY_CONST.STR_SCALE_MODE_NONE
    }

    var body: some View {

        ZStack{

            // 最背面は背景グレーで塗りつぶす.
            Rectangle()
                .foregroundColor( .gray )

            // 中層面で画像を表示する.
            if let TheUIImageUnwrapped = TheUIImage {

                let ui_img_w = CGFloat( DATA_W ) * RateX
                let ui_img_h = CGFloat( DATA_H ) * RateY

                if FlagScale == MY_CONST.SCALE_MODE_NONE {
                    // 等倍描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else if FlagScale == MY_CONST.SCALE_MODE_FIT {
                    // フィット描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .scaledToFit()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else if FlagScale == MY_CONST.SCALE_MODE_FILL {
                    // フィル描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .scaledToFill()
                        .frame( width: ui_img_w, height: ui_img_h )
                }
                else
                {
                    // 等倍描画する.
                    Image( uiImage: TheUIImageUnwrapped )
                        .resizable()
                        .frame( width: ui_img_w, height: ui_img_h )
                }

                // 画像を囲む枠線を描画する.
                Path{
                    path in

                    let xs = 0.0
                    let ys = 0.0
                    let xe = ui_img_w
                    let ye = ui_img_h

                    let pnt0 = CGPoint( x: xs, y: ys )
                    let pnt1 = CGPoint( x: xe, y: ys )
                    let pnt2 = CGPoint( x: xe, y: ye )
                    let pnt3 = CGPoint( x: xs, y: ye )

                    // 0, 1, 2, 3, 0 で矩形枠線を描画する.
                    path.move( to: pnt0 )
                    path.addLine(to: pnt1 )
                    path.addLine(to: pnt2 )
                    path.addLine(to: pnt3 )
                    path.addLine(to: pnt0 )
                }
                .stroke( PATH_LINE_COLOR, lineWidth: PATH_LINE_WIDTH )
                .frame( width: ui_img_w, height: ui_img_h )

                // 現在の描画モードを表示する.
                Text( String( format: "%@,%.1f*%.1f", StrScaleMode, ui_img_w, ui_img_h ) )
                    .foregroundColor( .white )
                    .font( .system( size: 12.0 ) )

            }
            else
            {
                // TheUIImage が適切に生成されていなければ何も表示しない.
                Spacer()
            }

            // 最前面の画面上部にデバッグ情報を表示する.
            VStack{

                let scr_w = UIScreen.main.bounds.size.width
                let scr_h = UIScreen.main.bounds.size.height

                Text( String( format: "ScreenWH is %.1f*%.1f", scr_w, scr_h ))
                    .foregroundColor( .white )
                Text( String( format: "DataWH is %d*%d", DATA_W, DATA_H ))
                    .foregroundColor( .white )

            }
            .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .top )
            .padding( .top )

            VStack{

                HStack{

                    // 描画サイズ変更リクエストボタン.
                    MyButton(
                        title: BTN_TITLE.X05_Y20,
                        action_type: BTN_ACTION.RATE_X05_Y20,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )


                    // 描画サイズ変更リクエストボタン.
                    MyButton(
                        title: BTN_TITLE.X20_Y05,
                        action_type: BTN_ACTION.RATE_X20_Y05,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )


                    // 描画サイズ変更リクエストボタン.
                    MyButton(
                        title: BTN_TITLE.X10_Y10,
                        action_type: BTN_ACTION.RATE_X10_Y10,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )

                }

                HStack {

                    // 等倍描画リクエストボタン.
                    MyButton(
                        title: MY_CONST.STR_SCALE_MODE_NONE,
                        action_type: BTN_ACTION.SCALE_MODE_NONE,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )

                    // フィット描画リクエストボタン.
                    MyButton(
                        title: MY_CONST.STR_SCALE_MODE_FIT,
                        action_type: BTN_ACTION.SCALE_MODE_FIT,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )

                    // フィル描画リクエストボタン.
                    MyButton(
                        title: MY_CONST.STR_SCALE_MODE_FILL,
                        action_type: BTN_ACTION.SCALE_MODE_FILL,
                        flag_scale: $FlagScale,
                        str_scale_mode: $StrScaleMode,
                        rate_x: $RateX,
                        rate_y: $RateY
                        )
                }

            }
            .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom )
            .padding( .bottom )

        } // ZStack end.
        .onAppear{

            // メモリを確保する.
            let numpix = DATA_W * DATA_H
            let size = MemoryLayout<STRUCT_RGBA>.stride * numpix
            let align = MemoryLayout<STRUCT_RGBA>.alignment
            Buffer = UnsafeMutableRawPointer.allocate( byteCount: size, alignment: align )

            // 連続メモリをRGBAの構造体の並びとして解釈する.
            DataRGBA = Buffer?.bindMemory( to: STRUCT_RGBA.self, capacity: numpix )

            // 確保したメモリに、ななめグラデーションデータを仕込む.
            SetGradDataRGBA( DataRGBA!, DATA_W, DATA_H )

            let one_scan_bytes = DATA_W * 4

            // ビットマップコンテキストを作成する.
            TheCGContext = CGContext(
                data: DataRGBA, // ここは data: Buffer, としても同じ動作になる.
                width: DATA_W,
                height: DATA_H,
                bitsPerComponent: 8,
                bytesPerRow: one_scan_bytes,
                space: CGColorSpaceCreateDeviceRGB(),
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
            )

            if TheCGContext != nil
            {
                let the_cgimage = TheCGContext!.makeImage()
                if let the_cgimage = the_cgimage
                {
                    TheUIImage = UIImage( cgImage: the_cgimage )
                }
                else
                {
                    // デバッグコンソールに出力する.
                    print( "the_cgimage is nil.")
                }
            }
            else
            {
                // デバッグコンソールに出力する.
                print( "the_cgcontext is nil." )
            }

        }
        .onDisappear {
            // メモリを破棄する.
            Buffer?.deallocate()
        }

    } // some View end.

    // 画像にデータを仕込むメソッド.
    func SetGradDataRGBA(
            _ data: UnsafeMutablePointer<STRUCT_RGBA>,
            _ width: Int,
            _ height: Int
            )->Void {

        let w = width
        let h = height

        var value: UInt8 = 0

        for j in 0 ..< h{
            var adrs = w * j
            for i in 0 ..< w{
                let tmp = i + j
                value = UInt8( tmp % 256 )
                data[ adrs ].R = 0x00;  // R
                data[ adrs ].G = 0x00;  // G
                data[ adrs ].B = value; // B
                data[ adrs ].A = 0xff;  // A
                adrs += 1
            }
        }

        return

    }

}

struct MyButton: View {
    let title: String
    let action_type: Int
    @Binding var flag_scale: Int
    @Binding var str_scale_mode: String
    @Binding var rate_x: Double
    @Binding var rate_y: Double

    var body: some View {

        Button( action: {

            if ( action_type == BTN_ACTION.SCALE_MODE_NONE )
            {
                flag_scale = MY_CONST.SCALE_MODE_NONE
                str_scale_mode = MY_CONST.STR_SCALE_MODE_NONE
            }
            else if ( action_type == BTN_ACTION.SCALE_MODE_FIT )
            {
                flag_scale = MY_CONST.SCALE_MODE_FIT
                str_scale_mode = MY_CONST.STR_SCALE_MODE_FIT
            }
            else if ( action_type == BTN_ACTION.SCALE_MODE_FILL )
            {
                flag_scale = MY_CONST.SCALE_MODE_FILL
                str_scale_mode = MY_CONST.STR_SCALE_MODE_FILL
            }
            else if ( action_type == BTN_ACTION.RATE_X10_Y10 )
            {
                rate_x = 1.0
                rate_y = 1.0
            }
            else if ( action_type == BTN_ACTION.RATE_X05_Y20 )
            {
                rate_x = 0.5
                rate_y = 2.0
            }
            else if ( action_type == BTN_ACTION.RATE_X20_Y05 )
            {
                rate_x = 2.0
                rate_y = 0.5
            }
            else
            {
                /* NOP */
            }

        })
        {
        Text( title )
            .padding( 10.0 )
            .foregroundColor( .white )
            .background( .blue )
        }
        .overlay(
            RoundedRectangle( cornerRadius: 5.0 )
                .stroke( .white, lineWidth: 1.0 )
        )

    }
}

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