構造体配列による信号波形を画面いっぱいに描画する

この記事は下記の記事の発展形です。

構造体配列による信号波形を描画する

1次元の信号波形を画面に描画する方法を紹介します。

上記の記事は画面の中の規定の範囲で波形を描画しておりますが、実際のところは iPhone の画面サイズぎりぎりで波形を表示したくなるものです。

Fig. 1 スマホ縦位置のシンク関数
Fig. 2 スマホ縦位置のサイン関数
Fig. 3 スマホ縦位置のコサイン関数
Fig. 4 スマホ横位置のシンク関数
Fig. 5 スマホ横位置のサイン関数
Fig. 6 スマホ横位置のコサイン関数

iPhone は機種によって画面サイズが違うので ZStack が占めるサイズを動的に取得できないと画面いっぱいに波形を表示できません。こういった場合は GeometryReader という仕組みをつかいます。

取得したい画面内の要素を GeometryReader で囲んでやることで、そのサイズを取得できます。本記事では ZStack が占める領域を動的に取得したいので、ZStack の外側を GeometryReader で囲みます。

17行目にユーザが命名した変数 geom に ZStack が占めるサイズが入っています。それの geom.size.width と geom.size.height を参照すれば、ZStatck が占める幅と高さを取得できます。

26、27行目で、ZStack が占めるサイズを動的に取得しています。
32、62、82、106行目で、ZStack が占めるサイズを .frame モディファイアで指定しています。

import SwiftUI

struct X_FX {
    var  x: Double
    var fx: Double
}

struct ContentView: View {

    let NUM_DATA = 1000
    @State var Data: [X_FX] = []

    var body: some View {

        GeometryReader {

            geom in

            ZStack{

                // 全体背景の矩形を描画する.
                Rectangle()
                    .foregroundColor( Color.mint )

                // 画面のうち ZStack が占めるサイズ.
                let RectW = geom.size.width
                let RectH = geom.size.height

                // 波形描画の背景の矩形を描画する.
                Rectangle()
                    .foregroundColor( Color.black )
                    .frame( width: RectW, height: RectH )

                // 波形を描画する.
                Path{ path in

                    let amp = RectH * 0.5
                    let off_y = RectH * 0.5

                    if ( Data.count > 0 )
                    {

                        var plot_x = Double(0)
                        var plot_y = -( Data[0].fx ) * amp + off_y
                        var pnt = CGPoint( x: plot_x, y: plot_y )
                        path.move( to: pnt )

                        let loop = Int( RectW )
                        for i in 1 ..< loop {
                            let rate = Double(i)/Double( RectW - 1 )
                            let n = Int( rate * Double( Data.count - 1 ))
                            plot_x = Double(i)
                            plot_y = -( Data[n].fx ) * amp  + off_y
                            pnt = CGPoint( x: plot_x, y: plot_y )
                            path.addLine( to: pnt )
                        }

                    }

                }
                .stroke( .green, lineWidth: 2.0 )
                .frame( width: RectW, height: RectH ) // ここが重要.

                // 目安になるセンター十字線を描画する.
                Path{ path in

                    let plot_x = RectW * 0.5
                    let plot_y = RectH * 0.5

                    let pnt00 = CGPoint( x: plot_x, y:     0 )
                    let pnt01 = CGPoint( x: plot_x, y: RectH )
                    path.move( to: pnt00 )
                    path.addLine( to: pnt01 )

                    let pnt10 = CGPoint( x:     0, y: plot_y )
                    let pnt11 = CGPoint( x: RectW, y: plot_y )
                    path.move( to: pnt10 )
                    path.addLine( to: pnt11 )

                }
                .stroke( .white, lineWidth: 1.0 )
                .frame( width: RectW, height: RectH ) // ここが重要.

                // ZStack を占める Rectangle を囲む枠線を描画する.
                Path{
                    path in

                    let xs = 0.0
                    let ys = 0.0
                    let xe = RectW
                    let ye = RectH

                    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( .pink, lineWidth: 3.0 )
                .frame( width: RectW, height: RectH ) // ここが重要.

                // データをセットするボタンを水平に並べる.
                HStack{

                    // シンク関数をセットするボタン.
                    Button( action: {
                        let _ = SetDataSinc( &Data, NUM_DATA )
                    }){
                        Text( "sinc" )
                            .foregroundColor( .white )
                    }
                    .padding()
                    .background( .blue )

                    // サイン関数をセットするボタン.
                    Button( action: {
                        let _ = SetDataSin( &Data, NUM_DATA )
                    }){
                        Text( "sin" )
                            .foregroundColor( .white )
                    }
                    .padding()
                    .background( .blue )

                    // コサイン関数データをセットするボタン.
                    Button( action: {
                        let _ = SetDataCos( &Data, NUM_DATA )
                    }){
                        Text( "cos" )
                            .foregroundColor( .white )
                    }
                    .padding()
                    .background( .blue )

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

            }
            .onAppear(){
                // 起動時にはシンク関数を描画する.
                SetDataSinc( &Data, NUM_DATA )
            }

        }

    }

    func SetDataSinc( _ data: inout [X_FX], _ req_num_data: Int )->Void {

        // いったん配列をクリアする.
        data.removeAll()

        let RANGE = 2.0
        let MUL = 2.0

        for n in 0 ..< req_num_data {

            let rate = Double(n)/Double( req_num_data - 1)
            var v = RANGE * rate
            v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.

            let t = v * ( 2.0 * ( Double.pi )) * MUL

            var ft = 0.0

            // ゼロ割チェック.
            if ( t != 0.0 )
            {
                ft = sin( t ) / t
            }
            else
            {
                ft = 1.0
            }

            // データ要素を追加する.
            let element = X_FX( x: t, fx: ft )
            data.append( element )

        }

    }

    func SetDataSin( _ data: inout [X_FX], _ req_num_data: Int )->Void {

        // いったん配列をクリアする.
        data.removeAll()

        let RANGE = 2.0
        let MUL = 2.0

        for n in 0 ..< req_num_data {

            let rate = Double(n)/Double( req_num_data - 1)
            var v = RANGE * rate
            v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.

            let t = v * ( 2.0 * ( Double.pi )) * MUL
            let ft = sin( t )

            // データ要素を追加する.
            let element = X_FX( x: t, fx: ft )
            data.append( element )

        }

    }

    func SetDataCos( _ data: inout [X_FX], _ req_num_data: Int )->Void {

        // いったん配列をクリアする.
        data.removeAll()

        let RANGE = 2.0
        let MUL = 2.0

        for n in 0 ..< req_num_data {

            let rate = Double(n)/Double( req_num_data - 1)
            var v = RANGE * rate
            v -= 1.0 // この時点で -1.0 〜 0.0 〜 +1.0 になる.

            let t = v * ( 2.0 * ( Double.pi )) * MUL
            let ft = cos( t )

            // データ要素を追加する.
            let element = X_FX( x: t, fx: ft )
            data.append( element )

        }

    }

}

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