クリアメモリ

プログラミングやモーショングラフィックス、便利なアプリケーションなど雑多に記録するブログ

【Swift】addArcで半円や正円を描画する方法

f:id:clrmemory:20170422185005p:plain 

今回はCocoaProgrammingでSwiftを使った「正円」や「半円」、それ以外の中途半端な弓形をビュー上に描画する方法を紹介します。

 

「addEllipse」だと正円や楕円しか描画できなかったのですが、addArcを使うことで270°の弓形なども描画できるようになるので参考にしてみてください。

 

 

はじめに

 

今回紹介する方法は「CocoaProgramming」で動作を確認しており、使用している言語はSwiftです。また、Xcodeを使用しているため、その他の言語やツールでは正しく機能しないので注意してください。

 

ではまず実際の動作を確認しましょう。

 

 

このように、正円だけではなく「270°」の弓形を描画したり、開始位置と終了位置を繋いだメーターのような図形を描画できるようになります。弓形というのは、270°のような正円でも半円でもない、弓のような形をしている円のことを言います。

 

 

ではまず、アプリケーションに表示するボタンやカスタムビューを設置していきましょう。今回は「xib」で作成してみました。

 

xibを作成

 

今回作成したアプリケーションは、以下のようなオブジェクトで構成されています。

 

 

左から順に「正円」「235°」の弓形、「270°」の弓形となっています。また、右側の270°の弓形は「円弧の開始点」と「終了点」を中心点と繋いだ「おうぎ形」を描画するようにしてみました。

 

 

これらのカスタムビューは「AppDelegate」とアウトレット接続されており、ボタンはまとめてアクション接続させてあります。

 

ではxibファイルが完成したので、続いて実際に図形を描画するコードを記述していきましょう。

 

プログラムの流れ

 

今回作成するプログラムは以下のような手順で行っていきます。

 

・円弧を描画するクラスを追加

・ボタンを押した時の処理を記述

・ボタンのタイトルを判定して処理を分ける

・作成したクラスを元にカスタムビューにビューを追加

・ボタンごとに描画される図形が違うことを確認

 

ボタンが押された時の処理

 

ボタンが押された時、どのボタンが押されたかによってそれぞれ処理を切り替えます。

今回の場合だと、以下のように記述することでそれぞれのカスタムビュー2秒ができるので確認してください。

 

@IBAction func pushButton(_ sender: NSButton) {
    switch sender.title {
    case "360°":
        let views = PerfectCircle(frame: NSRect(x: 10, y: 10, width: circleCustomView.bounds.width - 10, height: circleCustomView.bounds.height - 10))
        circleCustomView.addSubview(views)

        break
    case "235°":
        let views = arcCircle(frame: NSRect(x: 10, y: 10, width: circleCustomView2.bounds.width-10, height: circleCustomView2.bounds.height-10))
        circleCustomView2.addSubview(views)
        break
    case "270°":
        let views = fanCircle(frame: NSRect(x: 10, y: 10, width: circleCustomView3.bounds.width-10, height: circleCustomView3.bounds.height-10))
        circleCustomView3.addSubview(views)
        break
    default:
        break
    }
}

 

このようなコードを追加しました。

※この段階ではクラスが存在しないためエラーがでます。

 

ではまず、正円を描画するクラスを作成しましょう。

 

正円を描画

 

先日紹介した「addEllipse」を使えば、addArcよりも簡単に図形を描画できるのですが、こちらは正円や楕円を描画するためのコードでした。

 

詳しくは以下を参考にしてください。

 

https://clrmemory.com/swift/cocoa-nsgraphicscontext-draw/

 

今回は、より細かい違いを確認するため、正円の描画にもaddArcを使用しますが、正円を描画したい時は可読性も上がるためaddEllipseを使うと良いかと思います。

 

 

ではaddArcを使った正円を描画するクラスを確認してください。

 

class PerfectCircle: NSView{
    override func draw(_ dirtyRect: NSRect) {

        let context = NSGraphicsContext.current()?.cgContext
        let circleCenter = NSPoint(x: dirtyRect.width/2, y: dirtyRect.height/2)
        let circleRadius = dirtyRect.width/2 - 10
        let circleStartAngle = CGFloat(degreeToRadian(angle: 0))
        let circleEndAngle = CGFloat(degreeToRadian(angle: 360))
        let circleClockwise = true

        NSColor.blue.set()
        context?.setLineWidth(5.0)
        context?.addArc(center: circleCenter, radius: circleRadius, startAngle:circleStartAngle, endAngle: circleEndAngle, clockwise: circleClockwise)

        context?.strokePath()
    }
    func degreeToRadian(angle: Int) -> Double {

        let radian = Double(-angle - 90) * M_PI/180
        return radian

    }
}

 

順番に解説していきます。

 

context

 

まず、contextですが、こちらはNSGraphicsContextを使うための変数で、このcontextに描画する座標や色などを設定していきます。

 

circleCenter

 

circleCenterでは、描画する円の中心座標を設定しており、dirtyRectで受け取った範囲の中心を設定してあります。

 

circleRadius

 

読んで字のごとくですが、円の半径を設定しています。

今回の場合は、ビューの横幅の半分から10を引いて、余白をつけてみました。

 

Angle

 

circleStartAngleとcircleEndAngleはそれぞれ、円弧の開始点と終了点です。

これらは対比の関係にあるため一緒に解説しますが、どちらもCGFloatで設定してください。

 

 

今回は「degreeToRadian」というメソッドを作成し、円の中心角度をラジアンに変換させました。ラジアンの計算方法は「radian = 角度 * π / 180」です。

 

  • 90 としているのは、通常時だと、開始点が時計でいう「3時」の点になってしまうためで、90をプラスしておくことで「12時」を開始点にできます。

 

 

開始円を0、終了点を350°に設定して、90をプラスした場合とそうでない場合を比較してみてください。

 

+90した場合:

 

しなかった場合:

 

正円の場合はどちらも関係ないのですが、せっかくaddArcを使うので覚えておきましょう。

 

また、後述する正円以外を描画する時には詳しく解説せず、ここでまとめてしまうので注意してください。

 

circleClockwise

 

circleClockwiseでは、円が「時計回りかどうか」を設定しています。

ここでいう時計回りというのは、開始点から終了点まで描画する時にどちらから進むかです。

 

先ほど、開始点0で終了点350に設定した時はほぼ正円が完成しましたが、こちらのclockwiseをfalseに変更した時はどのようになるのか確認してください。

 

 

このように先ほど描画された範囲と逆になりましたね。

ここからも分かる通り、trueで時計回り、falseで反時計回りで設定できます。

 

NSColor.blue.set()

 

こちらは、addEllipseの記事でも紹介したのですが「パスに色を設定」するためのコードです。

 

今回の場合は「青い円弧」を描画したかったため「NSColor.blue.set( )」になりました。同様に、黒や赤などの色もセットすることができます。

 

context?.setLineWidth

 

こちらはそのままの意味で、線の太さを設定できます。

 

値はFloat型で設定でき、値を増やすことで線も太くなっていきます。

今回の場合はstrokeで描画したのでsetLineWidthを使用しましたが、塗りつぶしの図形を描画する際はこの記述は不要です。

 

context?.addArc

 

いよいよメインの「addArc」です。

こちらは、幾つかの引数を取ることができ、以下のように設定します。

addArc( 中心座標, 半径, 開始点, 終了点, 時計回りかどうか )

 

ここまで紹介してきたそれぞれの変数を割り当てることで、円弧が描画できるようになりました。では正円が描画できたか確認してみましょう。

 

 

このようになりましたか。

ここまでくれば、あとは角度を変えて円を描画するだけです。一気に見ていきましょう。

 

235°の円を描画

 

ここまででコードの解説は大方終了しているので、先に235°の弓形を描画するためのコードを確認してください。

 

class arcCircle: NSView{
    override func draw(_ dirtyRect: NSRect) {
        let context = NSGraphicsContext.current()?.cgContext
        let circleCenter = NSPoint(x: dirtyRect.width/2, y: dirtyRect.height/2)
        let circleRadius = dirtyRect.width/2 - 10
        let circleStartAngle = CGFloat(degreeToRadian(angle: 0))
        let circleEndAngle = CGFloat(degreeToRadian(angle: 235))
        let circleClockwise = true

        NSColor.blue.set()
        context?.setLineWidth(5.0)
        context?.addArc(center: circleCenter, radius: circleRadius, startAngle:circleStartAngle, endAngle: circleEndAngle, clockwise: circleClockwise)
        context?.closePath()
        context?.strokePath()

    }
    func degreeToRadian(angle: Int) -> Double {

        let radian = Double(-angle+90) * M_PI/180
        return radian
    }
}

 

235°の円弧を描画するにはこのような記述をします。

 

先ほどは登場していない「closePath( )」という記述が増えている点に着目してください。このような記述を追加することで「開始点」と「終了点」を繋ぐ直線を描画できるようになります。

 

ですので、開始点から終了点までの直線を描画したい時はclosePathを使うことで、わざわざ座標を取得する必要はなく、簡単にパスを閉じることができます。

 

 

最後におうぎ形(扇形)を描画する方法です。

 

おうぎ形を描画する

 

おうぎ形を描画する方法は、先ほどの235°の円を描画した時と似ているのですが、ここに一つ手順を加えます。2通りの方法で実装できるのですが、どちらも中心点をポイントに設定することで描画できます。

 

以下を確認してください。

 

class fanCircle: NSView{

    override func draw(_ dirtyRect: NSRect) {
        let context = NSGraphicsContext.current()?.cgContext
        let circleCenter = NSPoint(x: dirtyRect.width/2, y: dirtyRect.height/2)
        let circleRadius = dirtyRect.width/2 - 10
        let circleStartAngle = CGFloat(degreeToRadian(angle: 0))
        let circleEndAngle = CGFloat(degreeToRadian(angle: 270))
        let circleClockwise = true

        NSColor.blue.set()
        context?.setLineWidth(5.0)
        context?.addArc(center: circleCenter, radius: circleRadius, startAngle:circleStartAngle, endAngle: circleEndAngle, clockwise: circleClockwise)
        context?.closePath()
        context?.strokePath()
        Swift.print(circleEndAngle)

    }
    func degreeToRadian(angle: Int) -> Double {

        let radian = Double(-angle+90) * M_PI/180
        return radian

    }
}

 

こちらは先ほどの235°のコードをそのまま270°に書き換えただけなので、以下のようになります。

 

 

このような図形をおうぎ形にする場合、2通りの方法があると紹介しました。

まずは「中心点と円弧をつなぐ」方法から紹介します。

 

中心点と円弧をつなぐ

 

先ほどのコードでaddArcの前に以下のような記述を追加してください。

 

context?.move(to: circleCenter) 

 

このコードは描画開始の座標を設定するもので、円の中心からaddArcで指定した弧を通る図形を描画するという意味合いになります。

closePathが記述されているため、円弧の終了点から中心点を繋ぐ直線が描画されます。

 

 

ちなみにclosePathを記述しないと以下のようになります。

 

  

2つ目の方法は、円弧に中心点へつなぐ直線を追加する方法です。

 

円弧と中心点をつなぐ

 

addArcの後ろに以下の記述を追加してください。

 

context?.addLine(to: circleCenter)

 

先ほどの場合とは違い、addLineは「直線を追加する」コードになっています。moveと似ていますが全然違うコードなので覚えておきましょう。

 

実行結果は先ほどと同じなので割愛します。

 

まとめ

 

今回紹介した「addArc」を使うことで、ビュー上におうぎ形や弓形のような図形を描画できるようになりました。

 

moveやclosePath、addLineはaddArc以外の図形を描画する時にも登場するコードなので、ぜひ覚えておきましょう。

 

ではまた。

新着記事