ios UIBezierPath带圆角边的三角形

klsxnrf1  于 5个月前  发布在  iOS
关注(0)|答案(8)|浏览(80)

我设计了以下代码来生成UIBezierPath。此路径用于CAShapeLayer中以屏蔽UIView。注意,视图的高度和宽度是可变的。
这段代码生成了一个三角形与尖锐的边缘,但我想使角落圆角。我已经尝试了addArcWithCenter...lineCapStylelineJoinStyle等,但似乎没有什么适合我。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

CGPoint center = CGPointMake(rect.size.width / 2, 0);
CGPoint bottomLeft = CGPointMake(10, rect.size.height - 0);
CGPoint bottomRight = CGPointMake(rect.size.width - 0, rect.size.height - 0);
 
[bezierPath moveToPoint:center];
[bezierPath addLineToPoint:bottomLeft];
[bezierPath addLineToPoint:bottomRight];
[bezierPath closePath];

字符串
如何在UIBezierPath中圆化三角形的所有边?我需要子层,多个路径等吗?
我没有绘制此BezierPath,因此drawRect中的所有CGContext...函数在此上下文中都没有用

sigwle7e

sigwle7e1#

编辑

**FWIW:**这个答案解释了CGPathAddArcToPoint(...)为你做了什么,起到了教育的作用。我强烈建议你通读它,因为它将帮助你理解和欣赏CGPath API。然后你应该继续使用它,就像在an0's answer中看到的那样,而不是在你的应用程序中圆边时使用这个代码。如果你想玩和学习这样的几何计算,这个代码只应该用作参考。

原始答案

因为我觉得这样的问题很有趣,所以我必须回答它:)
这是一个很长的答案。没有简短的版本:D

  • 注意:为了简单起见,我的解决方案是对用于形成三角形的点做一些假设,例如:*
    • 三角形的面积足够大,以适应圆角(例如,三角形的高度大于角上圆的直径。我没有检查或试图防止任何可能发生的奇怪结果。*
    • 角是按逆时针顺序列出的。你可以让它适用于任何顺序,但为了简单起见,它似乎是一个足够公平的约束。*
  • 如果你愿意,你可以使用同样的技术来圆化任何多边形,只要它是严格凸的(即不是尖的星星)。我不会解释如何做到这一点,但它遵循相同的原则。*

这一切都是从一个三角形开始的,你想用某个半径,r 来圆化它的角:
x1c 0d1x的数据
圆角三角形应该包含在尖三角形中,所以第一步是找到尽可能靠近角的位置,在那里你可以用半径r拟合一个圆。
一个简单的方法是创建3条平行于三角形中3条边的新直线,并将每个距离 r 向内移动,与原始边的边正交。
要执行此操作,请计算每条线的斜率/Angular 以及要应用于两个新点的偏移:

CGFloat angle = atan2f(end.y - start.y,
                       end.x - start.x);

CGVector offset = CGVectorMake(-sinf(angle)*radius,
                                cosf(angle)*radius);

字符串

  • 注意事项:为了清楚起见,我使用CGVector类型(在iOS 7中可用),但您也可以使用点或大小来与以前的OS版本 * 一起使用。

然后将偏移量添加到每条线的起点和终点:

CGPoint offsetStart = CGPointMake(start.x + offset.dx,
                                  start.y + offset.dy);

CGPoint offsetEnd   = CGPointMake(end.x + offset.dx,
                                  end.y + offset.dy);


当你这样做的时候,你会看到这三条线在三个地方相交:



每个交点正好是距离 r 的两个边 (假设三角形足够大,如上所述)
可以计算两条直线的交点:

//       (x1⋅y2-y1⋅x2)(x3-x4) - (x1-x2)(x3⋅y4-y3⋅x4)
// px =  –––––––––––––––––––––––––––––––––––––––––––
//            (x1-x2)(y3-y4) - (y1-y2)(x3-x4)

//       (x1⋅y2-y1⋅x2)(y3-y4) - (y1-y2)(x3⋅y4-y3⋅x4)
// py =  –––––––––––––––––––––––––––––––––––––––––––
//            (x1-x2)(y3-y4) - (y1-y2)(x3-x4)

CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));

CGPoint intersection = CGPointMake(intersectionX, intersectionY);


其中,(x1,y1)至(x2,y2)是第一线,(x3,y3)至(x4,y 4)是第二线。
如果你在每个交点上放一个半径为 r 的圆,你可以看到它确实适用于圆角三角形(忽略三角形和圆的不同线宽):



现在要创建圆角三角形,您需要在原始三角形与交点正交的点上创建一条从直线到圆弧再到直线(等等)的路径。这也是圆与原始三角形相切的点。
已知三角形中所有三条边的斜率、圆角半径和圆心(交点),每个圆角的起始角和终止角都是该边的斜率- 90度。为了将这些东西组合在一起,我在代码中创建了一个结构体,但如果你不想这样做,可以不必这样做:

typedef struct {
    CGPoint centerPoint;
    CGFloat startAngle;
    CGFloat endAngle;
} CornerPoint;


为了减少代码重复,我为自己创建了一个方法,该方法计算一个点的交点和Angular ,给定一条从一个点经过另一个点到最终点的直线(它不是闭合的,所以它不是三角形):



代码如下(实际上只是我上面展示的代码,放在一起):

- (CornerPoint)roundedCornerWithLinesFrom:(CGPoint)from
                                      via:(CGPoint)via
                                       to:(CGPoint)to
                               withRadius:(CGFloat)radius
{
    CGFloat fromAngle = atan2f(via.y - from.y,
                               via.x - from.x);
    CGFloat toAngle   = atan2f(to.y  - via.y,
                               to.x  - via.x);

    CGVector fromOffset = CGVectorMake(-sinf(fromAngle)*radius,
                                        cosf(fromAngle)*radius);
    CGVector toOffset   = CGVectorMake(-sinf(toAngle)*radius,
                                        cosf(toAngle)*radius);

    CGFloat x1 = from.x +fromOffset.dx;
    CGFloat y1 = from.y +fromOffset.dy;

    CGFloat x2 = via.x  +fromOffset.dx;
    CGFloat y2 = via.y  +fromOffset.dy;

    CGFloat x3 = via.x  +toOffset.dx;
    CGFloat y3 = via.y  +toOffset.dy;

    CGFloat x4 = to.x   +toOffset.dx;
    CGFloat y4 = to.y   +toOffset.dy;

    CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
    CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));

    CGPoint intersection = CGPointMake(intersectionX, intersectionY);

    CornerPoint corner;
    corner.centerPoint = intersection;
    corner.startAngle  = fromAngle - M_PI_2;
    corner.endAngle    = toAngle   - M_PI_2;

    return corner;
}


然后,我使用该代码3次来计算3个角:

CornerPoint leftCorner  = [self roundedCornerWithLinesFrom:right
                                                       via:left
                                                        to:top
                                                withRadius:radius];

CornerPoint topCorner   = [self roundedCornerWithLinesFrom:left
                                                       via:top
                                                        to:right
                                                withRadius:radius];

CornerPoint rightCorner = [self roundedCornerWithLinesFrom:top
                                                       via:right
                                                        to:left
                                                withRadius:radius];


现在,有了所有必要的数据,开始我们创建实际路径的部分。我将依赖于CGPathAddArc将添加从当前点到起点的直线的事实,而不必自己绘制这些线(这是记录的行为)。
我唯一需要手动计算的点是路径的起点。我选择右下角的起点(没有具体原因)。从那里你只需添加一个弧,圆心在起点和终点的交点上:

CGMutablePathRef roundedTrianglePath = CGPathCreateMutable();
// manually calculated start point
CGPathMoveToPoint(roundedTrianglePath, NULL,
                  leftCorner.centerPoint.x + radius*cosf(leftCorner.startAngle),
                  leftCorner.centerPoint.y + radius*sinf(leftCorner.startAngle));
// add 3 arcs in the 3 corners 
CGPathAddArc(roundedTrianglePath, NULL,
             leftCorner.centerPoint.x, leftCorner.centerPoint.y,
             radius,
             leftCorner.startAngle, leftCorner.endAngle,
             NO);
CGPathAddArc(roundedTrianglePath, NULL,
             topCorner.centerPoint.x, topCorner.centerPoint.y,
             radius,
             topCorner.startAngle, topCorner.endAngle,
             NO);
CGPathAddArc(roundedTrianglePath, NULL,
             rightCorner.centerPoint.x, rightCorner.centerPoint.y,
             radius,
             rightCorner.startAngle, rightCorner.endAngle,
             NO);
// close the path
CGPathCloseSubpath(roundedTrianglePath);


看起来像这样:
x1c4d 1x的
没有所有支持线的最终结果,看起来像这样:


btqmn9zl

btqmn9zl2#

@大卫的几何很酷,也很有教育意义。但你真的不需要这样遍历整个几何。我将提供一个简单得多的代码:

CGFloat radius = 20;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2);
CGPathAddArcToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius);
CGPathAddArcToPoint(path, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius);
CGPathAddArcToPoint(path, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius);
CGPathCloseSubpath(path);

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:path];
CGPathRelease(path);

字符串
bezierPath是你需要的。关键是CGPathAddArcToPoint为你做几何。

ukdjmx9f

ukdjmx9f3#

Swift 4中的圆角三角形

基于@an0的answer的Playground。
x1c 0d1x的数据

import UIKit
import PlaygroundSupport
import CoreGraphics

var view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.backgroundColor = .black
PlaygroundPage.current.liveView = view

let triangle = CAShapeLayer()
triangle.fillColor = UIColor.systemGreen.cgColor
triangle.path = createRoundedTriangle(width: 220, height: 200, radius: 15)
triangle.position = CGPoint(x: 140, y: 130)
view.layer.addSublayer(triangle)

func createRoundedTriangle(width: CGFloat, height: CGFloat, radius: CGFloat) -> CGPath {
    // Draw the triangle path with its origin at the center.
    let point1 = CGPoint(x: -width / 2, y: height / 2)
    let point2 = CGPoint(x: 0, y: -height / 2)
    let point3 = CGPoint(x: width / 2, y: height / 2)

    let path = CGMutablePath()
    path.move(to: CGPoint(x: 0, y: height / 2))
    path.addArc(tangent1End: point1, tangent2End: point2, radius: radius)
    path.addArc(tangent1End: point2, tangent2End: point3, radius: radius)
    path.addArc(tangent1End: point3, tangent2End: point1, radius: radius)
    path.closeSubpath()

    return path
}

字符串

tsm1rwdh

tsm1rwdh4#

@an0 -谢谢你!我是全新的,所以我没有声誉标记为有用或评论,但你今天为我节省了大量的时间。
我知道这超出了问题的范围,但同样的逻辑适用于CGContextBeginPath()CGContextMoveToPoint()CGContextAddArcToPoint()CGContextClosePath(),以防任何人需要使用它。
如果有人感兴趣的话,这里是我写的一个指向右边的等边三角形的代码。triangleFrame是预定义的CGRectradius是预定义的CGFloat

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);

CGContextMoveToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame)); CGRectGetMidY(triangleFrame));
CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMinY(triangleFrame), radius);
CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMinY(triangleFrame), CGRectGetMaxX(triangleFrame), CGRectGetMidY(triangleFrame), radius);
CGContextAddArcToPoint(context, CGRectGetMaxX(triangleFrame), CGRectGetMidY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMaxY(triangleFrame), radius);
CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMaxY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame), radius);

字符串
当然,对于不规则三角形,每个点都可以更具体地定义。然后您可以根据需要CGContextFillPath()CGContextStrokePath()

vc6uscn9

vc6uscn95#

雨燕中的圆角三角

根据@invisible squirrel的回答,我写了一个UIView扩展。

class TriangleView: UIView { 
      override func draw(_ rect: CGRect) {
         let widthHeight = self.layer.frame.size.height

         let triangle = CAShapeLayer()
         triangle.fillColor = UIColor.black.cgColor
         triangle.path = roundedTriangle(widthHeight: widthHeight)
         triangle.position = CGPoint(x: 0, y: 0)
         self.layer.addSublayer(triangle)
      }

      func roundedTriangle(widthHeight: CGFloat) -> CGPath {
         let point1 = CGPoint(x: widthHeight/2, y:0)
         let point2 = CGPoint(x: widthHeight , y: widthHeight)
         let point3 =  CGPoint(x: 0, y: widthHeight)
    
         let path = CGMutablePath()
  
         path.move(to: CGPoint(x: 0, y: widthHeight))
         path.addArc(tangent1End: point1, tangent2End: point2, radius: 5)
         path.addArc(tangent1End: point2, tangent2End: point3, radius: 5)
         path.addArc(tangent1End: point3, tangent2End: point1, radius: 5)
         path.closeSubpath()
         return path
      }  
   }

字符串

aamkag61

aamkag616#

对于任何想要基于SwiftUI的解决方案的人,这里有一个根据@Anjali的答案改编的Shape。它可以在任何使用常规SwiftUI形状(如CircleEllipse)的地方使用。

struct Triangle: Shape {
  let cornerRadius: CGFloat
  
  func path(in rect: CGRect) -> Path {
    let width = rect.width
    let height = rect.height
    
    // The triangle's three corners.
    let bottomLeft = CGPoint(x: 0, y: height)
    let bottomRight = CGPoint(x: width, y: height)
    let topMiddle = CGPoint(x: rect.midX, y: 0)
    
    // We'll start drawing from the bottom middle of the triangle,
    // the midpoint between the two lower corners.
    let bottomMiddle = CGPoint(x: rect.midX, y: height)
    
    // Draw three arcs to trace the triangle.
    var path = Path()
    path.move(to: bottomMiddle)
    path.addArc(tangent1End: bottomRight, tangent2End: topMiddle, radius: cornerRadius)
    path.addArc(tangent1End: topMiddle, tangent2End: bottomLeft, radius: cornerRadius)
    path.addArc(tangent1End: bottomLeft, tangent2End: bottomRight, radius: cornerRadius)
    
    return path
  }
}

字符串

1l5u6lss

1l5u6lss7#

基于@大卫的answer,我写了一个UIBezierPath扩展,它添加了与CGPath相同的功能:

extension UIBezierPath {
    // Based on https://stackoverflow.com/a/20646446/634185
    public func addArc(from startPoint: CGPoint,
                       to endPoint: CGPoint,
                       centerPoint: CGPoint,
                       radius: CGFloat,
                       clockwise: Bool) {
        let fromAngle = atan2(centerPoint.y - startPoint.y,
                              centerPoint.x - startPoint.x)
        let toAngle = atan2(endPoint.y - centerPoint.y,
                            endPoint.x - centerPoint.x)

        let fromOffset = CGVector(dx: -sin(fromAngle) * radius,
                                  dy: cos(fromAngle) * radius)
        let toOffset = CGVector(dx: -sin(toAngle) * radius,
                                dy: cos(toAngle) * radius)

        let x1 = startPoint.x + fromOffset.dx
        let y1 = startPoint.y + fromOffset.dy

        let x2 = centerPoint.x + fromOffset.dx
        let y2 = centerPoint.y + fromOffset.dy

        let x3 = centerPoint.x + toOffset.dx
        let y3 = centerPoint.y + toOffset.dy

        let x4 = endPoint.x + toOffset.dx
        let y4 = endPoint.y + toOffset.dy

        let intersectionX =
            ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4))
                / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
        let intersectionY =
            ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4))
                / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))

        let intersection = CGPoint(x: intersectionX, y: intersectionY)
        let startAngle = fromAngle - .pi / 2.0
        let endAngle = toAngle - .pi / 2.0

        addArc(withCenter: intersection,
               radius: radius,
               startAngle: startAngle,
               endAngle: endAngle,
               clockwise: clockwise)
    }
}

字符串
因此,代码应该是这样的:

let path = UIBezierPath()

let center = CGPoint(x: rect.size.width / 2, y: 0)
let bottomLeft = CGPoint(x: 10, y: rect.size.height - 0)
let bottomRight = CGPoint(x: rect.size.width - 0, y: rect.size.height - 0)

path.move(to: center);
path.addArc(from: center,
            to: bottomRight,
            centerPoint: bottomLeft,
            radius: radius,
            clockwise: false)
path.addLine(to: bottomRight)
path.close()

lbsnaicq

lbsnaicq8#

简单得多的方法就是添加两行代码:

UIBezierPath *bezierPath = [UIBezierPath bezierPath];
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;

CGPoint center = CGPointMake(rect.size.width / 2, 0);
...

字符串

编辑:

要看到角是圆的,你需要使三角形比矩形边界小一点:

CGPoint center = CGPointMake(rect.size.width / 2, 10);
    CGPoint bottomLeft = CGPointMake(10, rect.size.height - 10);
    CGPoint bottomRight = CGPointMake(rect.size.width - 10, rect.size.height - 10);

相关问题