ドックで何かを右クリックすると、Mac OS Xのような「吹き出し」効果を取得しようとしています。私が今持っているものは次のとおりです。
下部の「三角形」部分を取得する必要があります。そのようなものを描き、その周りに境界線を得る方法はありますか?これはiPhoneアプリ用です。
前もって感謝します!
編集:Brad Larsonに感謝します、今のようになります:
私は実際にこの正確な形状を描画しました(下にポインティング三角形のある丸い長方形)。使用した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);
グラデーションや、単純な色よりも複雑な塗りつぶしを使用しない場合は、最後のクリッピングパスを省略できます。
UIBezierPathを作成するSwift 2コード:
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
}
これで、このパスで何でもできます。たとえば、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)
おそらく、より単純な質問は、「既にこれを行うコードはありますか」であり、その答えは「はい」です。
見よ MAAttachedWindow
:
確かに、「アタッチされたウィンドウ」の動作全体が必要なわけではありませんが、少なくとも描画コードはすでにそこにあります。 (そして、Matt Gemmellのコードは高品質のものです)
ここでは、既存のビューに「矢印」を描画するソリューションを探しています。
便利なコードをいくつかご紹介します。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))
}
}
これを達成できる可能性がある2つの方法があります。
drawRect:
メソッドをオーバーライドして、ビューをカスタム描画します。その後、必要に応じてパスを塗りつぶし、境界線の三角形に線形パスコンポーネントを追加できます。drawRect:
を使用して単純な三角形を描くには、次のようなことをします。このスニペットは、ビューの下部に下向きの三角形を描きます。
// 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リファレンス を参照してください。
Swift 2.0 Brad Larson
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)
}
以下の画像のポップアップメニューの三角形を参照してください。これはCore Graphicsファンクで描画され、完全にスケーラブルです。
このようにして正三角形を作成します(旧式の関数名、申し訳ありません):
#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は、正三角形の高さを計算するための最適化された式であることに注意してください。 h = 1/2 * sqrt(3)* x 1/2 * sqrt(3)は変更されないため、その定義に最適化しました。
Swift 4アップデート
Swift 4バージョンの AVTの元のコード です。
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)
Swift 3の Brad Larson の解
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 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 = 10
およびtriangleHeight = 5
OPバージョンのビューよりもはるかに小さいビュー。