How to draw a "speech bubble" on an iPhone? How to draw a "speech bubble" on an iPhone? ios ios

How to draw a "speech bubble" on an iPhone?


I've actually drawn this exact shape before (rounded rectangle with a pointing triangle at the bottom). The Quartz drawing code that I used is as follows:

CGRect currentFrame = self.bounds;CGContextSetLineJoin(context, kCGLineJoinRound);CGContextSetLineWidth(context, strokeWidth);CGContextSetStrokeColorWithColor(context, [MyPopupLayer popupBorderColor]); CGContextSetFillColorWithColor(context, [MyPopupLayer popupBackgroundColor]);// Draw and fill the bubbleCGContextBeginPath(context);CGContextMoveToPoint(context, borderRadius + strokeWidth + 0.5f, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5f);CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0f - WIDTHOFPOPUPTRIANGLE / 2.0f) + 0.5f, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5f);CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0f) + 0.5f, strokeWidth + 0.5f);CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0f + WIDTHOFPOPUPTRIANGLE / 2.0f) + 0.5f, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5f);CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5f, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5f, currentFrame.size.width - strokeWidth - 0.5f, currentFrame.size.height - strokeWidth - 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5f, currentFrame.size.height - strokeWidth - 0.5f, round(currentFrame.size.width / 2.0f + WIDTHOFPOPUPTRIANGLE / 2.0f) - strokeWidth + 0.5f, currentFrame.size.height - strokeWidth - 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, strokeWidth + 0.5f, currentFrame.size.height - strokeWidth - 0.5f, strokeWidth + 0.5f, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, strokeWidth + 0.5f, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5f, currentFrame.size.width - strokeWidth - 0.5f, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5f, borderRadius - strokeWidth);CGContextClosePath(context);CGContextDrawPath(context, kCGPathFillStroke);// Draw a clipping path for the fillCGContextBeginPath(context);CGContextMoveToPoint(context, borderRadius + strokeWidth + 0.5f, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50f) + 0.5f);CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5f, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50f) + 0.5f, currentFrame.size.width - strokeWidth - 0.5f, currentFrame.size.height - strokeWidth - 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5f, currentFrame.size.height - strokeWidth - 0.5f, round(currentFrame.size.width / 2.0f + WIDTHOFPOPUPTRIANGLE / 2.0f) - strokeWidth + 0.5f, currentFrame.size.height - strokeWidth - 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, strokeWidth + 0.5f, currentFrame.size.height - strokeWidth - 0.5f, strokeWidth + 0.5f, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5f, borderRadius - strokeWidth);CGContextAddArcToPoint(context, strokeWidth + 0.5f, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50f) + 0.5f, currentFrame.size.width - strokeWidth - 0.5f, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50f) + 0.5f, borderRadius - strokeWidth);CGContextClosePath(context);CGContextClip(context);     

The clipping path at the end can be left out if you're not going to use a gradient or some other more fill that's more complex than a simple color.


Swift 2 code that creates UIBezierPath:

var borderWidth : CGFloat = 4 // Should be less or equal to the `radius` propertyvar radius : CGFloat = 10var triangleHeight : CGFloat = 15private func bubblePathForContentSize(contentSize: CGSize) -> UIBezierPath {    let rect = CGRectMake(0, 0, contentSize.width, contentSize.height).offsetBy(dx: radius, dy: radius + triangleHeight)    let path = UIBezierPath();    let radius2 = radius - borderWidth / 2 // Radius adjasted for the border width    path.moveToPoint(CGPointMake(rect.maxX - triangleHeight * 2, rect.minY - radius2))    path.addLineToPoint(CGPointMake(rect.maxX - triangleHeight, rect.minY - radius2 - triangleHeight))    path.addArcWithCenter(CGPointMake(rect.maxX, rect.minY), radius: radius2, startAngle: CGFloat(-M_PI_2), endAngle: 0, clockwise: true)    path.addArcWithCenter(CGPointMake(rect.maxX, rect.maxY), radius: radius2, startAngle: 0, endAngle: CGFloat(M_PI_2), clockwise: true)    path.addArcWithCenter(CGPointMake(rect.minX, rect.maxY), radius: radius2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI), clockwise: true)    path.addArcWithCenter(CGPointMake(rect.minX, rect.minY), radius: radius2, startAngle: CGFloat(M_PI), endAngle: CGFloat(-M_PI_2), clockwise: true)    path.closePath()    return path}

Now you could do whatever you want with this path. For example use it with CAShapeLayer:

let bubbleLayer = CAShapeLayer()bubbleLayer.path = bubblePathForContentSize(contentView.bounds.size).CGPathbubbleLayer.fillColor = fillColor.CGColorbubbleLayer.strokeColor = borderColor.CGColorbubbleLayer.lineWidth = borderWidthbubbleLayer.position = CGPoint.zeromyView.layer.addSublayer(bubbleLayer)

enter image description here


Perhaps a simpler question is "Is there code that does this for me already", to which the answer is "Yes".

Behold MAAttachedWindow:

alt text

Granted, you may not want the whole "Attached window" behavior, but at least the drawing code is already there. (And Matt Gemmell's code is high quality stuff)