吹き出しのようなViewを作ってみる

下の画像の吹き出しのようなViewを作ってみます

<img width="375" alt="Simulator Screen Shot 2017.01.14 16.11.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/92771/3bfd6f8d-3102-6d50-08ca-8104e6c25e2d.png">

## レンダリング用のProtocolを定義

吹き出しだけではなく、その他のレンダリングするような処理とも共通化したいので先にProtocolを定義しておきます

```swift
// レンダリングのProtocol
protocol Renderer {
    associatedtype OptionType

    static func render(in context: CGContext, rect: CGRect, option: OptionType)
}

// パスに沿うレンダリングのProtocol
protocol PathRenderer: Renderer {
    static func path(rect: CGRect, option: OptionType) -> CGPath
}

```

## 吹き出し用のRenderer

先程のPathRendererに準拠して吹き出し用のRendererを作っていきます

```swift
struct BalloonRenderer: PathRenderer {
    typealias OptionType = Option

    struct Option {
        var cornerRadius: CGFloat
        var fillColor: UIColor
        var borderWidth: CGFloat
        var borderColor: UIColor

        static var `default`: Option {
            return Option(cornerRadius: 6,
                          fillColor: .lightGray,
                          borderWidth: 1.5,
                          borderColor: .orange)
        }
    }

    struct Triangle {
        let width: CGFloat = 9.0
        let height: CGFloat = 12.0
        let yOffset: CGFloat = 54.0
    }

    private static let triangle = Triangle()

    static func path(rect: CGRect, option: OptionType) -> CGPath {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: option.cornerRadius))
        path.addArc(withCenter: CGPoint(x: option.cornerRadius, y: option.cornerRadius),
                    radius: option.cornerRadius,
                    startAngle: CGFloat(-M_PI), endAngle: CGFloat(-M_PI_2),
                    clockwise: true)
        path.addLine(to: CGPoint(x: rect.width - option.cornerRadius - triangle.width, y: 0))
        path.addArc(withCenter: CGPoint(x: rect.width - option.cornerRadius - triangle.width, y: option.cornerRadius),
                    radius: option.cornerRadius,
                    startAngle: CGFloat(-M_PI_2), endAngle: 0,
                    clockwise: true)
        path.addLine(to: CGPoint(x: rect.width - triangle.width, y: triangle.yOffset))
        path.addLine(to: CGPoint(x: rect.width, y: triangle.height/2 + triangle.yOffset))
        path.addLine(to: CGPoint(x: rect.width - triangle.width, y: triangle.height + triangle.yOffset))
        path.addLine(to: CGPoint(x: rect.width - triangle.width, y: rect.height - option.cornerRadius))
        path.addArc(withCenter: CGPoint(x: rect.width - option.cornerRadius - triangle.width, y: rect.height - option.cornerRadius),
                    radius: option.cornerRadius,
                    startAngle: 0, endAngle: CGFloat(M_PI_2),
                    clockwise: true)
        path.addLine(to: CGPoint(x: option.cornerRadius, y: rect.height))
        path.addArc(withCenter: CGPoint(x: option.cornerRadius, y: rect.height - option.cornerRadius),
                    radius: option.cornerRadius,
                    startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI),
                    clockwise: true)
        path.close()
        return path.cgPath
    }

    static func render(in context: CGContext, rect: CGRect, option: OptionType) {
        let path = self.path(rect: rect, option: option)

        context.saveGState()
        context.setFillColor(UIColor.clear.cgColor)
        context.fill(rect)

        context.beginPath()
        context.addPath(path)
        context.setFillColor(option.fillColor.cgColor)
        context.fillPath()

        if option.borderWidth > 0.0 {
            context.beginPath()
            context.addPath(path)
            context.setLineWidth(option.borderWidth)
            context.setStrokeColor(option.borderColor.cgColor)
            context.strokePath()
        }
        
        context.restoreGState()
    }
    
}
```

## 吹き出しView

先程のBalloonRendererを使って実際にViewを作っていきます

```swift
class BalloonView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    func commonInit() {
        self.backgroundColor = .clear
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        let option = BalloonRenderer.Option.default
        BalloonRenderer.render(in: context, rect: rect, option: option)
    }
}
```

## おわり

こんな感じでサクっと書けます。また、Renderのプロトコルに準拠して角丸Viewとかも定義しておくと便利だと思います