UIBezierPath でパスを回転する方法をメモ。

UIBezierPath は UIKit に定義された CoreGraphics のラッパークラス。 このメモでは半円を描画して90度回転する。

目次

  1. 検証環境
  2. 回転する処理をどこに追加するか
  3. 回転する処理
  4. サンプル

検証環境

  • macOS 10.12.5
  • Xcode 8.3.2
  • Swift 3.1

回転処理をどこに追加するか

基本的な描画フロー で例えると『2. パスを描画する』の『3. 描画』の前に回転する処理を追加する。

let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
    bezierPath.close()

// ここ

bezierPath.stroke()
bezierPath.fill()

回転する処理

apply(_ transform:)CGAffineTransform(rotationAngle:) を使用する。ただしこれだけだと正しく回転しない。

// 90度回転する
bezierPath.apply(CGAffineTransform(rotationAngle: (90.0 * CGFloat.pi / 180.0)))
bezierPath.stroke()

we need to translate the context back to its original place

Saving a rotated image with CGContextRotateCTM | Treehouse Community

中心点が {0, 0} なので、CGAffineTransform(translationX:) を使用して中心点を移動する。

// 中心点を移動する
bezierPath.apply(CGAffineTransform(translationX: center.x, y: center.y).inverted())
// 90度回転する
bezierPath.apply(CGAffineTransform(rotationAngle: (90.0 * CGFloat.pi / 180.0)))
// 中心点を元に戻す
bezierPath.apply(CGAffineTransform(translationX: center.x, y: center.y))
bezierPath.stroke()

回転した後に中心点を元に戻してからパスを追加する必要がある。

サンプル

override func draw(_ rect: CGRect) {
    let lineWidth: CGFloat = 1.0
    let diameter = min(self.bounds.width, self.bounds.height) - lineWidth * 2 + lineWidth
    let radius = diameter / 2
    let center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2)
    let startAngle = -90.0 * CGFloat.pi / 180.0
    let endAngle = startAngle + CGFloat.pi

    let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
    bezierPath.close()

    bezierPath.apply(CGAffineTransform(translationX: center.x, y: center.y).inverted())
    bezierPath.apply(CGAffineTransform(rotationAngle: (90.0 * CGFloat.pi / 180.0)))
    bezierPath.apply(CGAffineTransform(translationX: center.x, y: center.y))

    bezierPath.lineWidth = lineWidth
    UIColor.black.setStroke()
    UIColor.gray.setFill()
    bezierPath.stroke()
    bezierPath.fill()
}

Previous Post