使用UIBezierPath绘制自定义视图会导致形状不对称

时间:2018-01-26 20:30:32

标签: ios iphone swift uiview uibezierpath

我正试图用一些'弯曲的边缘'绘制一个UIView。

这是它应该是什么样子:

enter image description here

这是我得到的:

enter image description here

注意右上角(TR)角是如何与右下角(BR)角对称的? BR角落与我想要达到的非常相似,但是我无法让TR角正确对齐(用一堆不同的起点和终点角度来玩)。

这是代码:

struct Constants {
    static let cornerRadius: CGFloat = 15.0 // used for left-top and left-bottom curvature
    static let rightTipWidth: CGFloat = 40.0 // the max. width for the right tip thingy
    static let rightCornerRadius: CGFloat = 10.0 // the radius for the right tip
    static let rightEdgeRadius: CGFloat = 10.0 // the radius for the top right and bottom right curvature
}
    override func draw(_ rect: CGRect) {
    super.draw(rect)

    // Initialize the path.
    let path = UIBezierPath()

    // starting point
    let startingPoint = CGPoint(x: Constants.cornerRadius, y: 0.0)
    path.move(to: startingPoint)

    // create a center point for the arc for the top left corner
    let leftTopCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: Constants.cornerRadius)
    path.addArc(withCenter: leftTopCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 270.degreesToRadians, endAngle: 180.degreesToRadians, clockwise: false)

    // move the path to the bottom left corner
    path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - Constants.cornerRadius))

    // add the arc to bottom left
    let leftBottomCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: frame.size.height - Constants.cornerRadius)
    path.addArc(withCenter: leftBottomCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 180.degreesToRadians, endAngle: 90.degreesToRadians, clockwise: false)

    // move along the bottom to the right edge - rightTipWidth
    let maxXRightEdge = frame.size.width - Constants.rightTipWidth
    path.addLine(to: CGPoint(x: maxXRightEdge, y: frame.size.height))

    // add a curve at the bottom before tipping up at 45 degrees
    let bottomRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: frame.size.height - Constants.rightEdgeRadius)
    path.addArc(withCenter: bottomRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 90.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: false)

    // figure out the center for the right side curvature
    let rightMidPointY = frame.size.height / 2.0
    let halfRadius = (Constants.rightCornerRadius / 2.0)

    // move up till the mid point corner radius
    path.addLine(to: CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY + halfRadius)))

    // the destination for the curve (end point of the curve)
    let rightEndPoint = CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY - halfRadius))

    // figure out the right side tip's control point (See: https://developer.apple.com/documentation/uikit/uibezierpath/1624351-addquadcurve)
    let rightControlPoint = CGPoint(x: frame.size.width - halfRadius, y: rightMidPointY)

    // add the curve for the right side tip
    path.addQuadCurve(to: rightEndPoint, controlPoint: rightControlPoint)

    // move up at 45 degrees
    path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius))

    let topRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: Constants.rightEdgeRadius)
    path.addArc(withCenter: topRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 315.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: false) // straight

    path.close()

    // Specify the fill color and apply it to the path.
    UIColor.orange.setFill()
    path.fill()

    // Specify a border (stroke) color.
    UIColor.orange.setStroke()
    path.stroke()
}

extension BinaryInteger {
    var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}

快速总结一下我的思维过程:

  • 创建bezierPath并将其移至startingPoint
  • 添加LT(左上角)曲线并将线向下移动
  • 沿左边缘移动线条并添加LB(左下角)曲线 以及沿底部到右边缘的移动线
  • 将行移至frame.size.width - Constants.rightTipWidth
  • 添加中心点为x = currentPointy = height- rightEdgeRadius
  • 的圆弧
  • 将该行向上移动到y = (height / 2.0) + (Constants.rightCornerRadius / 2.0)
  • 添加终点为y = (height / 2.0) - (Constants.rightCornerRadius / 2.0)
  • 的QuadCurve
  • 将该行向上移动到x = maxXRightEdge + Constants.rightEdgeRadius
  • 添加右上角(TR)曲线--->导致不对称 曲率

3 个答案:

答案 0 :(得分:4)

这是另一个演绎:

@IBDesignable
open class PointerView: UIView {

    /// The left-top and left-bottom curvature
    @IBInspectable var cornerRadius: CGFloat      = 15      { didSet { updatePath() } }

    /// The radius for the right tip
    @IBInspectable var rightCornerRadius: CGFloat = 10      { didSet { updatePath() } }

    /// The radius for the top right and bottom right curvature
    @IBInspectable var rightEdgeRadius: CGFloat   = 10      { didSet { updatePath() } }

    /// The fill color
    @IBInspectable var fillColor: UIColor         = .blue   { didSet { shapeLayer.fillColor = fillColor.cgColor } }

    /// The stroke color
    @IBInspectable var strokeColor: UIColor       = .clear  { didSet { shapeLayer.strokeColor = strokeColor.cgColor } }

    /// The angle of the tip
    @IBInspectable var angle: CGFloat             = 90      { didSet { updatePath() } }

    /// The line width
    @IBInspectable var lineWidth: CGFloat         = 0       { didSet { updatePath() } }

    /// The shape layer for the pointer
    private lazy var shapeLayer: CAShapeLayer = {
        let _shapeLayer = CAShapeLayer()
        _shapeLayer.fillColor = fillColor.cgColor
        _shapeLayer.strokeColor = strokeColor.cgColor
        _shapeLayer.lineWidth = lineWidth
        return _shapeLayer
    }()

    public override init(frame: CGRect) {
        super.init(frame: frame)

        configure()
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        configure()
    }

    private func configure() {
        layer.addSublayer(shapeLayer)
    }

    open override func layoutSubviews() {
        super.layoutSubviews()

        updatePath()
    }

    private func updatePath() {
        let path = UIBezierPath()

        let offset = lineWidth / 2
        let boundingRect = bounds.insetBy(dx: offset, dy: offset)

        let arrowTop = CGPoint(x: boundingRect.maxX - boundingRect.height / 2 / tan(angle * .pi / 180 / 2), y: boundingRect.minY)
        let arrowRight = CGPoint(x: boundingRect.maxX, y: boundingRect.midY)
        let arrowBottom = CGPoint(x: boundingRect.maxX - boundingRect.height / 2 / tan(angle * .pi / 180 / 2), y: boundingRect.maxY)
        let start = CGPoint(x: boundingRect.minX + cornerRadius, y: boundingRect.minY)

        // top left
        path.move(to: start)
        path.addQuadCurve(to: CGPoint(x: boundingRect.minX, y: boundingRect.minY + cornerRadius), controlPoint: CGPoint(x: boundingRect.minX, y: boundingRect.minY))

        // left
        path.addLine(to: CGPoint(x: boundingRect.minX, y: boundingRect.maxY - cornerRadius))

        // lower left
        path.addQuadCurve(to: CGPoint(x: boundingRect.minX + cornerRadius, y: boundingRect.maxY), controlPoint: CGPoint(x: boundingRect.minX, y: boundingRect.maxY))

        // bottom
        path.addLine(to: calculate(from: path.currentPoint, to: arrowBottom, less: rightEdgeRadius))

        // bottom right (before tip)
        path.addQuadCurve(to: calculate(from: arrowRight, to: arrowBottom, less: rightEdgeRadius), controlPoint: arrowBottom)

        // bottom edge of tip
        path.addLine(to: calculate(from: path.currentPoint, to: arrowRight, less: rightCornerRadius))

        // tip
        path.addQuadCurve(to: calculate(from: arrowTop, to: arrowRight, less: rightCornerRadius), controlPoint: arrowRight)

        // top edge of tip
        path.addLine(to: calculate(from: path.currentPoint, to: arrowTop, less: rightEdgeRadius))

        // top right (after tip)
        path.addQuadCurve(to: calculate(from: start, to: arrowTop, less: rightEdgeRadius), controlPoint: arrowTop)

        path.close()

        shapeLayer.lineWidth = lineWidth
        shapeLayer.path = path.cgPath
    }

    /// Calculate some point between `startPoint` and `endPoint`, but `distance` from `endPoint
    ///
    /// - Parameters:
    ///   - startPoint: The starting point.
    ///   - endPoint: The ending point.
    ///   - distance: Distance from the ending point
    /// - Returns: Returns the point that is `distance` from the `endPoint` as you travel from `startPoint` to `endPoint`.

    private func calculate(from startPoint: CGPoint, to endPoint: CGPoint, less distance: CGFloat) -> CGPoint {
        let angle = atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x)
        let totalDistance = hypot(endPoint.y - startPoint.y, endPoint.x - startPoint.x) - distance

        return CGPoint(x: startPoint.x + totalDistance * cos(angle),
                       y: startPoint.y + totalDistance * sin(angle))
    }
}

因为那是@IBDesignable,我可以把它放在一个单独的框架目标中,然后在Interface Builder中选择使用它(并自定义它):

enter image description here

我在参数中做出的唯一改变是不使用尖端的宽度,而是使用尖端的角度。这样,如果大小随着约束(或其他)的变化而变化,则会保留所需的形状。

我还将此更改为使用CAShapeLayer而不是自定义draw(_:)方法,以享受Apple内置图层所具有的任何效率。

答案 1 :(得分:0)

我不知道你的实现,但我认为如果你这样实现它会很容易,这样你就可以完美地实现对称形状

enter image description here

绘制一个三角形,只需调整三角形点的位置

 class TriangleView : UIView {

override init(frame: CGRect) {
    super.init(frame: frame)
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func draw(_ rect: CGRect) {

    guard let context = UIGraphicsGetCurrentContext() else { return }

    context.beginPath()
    context.move(to: CGPoint(x: rect.minX, y: rect.maxY))
    context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
    context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
    context.closePath()

    context.setFillColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.60)
    context.fillPath()
}
}

答案 2 :(得分:0)

在这里,您忘记了// move up at 45 degrees path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius - halfRadius))

override func draw(_ rect: CGRect) {
    super.draw(rect)

    // Initialize the path.
    let path = UIBezierPath()

    // starting point
    let startingPoint = CGPoint(x: Constants.cornerRadius, y: 0.0)
    path.move(to: startingPoint)

    // create a center point for the arc for the top left corner
    let leftTopCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: Constants.cornerRadius)
    path.addArc(withCenter: leftTopCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 270.degreesToRadians, endAngle: 180.degreesToRadians, clockwise: false)

    // move the path to the bottom left corner
    path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - Constants.cornerRadius))

    // add the arc to bottom left
    let leftBottomCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: frame.size.height - Constants.cornerRadius)
    path.addArc(withCenter: leftBottomCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 180.degreesToRadians, endAngle: 90.degreesToRadians, clockwise: false)

    // move along the bottom to the right edge - rightTipWidth
    let maxXRightEdge = frame.size.width - Constants.rightTipWidth
    path.addLine(to: CGPoint(x: maxXRightEdge, y: frame.size.height))

    // add a curve at the bottom before tipping up at 45 degrees
    let bottomRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: frame.size.height - Constants.rightEdgeRadius)
    path.addArc(withCenter: bottomRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 90.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: false)

    // figure out the center for the right side curvature
    let rightMidPointY = frame.size.height / 2.0
    let halfRadius = (Constants.rightCornerRadius / 2.0)

    // move up till the mid point corner radius
    path.addLine(to: CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY + halfRadius)))

    // the destination for the curve (end point of the curve)
    let rightEndPoint = CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY - halfRadius))

    // figure out the right side tip's control point (See: https://developer.apple.com/documentation/uikit/uibezierpath/1624351-addquadcurve)
    let rightControlPoint = CGPoint(x: frame.size.width - halfRadius, y: rightMidPointY)

    // add the curve for the right side tip
    path.addQuadCurve(to: rightEndPoint, controlPoint: rightControlPoint)

    // move up at 45 degrees
    path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius - halfRadius))

    let topRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: Constants.rightEdgeRadius)
    path.addArc(withCenter: topRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 315.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: false) // straight

    path.close()

    // Specify the fill color and apply it to the path.
    UIColor.orange.setFill()
    path.fill()

    // Specify a border (stroke) color.
    UIColor.orange.setStroke()
    path.stroke()
}

完整代码:

if (file.exists) {
    trace(file.size); 
} else {
    trace("File does not exist (yet!)");
}