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

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

Simulator Screen Shot 2017.01.14 16.11.26.png

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

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

// レンダリングの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を作っていきます

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を作っていきます

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とかも定義しておくと便利だと思います


『 Swift 』Article List