SwiftでUIBezierPathを使い、簡単な図形を描画するアプリを作ってみた。①

#はじめに
当方、Swiftの勉強を始めて3週間の初心者です。
ずっとInputしているだけでは、あまり本当に理解していることにはならないな〜と思い、自分がやったことをOutputしてみたいと思います。

勉強のために簡単なアプリ「DotView」を作ってみたので、初投稿はその作り方を説明していきたいと思います。

#アプリの概要
- 円の中に正多角形が描かれている図形を、UIBezierPathを使って描画する。

完成図は以下の画像のようになります。
![Simulator Screen Shot - iPhone 8 Plus - 2018-09-26 at 18.16.19.png](https://qiita-image-store.s3.amazonaws.com/0/269046/59f42049-b3ab-a552-0766-9464025e6be2.png)

XとY：円の中心座標
Radius：円の半径
No.Vert：正多角形の頂点の数
Area：円の面積
がそれぞれ表示されます。

RadiusとNo.Vertは、スライダーで値を変更することができます。
また、それぞれのテキストフィールドに直接値を入力することで、図形を変更することもできます。

それでは作成していきます。

#図形を描画する領域を作成する。
円や多角形を実際に描画する領域を作成していきます。

まず、StoryBoardを開き、Object LibraryからView(UIView)を選択し、貼り付けます。
Constraintsは適度に調整します。自分はTop, Left, Rightは画面いっぱいにしました。

![スクリーンショット 2018-09-28 午後5.24.32.png](https://qiita-image-store.s3.amazonaws.com/0/269046/ca4b7a21-2c51-d3b4-cae2-bcc2483a12bc.png)

貼り付けれたら、このView上の描画を管理する.swiftファイルを作成し、UIViewからリファレンスを引いて関連づけます。
<img width=300 alt="スクリーンショット-2018-09-28-午後5.20.34.png.png" src="https://qiita-image-store.s3.amazonaws.com/0/269046/57315a7b-d26f-ce16-5764-ec53e0332e0d.png">

```ViewController.swift
@IBOutlet weak var nextView: TestDraw!
```
これで、図形を描画する領域が確保できました。

####注意
UIViewと関連づけるところで少しハマりました。
最初は

```ViewController.swift
let nextView = TestDraw()
```
というように書いていたのですが、これだとStoryboardに貼り付けられているUIViewとは全く異なる新しいインスタンスが生成されてしまい、何度やっても円が描画されない事態に陥ってしまいました。
Storyboard上で貼り付けたものは、きちんとStoryboardから関連づけるようにしましょう。

#各値を入力する部分を作成する。
円の中心座標や半径などを入力・表示するUITextFieldやUILabel、および半径と頂点の数を調整するUISliderを作成していきます。

- UITextFieldとUILabel

完成画像通りになるように、Storyboard上にUITextFieldとUILabelを貼り付けていきます。![スクリーンショット 2018-09-28 午後5.59.23.png](https://qiita-image-store.s3.amazonaws.com/0/269046/00ff9285-0450-bba7-6519-7b43fcf0f4a7.png)
貼り付けられたら、それぞれをプログラムと関連づけていきます。

```ViewController.swift
    @IBOutlet weak var xLabel: UILabel! // 「X」
    @IBOutlet weak var yLabel: UILabel! // 「Y」
    @IBOutlet weak var radiusLabel: UILabel! // 「Radius」
    @IBOutlet weak var areaLabel: UILabel! // 「Area」
    @IBOutlet weak var vertexLabel: UILabel! // 「No.Vert」
    @IBOutlet weak var areaValueLabel: UILabel! // 計算された円の面積を表示
```

- UISlider

RadiusとNo.Vertの値を設定するスライダーを作成します。
![スクリーンショット 2018-09-28 午後6.08.35.png](https://qiita-image-store.s3.amazonaws.com/0/269046/17080fa6-a8df-99b4-c245-c93fd911994f.png)
Sliderの値が変更された時はそれを反映しなければならないので、プログラムとはIBActionで繋ぎます。
その際、

```ViewController.swift
    @IBAction func UISlider(_ sender: Any) {
    }
```
となっている部分を

```ViewController.swift
    @IBAction func UISlider(_ sender: UISlider) {
    }
```
と変更してやります。
実際のコードは以下のようになります。

```ViewController.swift
    // radiusを設定するスライダー
    @IBAction func radiusSlider(_ sender: UISlider) {
    }
    
    // vertexを設定するスライダー
    @IBAction func vertexSlider(_ sender: UISlider) {
    }
```
これで、TextField, Label, Sliderをプログラムと関連づけることができました。

次は、実際にUIViewに図形を描画していきます。

#円および多角形を描画する部分を作成する。
円と多角形を描画する部分を、TestDraw.swiftに書いていきます。
初めに、コード全体の流れですが、

```TestDraw.swift
import UIKit

class TestDraw: UIView {
    // 中心座標、半径、頂点の数それぞれの初期値を設定

    override func draw(_ rect: CGRect) {
        // 実際に描画
    
    func drawCircle(radius: CGFloat, center: CGPoint){
        // 円を描画する関数
    }

    func drawRegularPolygon(_ p:Int, radius:CGFloat, center:CGPoint){
        // 正多角形を描画する関数
    }
```
という流れで作成します。

###初期値の設定
中心のX座標・Y座標・半径・多角形の頂点の数を設定します。
また円の面積を格納する変数も設定します。

```TestDraw.swift
    // 中心のx座標。ViewControllerのXtextからの値を格納する変数
    var x:CGFloat = 150 // 初期値
    {
        didSet {
            if oldValue != x {
                self.setNeedsDisplay()
            }
        }
    }
    // 中心のy座標。同じくyTextからの値を格納する変数
    var y:CGFloat = 100 // 初期値
    {
        didSet {
            if oldValue != y {
                self.setNeedsDisplay()
            }
        }
    }
    
    //円の半径
    var radius: CGFloat = 50
    {
        didSet {
            if oldValue != radius {
                self.setNeedsDisplay()
            }
        }
    }
    
    //正多角形の頂点の数
    var vertex: Int = 3
    {
        didSet {
            if oldValue != vertex {
                self.setNeedsDisplay()
            }
        }
    }
    
    var area: CGFloat?
```



```TestDraw.swift
var x:CGFloat = 150 // 初期値
    {
        didSet { // var xが変更された直後に呼び出される
            if oldValue != x { // xが前の値と異なっていたら
                self.setNeedsDisplay()  // 画面を更新
            }
        }
    }
```
のように設定することで、それぞれの値が変更されると同時に、図形が再描画されるようになります。

####注意
座標設定に使われるX, Y座標は、**UIViewの左上端点を(0, 0)として**定義されています。
これからも何度か座標設定の場面が出てきますので、覚えておいてください。

###実際に描画
円と正多角形を、UIViewに実際に描画する部分を作成します。

```TestDraw.swift
    override func draw(_ rect: CGRect) {
        // Drawing code
        // 円の描画
        let center = CGPoint(x: x,y: y) // 円の中心座標を設定
        self.drawCircle(radius: radius,center: center) // 半径と中心を設定し、円を描画
        // 正多角形の描画
        self.drawRegularPolygon(vertex, radius: radius, center: center) // 頂点の数・外接円の半径・中心を設定し多角形を描画
    }
```
以下、drawCircleとdrawRegularPolygonを書いていきます。

###円の描画関数を作成(drawCircle)
引数として、
**円の半径（radius）：**CGFloat型
**円の中心座標（center）：**CGPoint型
を渡します。
なぜCGFloat, CGPointで渡しているかというと、UIBezierPathの引数がこの二種類の型で設定されているからです。
また、この段階で面積も計算しておきます。

```TestDraw.swift
    // 円の描画関数
    func drawCircle(radius: CGFloat, center: CGPoint){

        // 円の形を設定
        let circle = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(Double.pi*2.0*0.0/360.0), endAngle: CGFloat(Double.pi*2.0*360.0/360.0), clockwise: true)
        let circleColor = UIColor.red // 色を赤に設定
        circleColor.setStroke() // 線の色
        circleColor.setFill() // 内側塗りつぶし色
        circle.fill() // 内側塗り潰し
        circle.lineWidth = 4 // 線の幅
        circle.stroke() // 描画
        area = (radius * radius) * CGFloat(Double.pi) // 面積の計算
    }
```
円の形状を決める、

```swift

UIBezierPath(arcCenter: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)
```
の各引数について解説すると、

**arcCenter：**円の中心座標。CGFloat型の値により作られる、CGPoint型の値を入れる。
**radius：**円の半径。
**startAngle, endAngle：**円弧の始まりの位置と終わりの位置。3時の方向を0とするラジアンで表される。
**clockwise：**trueだと時計回り、falseだと反時計回りに円弧が描画される。

となっています。

###正多角形の描画関数を作成(drawRegularPolygon)
引数として、
**頂点の数（p）：**Int型
**外接円の半径（radius）：**CGFloat型
**外接円の中心座標（center）：**CGPoint型
を渡します。

```TestDraw.swift
    // 正多角形の描画関数
    func drawRegularPolygon(_ p:Int, radius:CGFloat, center:CGPoint){
        let line = UIBezierPath() // インスタンス生成
        var pt:[CGPoint]=[] // 頂点の座標を格納する配列
        for i in 0 ..< p {
            let rad: CGFloat = CGFloat(Double.pi * Double(i) * 2.0 / Double(p) + Double.pi / 2.0)
            let point = CGPoint(x: center.x + radius * cos(rad), y: center.y - radius * sin(rad))
            pt.append(point)
        }
        
        line.move(to: pt[0]) // 起点
        for j in 1..<p {
            line.addLine(to: pt[j]) // 帰着点
        }
        line.close() // ラインを結ぶ
        UIColor.yellow.setStroke() // 色の設定
        line.lineWidth = 4 // ライン幅
        line.stroke() // 描画
    }
```

以下、少しわかりにくいと思う部分を見ていきます。

```swift
        for i in 0 ..< p {
            let rad: CGFloat = CGFloat(Double.pi * Double(i) * 2.0 / Double(p) + Double.pi / 2.0)
            let point = CGPoint(x: center.x + radius * cos(rad), y: center.y - radius * sin(rad))
            pt.append(point)
        }
```
ここでは、正多角形の各頂点の座標を設定しています。
π/2ラジアンの点を起点とし、そこから頂点の数だけ等分された角度ごとに座標を計算していきます。
例えば三角形の場合、各頂点の座標は以下の画像のように計算できます。
![vertexPicture.png](https://qiita-image-store.s3.amazonaws.com/0/269046/a0a30ee7-dfe2-d316-1b58-0c8ab3389618.png)

そして、計算した座標をCGPoint型にし、順次リストに追加していきます。

```swift
        line.move(to: pt[0]) // 起点
        for j in 1..<p {
            line.addLine(to: pt[j]) // 帰着点
        }
```
ここでは、設定した頂点間で、線を引く順番を指定しています。
pt[0]を最初の線の開始点とし、pt[1]に向け直線を引くよう指定します。
続けて、pt[1]からpt[2]、pt[2]からpt[3]...というように線の開始点・終着点を指定しています。
addlineが呼び出されると、**自動的に前回の帰着点を開始点に更新する**ため、最初の点さえ設定してやれば、あとは帰着点を設定するだけで良いのです。

---

長くなりましたので、今回はここまでにします。
次回は、TextFieldやSliderで入力した値を、UIViewに反映する部分を作ります。

#参考文献
[[iPhone] iOSアプリで図形を描画するにはUIBezierPathを使う](https://i-app-tec.com/ios/uibezierpath.html)