ZStackでいろいろな図形を描画する

図形の描画は色々ありますが、多くの場合、Rectangle、RoundedRectangle、Ellipse、Circle、Pathによる線描画、で用が済みます。Path を除くほとんどの図形は rotationEffect というモディファイアがあり、回転角度を指定できます。単位は弧度ラジアンと角度デグリーで指定できます。

Fig. 1 傾き +0.78 ラジアン

スライダを使って、回転角度を変化させた様子が下記の図です。-1.57 ラジアンは -90 度(-90デグリー)、-0.78 ラジアンは -45 度、+0.78 ラジアンは +45 度、+1.57 ラジアンは +90 度を意味します。

Fig. 2 傾き -1.57 ラジアン
Fig. 3 傾き -0.78 ラジアン
Fig. 4 傾き 0.00 ラジアン
Fig. 5 傾き +0.78 ラジアン
Fig. 6 傾き +1.57 ラジアン

コードの解説をします。

13行目、ZStack のサイズを取得したいので GeometryReader で囲みます。
27〜29行目、ZStack の領域の幅の半分、高さの半分のサイズで図形を描画します。
57〜59行目、Circle に .frame で違った幅と高さを指定しても、どちらか小さいほうのサイズが直径に割り当てられます。

import SwiftUI

struct ContentView: View {

    let COLOR_BG = Color( red: 0.0, green: 0.0, blue: 0.25, opacity: 1.0 )
    let LINE_WIDTH = 2.0
    let LINE_WIDTH_THIN = 1.0

    @State var RotValue = 0.0

    var body: some View {

        GeometryReader {

            geom in

            ZStack{

                let geom_w = geom.size.width
                let geom_h = geom.size.height

                // セーフエリアを除いた背景を塗りつぶす.
                Rectangle()
                    .frame( width: geom_w, height: geom_h )
                    .foregroundColor( COLOR_BG )

                let RATE = 0.5
                let rect_w = geom_w * RATE
                let rect_h = geom_h * RATE

                // 矩形を描画する.
                Rectangle()
                    .stroke( .yellow, lineWidth: LINE_WIDTH )
                    .frame( width: rect_w, height: rect_h )

                // 楕円を描画する.
                Ellipse()
                    .stroke( .green, lineWidth: LINE_WIDTH )
                    .frame( width: rect_w, height: rect_h )
                    .rotationEffect( .degrees( 0.0 ) )

                // 角丸矩形を描画する(回転も含む).
                RoundedRectangle( cornerRadius: 40.0 )
                    .stroke( .cyan, lineWidth: LINE_WIDTH )
                    .frame( width: rect_w, height: rect_h )
                    .rotationEffect( .radians( RotValue ) ) // radian で回転させる場合.
                    //.rotationEffect( .degrees( RotValue ) ) // degee で回転させる場合.

                // 楕円を描画する(回転も含む).
                Ellipse()
                    .stroke( .orange, lineWidth: LINE_WIDTH )
                    .frame( width: rect_w, height: rect_h )
                    .rotationEffect( .radians( RotValue ) ) // radian で回転させる場合.
                    //.rotationEffect( .degrees( RotValue ) ) // degee で回転させる場合.

                // 真円を描画する.
                Circle()
                    .stroke( .pink, lineWidth: LINE_WIDTH )
                    .frame( width: rect_w, height: rect_h )

                // めやすの描画中心の十字線を描画する.
                Path {
                    path in
                    let center_x = rect_w * 0.5
                    let center_y = rect_h * 0.5
                    path.move( to: CGPoint( x: center_x, y: 0.0 ) )
                    path.addLine( to: CGPoint( x: center_x, y: rect_h ) )
                    path.move( to: CGPoint( x: 0.0, y: center_y ) )
                    path.addLine( to: CGPoint( x: rect_w, y: center_y ) )
                }
                .stroke( .brown, lineWidth:  LINE_WIDTH_THIN )
                .frame( width: rect_w, height: rect_h )

                // 各種サイズ情報を表示するビュー.
                MyTextShowZstacWHRateRectWH(
                    geometry_w: geom_w,
                    geometry_h: geom_h,
                    rate_wh: RATE,
                    rectangle_w: rect_w,
                    rectangle_h: rect_h
                    )

                // 下部に配置されるスライダとその現在値をテキストで表示するビュー.
                MyTextAndSliderAlignBottom( value: $RotValue )

            }

        }

    }

}

struct MyTextShowZstacWHRateRectWH: View {

    var geometry_w: Double
    var geometry_h: Double
    var rate_wh: Double
    var rectangle_w: Double
    var rectangle_h: Double

    var body: some View {

        VStack{
            Text( String( format: "zstack: %.1f * %.1f", geometry_w, geometry_h ) )
                .foregroundColor( .white )
            Text( String( format: "Rate: %.2f", rate_wh ) )
                .foregroundColor( .white )
            Text( String( format: "rect: %.1f * %.1f", rectangle_w, rectangle_h ) )
                .foregroundColor( .white )
        }

    }

}

struct MyTextAndSliderAlignBottom: View {

    @Binding var value: Double

    var body: some View {

        // radian で管理する場合.
        let ROT_MIN = -2.0 * Double.pi
        let ROT_MAX = +2.0 * Double.pi
        let ROT_TICKS = 0.01

        // degree  で管理する場合.
//        let ROT_MIN = -360.0
//        let ROT_MAX = +360.0
//        let ROT_TICKS = 1.0

        VStack
        {

            // radian と degree で表示の単位を変える.
            let FMT = "%.2f rad"
//            let FMT = "%.2f deg"

            Text( String( format: FMT, value ) )
                .foregroundColor( .white )

            Slider(
                value: $value,
                in: ROT_MIN...ROT_MAX,
                step: ROT_TICKS
                )

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

    }

}


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