如何让气泡上的箭头一直到达包围矩形

问题描述 投票:0回答:1

因此,我创建了一个演示项目来回答这里的问题,关于如何绘制侧面带有圆角矩形作为指针的气泡形状(有点像聊天气泡。)

代码创建这样的形状:

它使用

CGMutablePath
方法
addArc(tangent1End:tangent2End:radius:transform:)
来绘制指针矩形的圆角。

我在形状图层的边界周围添加了边框,以便您可以看到形状的限制。

理想情况下,左侧三角形的圆角应一直延伸到边界的左边缘。我不知道如何计算圆角与三角形圆角尖端的距离。

如果我知道如何计算圆角被切除的量,我可以将形状的整个左边缘移动该量,并使圆角向右移动到边界矩形的左边缘。

这是生成路径的代码:

var path: CGPath  {
    let cgPath = CGMutablePath()
    
    cgPath.move(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height/2 + triangleHeight/2))
    cgPath.addArc(center: CGPoint(x: triangleWidth + radius, y: shapeLayer.bounds.height - radius),
                  radius: radius,
                  startAngle: CGFloat.pi,
                  endAngle: CGFloat.pi / 2,
                  clockwise: true,
                  transform: .identity)
    
    cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: shapeLayer.bounds.height - radius),
                  radius: radius,
                  startAngle: CGFloat.pi / 2,
                  endAngle: 0,
                  clockwise: true,
                  transform: .identity)
    cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: radius),
                  radius: radius,
                  startAngle: 0,
                  endAngle: 3 * CGFloat.pi / 2,
                  clockwise: true,
                  transform: .identity)
    
    cgPath.addArc(center: CGPoint(x: triangleWidth + radius, y: radius),
                  radius: radius,
                  startAngle: 3 * CGFloat.pi / 2,
                  endAngle: CGFloat.pi,
                  clockwise: true,
                  transform: .identity)
    cgPath.addLine(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 - triangleHeight / 2))
    
    cgPath.addArc(tangent1End: CGPoint(x: 0, y: shapeLayer.bounds.height / 2), tangent2End: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 + triangleHeight / 2), radius: triangleRadius)
    cgPath.addLine(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 + triangleHeight / 2))

    return cgPath
}

(最后一条弧线是创建矩形圆角的弧线。)

我想要一个适用于任何三角形高度、宽度和角半径的通用解决方案,但我无法找出可以让我计算三角形被剪切量的三角函数。

这是全班同学:

//
//  CustomBubbleView.swift
//  CustomBubbleView
//
//  Created by Duncan Champney on 11/21/23.
//

import UIKit
import CoreGraphics

class CustomBubbleView: UIView {
    
    let radius: CGFloat = 10.0
    let triangleRadius = 5.0
    let triangleWidth: CGFloat = 30
    let triangleHeight: CGFloat = 30


    var shapeLayer = CAShapeLayer()
    
    override var bounds: CGRect {
        didSet {
            print("In didSet")
            shapeLayer.frame = bounds
//            shapeLayer.frame = bounds.insetBy(dx: 10, dy: 10)
            shapeLayer.path = path
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        // Custom initialization
        backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1)
        layer.borderWidth = 1
        layer.borderColor = UIColor.blue.cgColor
        layer.addSublayer(shapeLayer)
        shapeLayer.fillColor = UIColor.white.cgColor
        shapeLayer.strokeColor = UIColor.lightGray.cgColor
        shapeLayer.lineWidth = 2
    }
    
    var path: CGPath  {
        let cgPath = CGMutablePath()
        
        cgPath.move(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height/2 + triangleHeight/2))
        cgPath.addArc(center: CGPoint(x: triangleWidth + radius, y: shapeLayer.bounds.height - radius),
                      radius: radius,
                      startAngle: CGFloat.pi,
                      endAngle: CGFloat.pi / 2,
                      clockwise: true,
                      transform: .identity)
        
        cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: shapeLayer.bounds.height - radius),
                      radius: radius,
                      startAngle: CGFloat.pi / 2,
                      endAngle: 0,
                      clockwise: true,
                      transform: .identity)
        cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: radius),
                      radius: radius,
                      startAngle: 0,
                      endAngle: 3 * CGFloat.pi / 2,
                      clockwise: true,
                      transform: .identity)
        
        cgPath.addArc(center: CGPoint(x: triangleWidth + radius, y: radius),
                      radius: radius,
                      startAngle: 3 * CGFloat.pi / 2,
                      endAngle: CGFloat.pi,
                      clockwise: true,
                      transform: .identity)
        cgPath.addLine(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 - triangleHeight / 2))
        
        cgPath.addArc(tangent1End: CGPoint(x: 0, y: shapeLayer.bounds.height / 2), tangent2End: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 + triangleHeight / 2), radius: triangleRadius)
        cgPath.addLine(to: CGPoint(x: triangleWidth, y: shapeLayer.bounds.height / 2 + triangleHeight / 2))

        return cgPath
    }
     /*
    override func draw(_ rect: CGRect) {
        let radius: CGFloat = 10
        let triangleWidth: CGFloat = 20
        let triangleHeight: CGFloat = 20

        // Create the path for the triangle and the rounded rectangle
        let path = UIBezierPath()

        // Start with the triangle part
        path.move(to: CGPoint(x: triangleWidth, y: rect.height / 2 - triangleHeight / 2))
        path.addLine(to: CGPoint(x: 0, y: rect.height / 2))
        path.addLine(to: CGPoint(x: triangleWidth, y: rect.height / 2 + triangleHeight / 2))

        // Continue with the rounded rectangle part
        path.addLine(to: CGPoint(x: triangleWidth, y: rect.height - radius))
        path.addArc(withCenter: CGPoint(x: triangleWidth + radius, y: rect.height - radius),
                    radius: radius,
                    startAngle: CGFloat.pi,
                    endAngle: CGFloat.pi / 2,
                    clockwise: false)
        path.addLine(to: CGPoint(x: rect.width - radius, y: rect.height))
        path.addArc(withCenter: CGPoint(x: rect.width - radius, y: rect.height - radius),
                    radius: radius,
                    startAngle: CGFloat.pi / 2,
                    endAngle: 0,
                    clockwise: false)
        path.addLine(to: CGPoint(x: rect.width, y: radius))
        path.addArc(withCenter: CGPoint(x: rect.width - radius, y: radius),
                    radius: radius,
                    startAngle: 0,
                    endAngle: -CGFloat.pi / 2,
                    clockwise: false)
        path.addLine(to: CGPoint(x: triangleWidth + radius, y: 0))
        path.addArc(withCenter: CGPoint(x: triangleWidth + radius, y: radius),
                    radius: radius,
                    startAngle: -CGFloat.pi / 2,
                    endAngle: -CGFloat.pi,
                    clockwise: false)
        path.close()

        // Set the stroke color to gray
        UIColor.gray.setStroke()

        // Set the fill color to white
        UIColor.white.setFill()

        // Draw the filled path
        path.fill()

        // Draw the path with stroke
        path.stroke()
    }
*/
}

还有 Github 上的项目:

https://github.com/DuncanMC/CustomBubbleView.git

swift cgpath
1个回答
0
投票

可以在这篇 Math.SE 帖子中找到相关数学知识。从该帖子中,您可以找到圆弧中心与切线交点之间的距离(该帖子中称为 QA)。从中减去三角形半径就是您需要移动的量。

QA 等于半径除以 sin(alpha/2),其中 alpha 是切线所成的角度。

func calculateShift(width: CGFloat, height: CGFloat, cornerRadius: CGFloat) -> CGFloat {
    // here the CGSizes represent 2D vectors
    let v1 = CGSize(width: -width, height: height / 2)
    let v2 = CGSize(width: -width, height: -height / 2)
    let alpha = angle(between: v1, and: v2)
    let qa = cornerRadius / sin(alpha / 2)
    return qa - cornerRadius
}

func cross(_ v1: CGSize, _ v2: CGSize) -> CGFloat {
    v1.width * v2.height - v1.height * v2.width
}

func dot(_ v1: CGSize, _ v2: CGSize) -> CGFloat {
    v1.width * v2.width + v1.height * v2.height
}

func angle(between v1: CGSize, and v2: CGSize) -> CGFloat {
    atan2(cross(v1, v2), dot(v1, v2))
}

然后路径可以轻松地向左移动:

let cgPath = CGMutablePath()
let shiftAmount = calculateShift(width: triangleWidth, height: triangleHeight, cornerRadius: triangleRadius)
cgPath.move(to: CGPoint(x: triangleWidth - shiftAmount, y: shapeLayer.bounds.height/2 + triangleHeight/2))
cgPath.addArc(center: CGPoint(x: triangleWidth + radius - shiftAmount, y: shapeLayer.bounds.height - radius),
              radius: radius,
              startAngle: CGFloat.pi,
              endAngle: CGFloat.pi / 2,
              clockwise: true,
              transform: .identity)

cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: shapeLayer.bounds.height - radius),
              radius: radius,
              startAngle: CGFloat.pi / 2,
              endAngle: 0,
              clockwise: true,
              transform: .identity)
cgPath.addArc(center: CGPoint(x: shapeLayer.bounds.width - radius, y: radius),
              radius: radius,
              startAngle: 0,
              endAngle: 3 * CGFloat.pi / 2,
              clockwise: true,
              transform: .identity)

cgPath.addArc(center: CGPoint(x: triangleWidth + radius - shiftAmount, y: radius),
              radius: radius,
              startAngle: 3 * CGFloat.pi / 2,
              endAngle: CGFloat.pi,
              clockwise: true,
              transform: .identity)
cgPath.addLine(to: CGPoint(x: triangleWidth - shiftAmount, y: shapeLayer.bounds.height / 2 - triangleHeight / 2))

cgPath.addArc(
    tangent1End: CGPoint(x: -shiftAmount, y: shapeLayer.bounds.height / 2),
    tangent2End: CGPoint(x: triangleWidth - shiftAmount, y: shapeLayer.bounds.height / 2 + triangleHeight / 2),
    radius: triangleRadius
)
cgPath.addLine(to: CGPoint(x: triangleWidth - shiftAmount, y: shapeLayer.bounds.height / 2 + triangleHeight / 2))

return cgPath
© www.soinside.com 2019 - 2024. All rights reserved.