如何在iPhone上绘制“语音泡泡”?

我试图得到一个类似于Mac OS X中的“语音气泡”效果,当你点击在docker上的东西。 这是我现在拥有的:

替代文字

我需要得到较低部分的“三angular形”部分。 有什么办法可以画出这样的东西,并得到一个边界吗? 这将是一个iPhone应用程序。

提前致谢!

编辑:非常感谢Brad Larson,现在看起来像这样: 替代文字

实际上,我已经绘制了这个确切的形状(底部有一个指向三angular形的圆angular矩形)。 我用的Quartz绘图代码如下:

CGRect currentFrame = self.bounds; CGContextSetLineJoin(context, kCGLineJoinRound); CGContextSetLineWidth(context, strokeWidth); CGContextSetStrokeColorWithColor(context, [MyPopupLayer popupBorderColor]); CGContextSetFillColorWithColor(context, [MyPopupLayer popupBackgroundColor]); // Draw and fill the bubble CGContextBeginPath(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 fill CGContextBeginPath(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); 

如果您不打算使用渐变或其他比简单颜色更复杂的填充,最后的剪切path可以省略。

也许一个更简单的问题是“是否有代码已经为我做了”,答案是“是”。

请看MAAttachedWindow

替代文字

当然,你可能不希望整个“附加窗口”的行为,但至less绘图代码已经在那里。 (而马特Gemmell的代码是高品质的东西)

Swift 2创buildUIBezierPath的代码:

 var borderWidth : CGFloat = 4 // Should be less or equal to the `radius` property var radius : CGFloat = 10 var triangleHeight : CGFloat = 15 private 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 } 

现在,你可以做任何你想要的path。 例如与CAShapeLayer一起使用:

 let bubbleLayer = CAShapeLayer() bubbleLayer.path = bubblePathForContentSize(contentView.bounds.size).CGPath bubbleLayer.fillColor = fillColor.CGColor bubbleLayer.strokeColor = borderColor.CGColor bubbleLayer.lineWidth = borderWidth bubbleLayer.position = CGPoint.zero myView.layer.addSublayer(bubbleLayer) 

在这里输入图像说明

有两种方法可以实现这一点:

  1. 在正确的地方添加一个带有三angular形图像的UIImageView 。 确保图像的其余部分是透明的,以免遮挡背景。
  2. 重写UIView上的drawRect:方法来自定义绘制视图。 然后,您可以为三angular形添加线性path组件,根据需要填充path。

要用drawRect:绘制一个简单的三angular形,你可以做这样的事情。 这段代码会在视图底部绘制一个向下的三angular形。

 // Get the context CGContextRef context = UIGraphicsGetCurrentContext(); // Pick colors CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]); CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]); // Define triangle dimensions CGFloat baseWidth = 30.0; CGFloat height = 20.0; // Define path CGContextMoveToPoint(context, self.bounds.size.width / 2.0 - baseWidth / 2.0, self.bounds.size.height - height); CGContextAddLineToPoint(context, self.bounds.size.width / 2.0 + baseWidth / 2.0, self.bounds.size.height - height); CGContextAddLineToPoint(context, self.bounds.size.width / 2.0, self.bounds.size.height); // Finalize and draw using path CGContextClosePath(context); CGContextStrokePath(context); 

有关更多信息,请参阅CGContext参考 。

我在这里寻找解决scheme,在现有的视图中绘制“箭头”。
我很高兴与你分享一些我希望有用的代码 – Swift 2.3兼容

 public extension UIView { public enum PeakSide: Int { case Top case Left case Right case Bottom } public func addPikeOnView(side side: PeakSide, size: CGFloat = 10.0) { self.layoutIfNeeded() let peakLayer = CAShapeLayer() var path: CGPathRef? switch side { case .Top: path = self.makePeakPathWithRect(self.bounds, topSize: size, rightSize: 0.0, bottomSize: 0.0, leftSize: 0.0) case .Left: path = self.makePeakPathWithRect(self.bounds, topSize: 0.0, rightSize: 0.0, bottomSize: 0.0, leftSize: size) case .Right: path = self.makePeakPathWithRect(self.bounds, topSize: 0.0, rightSize: size, bottomSize: 0.0, leftSize: 0.0) case .Bottom: path = self.makePeakPathWithRect(self.bounds, topSize: 0.0, rightSize: 0.0, bottomSize: size, leftSize: 0.0) } peakLayer.path = path let color = (self.backgroundColor ?? .clearColor()).CGColor peakLayer.fillColor = color peakLayer.strokeColor = color peakLayer.lineWidth = 1 peakLayer.position = CGPoint.zero self.layer.insertSublayer(peakLayer, atIndex: 0) } func makePeakPathWithRect(rect: CGRect, topSize ts: CGFloat, rightSize rs: CGFloat, bottomSize bs: CGFloat, leftSize ls: CGFloat) -> CGPathRef { // P3 // / \ // P1 -------- P2 P4 -------- P5 // | | // | | // P16 P6 // / \ // P15 P7 // \ / // P14 P8 // | | // | | // P13 ------ P12 P10 -------- P9 // \ / // P11 let centerX = rect.width / 2 let centerY = rect.height / 2 var h: CGFloat = 0 let path = CGPathCreateMutable() var points: [CGPoint] = [] // P1 points.append(CGPointMake(rect.origin.x, rect.origin.y)) // Points for top side if ts > 0 { h = ts * sqrt(3.0) / 2 let x = rect.origin.x + centerX let y = rect.origin.y points.append(CGPointMake(x - ts, y)) points.append(CGPointMake(x, y - h)) points.append(CGPointMake(x + ts, y)) } // P5 points.append(CGPointMake(rect.origin.x + rect.width, rect.origin.y)) // Points for right side if rs > 0 { h = rs * sqrt(3.0) / 2 let x = rect.origin.x + rect.width let y = rect.origin.y + centerY points.append(CGPointMake(x, y - rs)) points.append(CGPointMake(x + h, y)) points.append(CGPointMake(x, y + rs)) } // P9 points.append(CGPointMake(rect.origin.x + rect.width, rect.origin.y + rect.height)) // Point for bottom side if bs > 0 { h = bs * sqrt(3.0) / 2 let x = rect.origin.x + centerX let y = rect.origin.y + rect.height points.append(CGPointMake(x + bs, y)) points.append(CGPointMake(x, y + h)) points.append(CGPointMake(x - bs, y)) } // P13 points.append(CGPointMake(rect.origin.x, rect.origin.y + rect.height)) // Point for left side if ls > 0 { h = ls * sqrt(3.0) / 2 let x = rect.origin.x let y = rect.origin.y + centerY points.append(CGPointMake(x, y + ls)) points.append(CGPointMake(x - h, y)) points.append(CGPointMake(x, y - ls)) } let startPoint = points.removeFirst() self.startPath(path: path, onPoint: startPoint) for point in points { self.addPoint(point, toPath: path) } self.addPoint(startPoint, toPath: path) return path } private func startPath(path path: CGMutablePath, onPoint point: CGPoint) { CGPathMoveToPoint(path, nil, point.x, point.y) } private func addPoint(point: CGPoint, toPath path: CGMutablePath) { CGPathAddLineToPoint(path, nil, point.x, point.y) } } 

通过这种方式,您可以为每种视图调用它:

 let view = UIView(frame: frame) view.addPikeOnView(side: .Top) 

在未来我会增加抵消派克的位置。

  • 是的,名字肯定是可以改变的!

SWIFT 3版本

 public extension UIView { public enum PeakSide: Int { case Top case Left case Right case Bottom } public func addPikeOnView( side: PeakSide, size: CGFloat = 10.0) { self.layoutIfNeeded() let peakLayer = CAShapeLayer() var path: CGPath? switch side { case .Top: path = self.makePeakPathWithRect(rect: self.bounds, topSize: size, rightSize: 0.0, bottomSize: 0.0, leftSize: 0.0) case .Left: path = self.makePeakPathWithRect(rect: self.bounds, topSize: 0.0, rightSize: 0.0, bottomSize: 0.0, leftSize: size) case .Right: path = self.makePeakPathWithRect(rect: self.bounds, topSize: 0.0, rightSize: size, bottomSize: 0.0, leftSize: 0.0) case .Bottom: path = self.makePeakPathWithRect(rect: self.bounds, topSize: 0.0, rightSize: 0.0, bottomSize: size, leftSize: 0.0) } peakLayer.path = path let color = (self.backgroundColor?.cgColor) peakLayer.fillColor = color peakLayer.strokeColor = color peakLayer.lineWidth = 1 peakLayer.position = CGPoint.zero self.layer.insertSublayer(peakLayer, at: 0) } func makePeakPathWithRect(rect: CGRect, topSize ts: CGFloat, rightSize rs: CGFloat, bottomSize bs: CGFloat, leftSize ls: CGFloat) -> CGPath { // P3 // / \ // P1 -------- P2 P4 -------- P5 // | | // | | // P16 P6 // / \ // P15 P7 // \ / // P14 P8 // | | // | | // P13 ------ P12 P10 -------- P9 // \ / // P11 let centerX = rect.width / 2 let centerY = rect.height / 2 var h: CGFloat = 0 let path = CGMutablePath() var points: [CGPoint] = [] // P1 points.append(CGPoint(x:rect.origin.x,y: rect.origin.y)) // Points for top side if ts > 0 { h = ts * sqrt(3.0) / 2 let x = rect.origin.x + centerX let y = rect.origin.y points.append(CGPoint(x:x - ts,y: y)) points.append(CGPoint(x:x,y: y - h)) points.append(CGPoint(x:x + ts,y: y)) } // P5 points.append(CGPoint(x:rect.origin.x + rect.width,y: rect.origin.y)) // Points for right side if rs > 0 { h = rs * sqrt(3.0) / 2 let x = rect.origin.x + rect.width let y = rect.origin.y + centerY points.append(CGPoint(x:x,y: y - rs)) points.append(CGPoint(x:x + h,y: y)) points.append(CGPoint(x:x,y: y + rs)) } // P9 points.append(CGPoint(x:rect.origin.x + rect.width,y: rect.origin.y + rect.height)) // Point for bottom side if bs > 0 { h = bs * sqrt(3.0) / 2 let x = rect.origin.x + centerX let y = rect.origin.y + rect.height points.append(CGPoint(x:x + bs,y: y)) points.append(CGPoint(x:x,y: y + h)) points.append(CGPoint(x:x - bs,y: y)) } // P13 points.append(CGPoint(x:rect.origin.x, y: rect.origin.y + rect.height)) // Point for left sidey: if ls > 0 { h = ls * sqrt(3.0) / 2 let x = rect.origin.x let y = rect.origin.y + centerY points.append(CGPoint(x:x,y: y + ls)) points.append(CGPoint(x:x - h,y: y)) points.append(CGPoint(x:x,y: y - ls)) } let startPoint = points.removeFirst() self.startPath(path: path, onPoint: startPoint) for point in points { self.addPoint(point: point, toPath: path) } self.addPoint(point: startPoint, toPath: path) return path } private func startPath( path: CGMutablePath, onPoint point: CGPoint) { path.move(to: CGPoint(x: point.x, y: point.y)) } private func addPoint(point: CGPoint, toPath path: CGMutablePath) { path.addLine(to: CGPoint(x: point.x, y: point.y)) } } 

对于那些使用基于Brad Larson的答案的swift 2.0

 override func drawRect(rect: CGRect) { super.drawRect(rect) // optional if a direct UIView-subclass, should be called otherwise. let HEIGHTOFPOPUPTRIANGLE:CGFloat = 20.0 let WIDTHOFPOPUPTRIANGLE:CGFloat = 40.0 let borderRadius:CGFloat = 8.0 let strokeWidth:CGFloat = 3.0 // Get the context let context: CGContextRef = UIGraphicsGetCurrentContext()! CGContextTranslateCTM(context, 0.0, self.bounds.size.height) CGContextScaleCTM(context, 1.0, -1.0) // let currentFrame: CGRect = self.bounds CGContextSetLineJoin(context, CGLineJoin.Round) CGContextSetLineWidth(context, strokeWidth) CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor) CGContextSetFillColorWithColor(context, UIColor.blackColor().CGColor) // Draw and fill the bubble CGContextBeginPath(context) CGContextMoveToPoint(context, borderRadius + strokeWidth + 0.5, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5) CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0 - WIDTHOFPOPUPTRIANGLE / 2.0) + 0.5, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5) CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0) + 0.5, strokeWidth + 0.5) CGContextAddLineToPoint(context, round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) + 0.5, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5) CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5, currentFrame.size.width - strokeWidth - 0.5, currentFrame.size.height - strokeWidth - 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5, currentFrame.size.height - strokeWidth - 0.5, round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) - strokeWidth + 0.5, currentFrame.size.height - strokeWidth - 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, strokeWidth + 0.5, currentFrame.size.height - strokeWidth - 0.5, strokeWidth + 0.5, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, strokeWidth + 0.5, strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5, currentFrame.size.width - strokeWidth - 0.5, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5, borderRadius - strokeWidth) CGContextClosePath(context) CGContextDrawPath(context, CGPathDrawingMode.FillStroke) // Draw a clipping path for the fill CGContextBeginPath(context) CGContextMoveToPoint(context, borderRadius + strokeWidth + 0.5, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5) CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5, currentFrame.size.width - strokeWidth - 0.5, currentFrame.size.height - strokeWidth - 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, currentFrame.size.width - strokeWidth - 0.5, currentFrame.size.height - strokeWidth - 0.5, round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) - strokeWidth + 0.5, currentFrame.size.height - strokeWidth - 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, strokeWidth + 0.5, currentFrame.size.height - strokeWidth - 0.5, strokeWidth + 0.5, HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5, borderRadius - strokeWidth) CGContextAddArcToPoint(context, strokeWidth + 0.5, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5, currentFrame.size.width - strokeWidth - 0.5, round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5, borderRadius - strokeWidth) CGContextClosePath(context) CGContextClip(context) } 

看到下面的图片popup菜单上的三angular形,这是用Core Graphics funcs绘制,是完全可扩展的。

替代文字

这样做做等边三angular形(老学校function名称,对不起):

 #define triH(v) (v * 0.866) func(CGContextRef inContext, CGRect arrowRect, CustomPushButtonData* controlData) { // Draw the triangle float arrowXstart, arrowYstart; float arrowXpos, arrowYpos, arrowHpos; if (controlData->controlEnabled && controlData->controlActive) { CGContextSetRGBFillColor(inContext, 0., 0., 0., 1.); } else { CGContextSetRGBFillColor(inContext, 0., 0., 0., 0.5); } arrowHpos = triH(arrowRect.size.height); // Point C CGContextBeginPath(inContext); arrowXstart = arrowXpos = (arrowRect.origin.x + ((float)(arrowRect.size.width / 2.) - (arrowSize / 2.))); arrowYstart = arrowYpos = (arrowRect.origin.y + (float)((arrowRect.size.height / 2.) - (float)(arrowHpos / 2.))); CGContextMoveToPoint(inContext, arrowXpos, arrowYpos); // Point A arrowXpos += arrowSize; CGContextAddLineToPoint(inContext, arrowXpos, arrowYpos); // Point B arrowYpos += arrowHpos; arrowXpos -= (float)(arrowSize / 2.0); CGContextAddLineToPoint(inContext, arrowXpos, arrowYpos); // Point C CGContextAddLineToPoint(inContext, arrowXstart, arrowYstart); CGContextClosePath(inContext); CGContextFillPath(inContext); 

}

请注意,triH(x)func是计算等边三angular形高度的优化公式,例如h = 1/2 * sqrt(3)* x。 由于1/2 * sqrt(3)永远不会改变,所以我将其优化为该定义。

如果有人前来寻找Swift 3的答案,这是不可能的! 感谢那些在我之前做出贡献的人,可爱的一段代码!

  let rRect = CGRect(x: start.x, y: start.y, width: defaultHeightWidth.0, height: defaultHeightWidth.1) context?.translateBy(x: 0, y: rRect.size.height - 3) context?.scaleBy(x: 1.0, y: -1.0) context?.setLineJoin(.bevel) context?.setLineWidth(strokeWidth) context?.setStrokeColor(UIColor.black.cgColor) context?.setFillColor(UIColor.white.cgColor) // draw and fill the bubble context?.beginPath() context?.move(to: CGPoint(x: borderRadius + strokeWidth + 0.5, y: strokeWidth + triangleHeight + 0.5)) context?.addLine(to: CGPoint(x: round(rRect.size.width / 2.0 - triangleWidth / 2.0) + 0.5, y: triangleHeight + strokeWidth + 0.5)) context?.addLine(to: CGPoint(x: round(rRect.size.width / 2.0) + 0.5, y: strokeWidth + 0.5)) context?.addLine(to: CGPoint(x: round(rRect.size.width / 2.0 + triangleWidth / 2.0), y: triangleHeight + strokeWidth + 0.5)) context?.addArc(tangent1End: CGPoint(x: rRect.size.width - strokeWidth - 0.5, y: strokeWidth + triangleHeight + 0.5), tangent2End: CGPoint(x: rRect.size.width - strokeWidth - 0.5, y: rRect.size.height - strokeWidth - 0.5), radius: borderRadius - strokeWidth) context?.addArc(tangent1End: CGPoint(x: rRect.size.width - strokeWidth - 0.5, y: rRect.size.height - strokeWidth - 0.5), tangent2End: CGPoint(x: round(rRect.size.width / 2.0 + triangleWidth / 2.0) - strokeWidth + 0.5, y: rRect.size.height - strokeWidth - 0.5), radius: borderRadius - strokeWidth) context?.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y: rRect.size.height - strokeWidth - 0.5), tangent2End: CGPoint(x: strokeWidth + 0.5, y: triangleHeight + strokeWidth + 0.5), radius: borderRadius - strokeWidth) context?.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y: strokeWidth + triangleHeight + 0.5), tangent2End: CGPoint(x: rRect.size.width - strokeWidth - 0.5, y: triangleHeight + strokeWidth + 0.5), radius: borderRadius - strokeWidth) context?.closePath() context?.drawPath(using: .fillStroke) 

在我的情况下, triangleWidth = 10triangleHeight = 5比OPs版本中的视图小得多。

这是Brad Larson的快速解决scheme

 override func draw(_ rect: CGRect) { super.draw(rect) // optional if a direct UIView-subclass, should be called otherwise. let HEIGHTOFPOPUPTRIANGLE:CGFloat = 20.0 let WIDTHOFPOPUPTRIANGLE:CGFloat = 40.0 let borderRadius:CGFloat = 8.0 let strokeWidth:CGFloat = 3.0 // Get the context let context: CGContext = UIGraphicsGetCurrentContext()! context.translateBy(x: 0.0, y: self.bounds.size.height) context.scaleBy(x: 1.0, y: -1.0) // let currentFrame: CGRect = self.bounds context.setLineJoin(CGLineJoin.round) context.setLineWidth(strokeWidth) context.setStrokeColor(UIColor.white.cgColor) context.setFillColor(UIColor.black.cgColor) // Draw and fill the bubble context.beginPath() context.move(to: CGPoint(x: borderRadius + strokeWidth + 0.5, y: strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5)) context.addLine(to: CGPoint(x: round(currentFrame.size.width / 2.0 - WIDTHOFPOPUPTRIANGLE / 2.0) + 0.5, y: HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5)) context.addLine(to: CGPoint(x: round(currentFrame.size.width / 2.0) + 0.5, y: strokeWidth + 0.5)) context.addLine(to: CGPoint(x: round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) + 0.5, y: HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5)) context.addArc(tangent1End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5), tangent2End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: currentFrame.size.height - strokeWidth - 0.5), radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: currentFrame.size.height - strokeWidth - 0.5) , tangent2End: CGPoint(x: round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) - strokeWidth + 0.5, y: currentFrame.size.height - strokeWidth - 0.5) , radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y: currentFrame.size.height - strokeWidth - 0.5), tangent2End: CGPoint(x: strokeWidth + 0.5, y: HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5), radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y :strokeWidth + HEIGHTOFPOPUPTRIANGLE + 0.5), tangent2End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5 ,y: HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5), radius: borderRadius - strokeWidth) context.closePath() context.drawPath(using: CGPathDrawingMode.fillStroke) // Draw a clipping path for the fill context.beginPath() context.move(to: CGPoint(x: borderRadius + strokeWidth + 0.5, y: round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5)) context.addArc(tangent1End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5), tangent2End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: currentFrame.size.height - strokeWidth - 0.5), radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: currentFrame.size.height - strokeWidth - 0.5) , tangent2End: CGPoint(x: round(currentFrame.size.width / 2.0 + WIDTHOFPOPUPTRIANGLE / 2.0) - strokeWidth + 0.5, y: currentFrame.size.height - strokeWidth - 0.5), radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y: currentFrame.size.height - strokeWidth - 0.5), tangent2End: CGPoint(x: strokeWidth + 0.5, y: HEIGHTOFPOPUPTRIANGLE + strokeWidth + 0.5), radius: borderRadius - strokeWidth) context.addArc(tangent1End: CGPoint(x: strokeWidth + 0.5, y: round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5), tangent2End: CGPoint(x: currentFrame.size.width - strokeWidth - 0.5, y: round((currentFrame.size.height + HEIGHTOFPOPUPTRIANGLE) * 0.50) + 0.5), radius: borderRadius - strokeWidth) context.closePath() context.clip() } 

Swift 4更新

这是AVT原始代码的Swift 4版本。

  private func bubblePathForContentSize(contentSize: CGSize) -> UIBezierPath { let rect = CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height)).offsetBy(dx: radius, dy: radius + triangleHeight) let path = UIBezierPath(); let radius2 = radius - borderWidth / 2 // Radius adjasted for the border width path.move(to: CGPoint(x: rect.maxX - triangleHeight * 2, y: rect.minY - radius2)) path.addLine(to: CGPoint(x: rect.maxX - triangleHeight, y: rect.minY - radius2 - triangleHeight)) path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.minY), radius: radius2, startAngle: CGFloat(-(Double.pi/2)), endAngle: 0, clockwise: true) path.addArc(withCenter: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius2, startAngle: 0, endAngle: CGFloat(Double.pi/2), clockwise: true) path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.maxY), radius: radius2, startAngle: CGFloat(Double.pi/2),endAngle: CGFloat(Double.pi), clockwise: true) path.addArc(withCenter: CGPoint(x: rect.minX, y: rect.minY), radius: radius2, startAngle: CGFloat(Double.pi), endAngle: CGFloat(-(Double.pi/2)), clockwise: true) path.close() return path } //Example usage: let bubbleLayer = CAShapeLayer() bubbleLayer.path = bubblePathForContentSize(contentView.bounds.size).CGPath bubbleLayer.fillColor = fillColor.CGColor bubbleLayer.strokeColor = borderColor.CGColor bubbleLayer.lineWidth = borderWidth bubbleLayer.position = CGPoint.zero myView.layer.addSublayer(bubbleLayer) 

我可能会在Photoshop中制作整个图像(包括三angular形),然后在适当的时候使用以下命令将其显示在屏幕上:

 CGRect myRect = CGRectMake(10.0f, 0.0f, 300.0f, 420.0f); UIImageView *myImage = [[UIImageView alloc] initWithFrame:myRect]; [myImage setImage:[UIImage imageNamed:@"ThisIsMyImageName.png"]]; myImage.opaque = YES; [self.view addSubview:myImage]; [myImage release];