【问题标题】:Drawing UIBezierPath on code generated UIView在代码生成的 UIView 上绘制 UIBezierPath
【发布时间】:2014-02-14 05:16:36
【问题描述】:

我在运行时在代码中添加了UIView

我想在其中画一个UIBezierPath,但这是否意味着我必须为 UIView 覆盖drawRect

或者在定制的UIView上是否有另一种绘制方式?

这里是生成UIView的代码:

UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)];
shapeView.clipsToBounds = YES;

这里是创建和返回UIBezierPath的函数:

- (UIBezierPath*)createPath
{
    UIBezierPath* path = [[UIBezierPath alloc]init];
    [path moveToPoint:CGPointMake(100.0, 50.0)];
    [path addLineToPoint:CGPointMake(200.0,50.0)];
    [path addLineToPoint:CGPointMake(200.0, 200.0)];
    [path addLineToPoint:CGPointMake(100.0, 200.0)];
    [path closePath];
    return path;
}

【问题讨论】:

    标签: ios iphone objective-c uiview uibezierpath


    【解决方案1】:

    如果您使用CAShapeLayer 会更容易,如下所示:

    CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];
    

    并设置其path:

    [shapeView setPath:[self createPath].CGPath];
    

    最后添加:

    [[self.view layer] addSublayer:shapeView];
    

    【讨论】:

      【解决方案2】:

      您可以使用CAShapeLayer 来执行此操作。

      像这样……

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [self createPath].CGPath;
      shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc...
      shapeLayer.lineWidth = 2.0; //etc...
      shapeLayer.position = CGPointMake(100, 100); //etc...
      [self.layer addSublayer:shapeLayer];
      

      这将添加和绘制路径,而无需覆盖drawRect

      【讨论】:

        【解决方案3】:

        是的,如果你想绘制任何东西,你必须重写 drawrect。创建 UIBezierPath 可以在任何地方完成,但是要绘制一些东西你必须在 drawrect 方法中进行

        如果您在 UIView 的子类中重写 drawRect,您应该调用 setNeedsDisplay,这基本上是在屏幕上绘制某些东西的自定义视图,例如线条、图像、矩形。

        【讨论】:

        • setNeedsDisplay 在哪里调用?
        【解决方案4】:

        正如其他海报所指出的,使用形状图层是一个不错的方法。

        形状层 a 可能比覆盖 drawRect 提供更好的性能。

        如果您想自己绘制路径,那么是的,您需要为您的自定义视图类覆盖 drawRect。

        【讨论】:

          【解决方案5】:

          有多种方法可以实现您的愿望。我见过最多的是:覆盖drawRect,将您的形状绘制到CAShapeLayer中,然后将其作为子层添加到您的视图中,或者draw your path onto another context,将其保存为图像,然后将其添加到您的视图中.

          所有这些都是合理的选择,哪一个最好取决于许多其他因素,例如您是否要不断添加形状、调用频率等。

          【讨论】:

            【解决方案6】:

            不久前,我什至不知道如何发音 Bézier,更不用说如何使用 Bézier 路径来制作自定义形状。以下是我学到的。事实证明,它们并不像一开始看起来那么可怕。

            如何在自定义视图中绘制Bézier path

            这些是主要步骤:

            1. 设计所需形状的轮廓。
            2. 将轮廓路径划分为线段、圆弧段和曲线段。
            3. 以编程方式构建该路径。
            4. drawRectCAShapeLayer 中绘制路径。

            设计外形轮廓

            你可以做任何事情,但作为一个例子,我选择了下面的形状。它可能是键盘上的弹出键。

            将路径分割成段

            回顾您的形状设计并将其分解为更简单的线条元素(用于直线)、弧线(用于圆和圆角)和曲线(用于其他任何东西)。

            我们的示例设计如下所示:

            • 黑色是线段
            • 浅蓝色是弧线段
            • 红色是曲线
            • 橙色点是曲线的控制点
            • 绿点是路径段之间的点
            • 虚线表示边界矩形
            • 深蓝色数字是按程序添加顺序的分段

            以编程方式构建路径

            我们将任意从左下角开始,顺时针工作。我将使用图像中的网格来获取点的 x 和 y 值。我将在这里对所有内容进行硬编码,但当然你不会在实际项目中这样做。

            基本流程是:

            1. 创建一个新的UIBezierPath
            2. 选择路径上的起点moveToPoint
            3. 向路径添加段
              • 线路:addLineToPoint
              • 弧:addArcWithCenter
              • 曲线:addCurveToPoint
            4. closePath关闭路径

            这是在上图中制作路径的代码。

            func createBezierPath() -> UIBezierPath {
            
                // create a new path
                let path = UIBezierPath()
            
                // starting point for the path (bottom left)
                path.move(to: CGPoint(x: 2, y: 26))
            
                // *********************
                // ***** Left side *****
                // *********************
            
                // segment 1: line
                path.addLine(to: CGPoint(x: 2, y: 15))
            
                // segment 2: curve
                path.addCurve(to: CGPoint(x: 0, y: 12), // ending point
                    controlPoint1: CGPoint(x: 2, y: 14),
                    controlPoint2: CGPoint(x: 0, y: 14))
            
                // segment 3: line
                path.addLine(to: CGPoint(x: 0, y: 2))
            
                // *********************
                // ****** Top side *****
                // *********************
            
                // segment 4: arc
                path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle
                    radius: 2, // this will make it meet our path line
                    startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
                    endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
                    clockwise: true) // startAngle to endAngle goes in a clockwise direction
            
                // segment 5: line
                path.addLine(to: CGPoint(x: 8, y: 0))
            
                // segment 6: arc
                path.addArc(withCenter: CGPoint(x: 8, y: 2),
                                      radius: 2,
                                      startAngle: CGFloat(3*M_PI_2), // straight up
                    endAngle: CGFloat(0), // 0 radians = straight right
                    clockwise: true)
            
                // *********************
                // ***** Right side ****
                // *********************
            
                // segment 7: line
                path.addLine(to: CGPoint(x: 10, y: 12))
            
                // segment 8: curve
                path.addCurve(to: CGPoint(x: 8, y: 15), // ending point
                    controlPoint1: CGPoint(x: 10, y: 14),
                    controlPoint2: CGPoint(x: 8, y: 14))
            
                // segment 9: line
                path.addLine(to: CGPoint(x: 8, y: 26))
            
                // *********************
                // **** Bottom side ****
                // *********************
            
                // segment 10: line
                path.close() // draws the final line to close the path
            
                return path
            }
            

            注意:上面的一些代码可以通过在单个命令中添加一条线和一条弧来减少(因为弧有一个隐含的起点)。详情请见here

            画出路径

            我们可以在图层或drawRect中绘制路径。

            方法一:在图层中绘制路径

            我们的自定义类如下所示。当视图初始化时,我们将 Bezier 路径添加到新的CAShapeLayer

            import UIKit
            class MyCustomView: UIView {
            
                override init(frame: CGRect) {
                    super.init(frame: frame)
                    setup()
                }
            
                required init?(coder aDecoder: NSCoder) {
                    super.init(coder: aDecoder)
                    setup()
                }
            
                func setup() {
            
                    // Create a CAShapeLayer
                    let shapeLayer = CAShapeLayer()
            
                    // The Bezier path that we made needs to be converted to 
                    // a CGPath before it can be used on a layer.
                    shapeLayer.path = createBezierPath().cgPath
            
                    // apply other properties related to the path
                    shapeLayer.strokeColor = UIColor.blue.cgColor
                    shapeLayer.fillColor = UIColor.white.cgColor
                    shapeLayer.lineWidth = 1.0
                    shapeLayer.position = CGPoint(x: 10, y: 10)
            
                    // add the new layer to our custom view
                    self.layer.addSublayer(shapeLayer)
                }
            
                func createBezierPath() -> UIBezierPath {
            
                    // see previous code for creating the Bezier path
                }
            }
            

            然后像这样在 View Controller 中创建我们的视图

            override func viewDidLoad() {
                super.viewDidLoad()
            
                // create a new UIView and add it to the view controller
                let myView = MyCustomView()
                myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
                myView.backgroundColor = UIColor.yellow
                view.addSubview(myView)
            
            }
            

            我们得到...

            嗯,这有点小,因为我硬编码了所有数字。不过,我可以放大路径大小,如下所示:

            let path = createBezierPath()
            let scale = CGAffineTransform(scaleX: 2, y: 2)
            path.apply(scale)
            shapeLayer.path = path.cgPath
            

            方法二:在draw中绘制路径

            使用draw比绘制到图层要慢,所以如果不需要,不推荐使用。

            这是我们自定义视图的修改代码:

            import UIKit
            class MyCustomView: UIView {
            
                override func draw(_ rect: CGRect) {
            
                    // create path (see previous code)
                    let path = createBezierPath()
            
                    // fill
                    let fillColor = UIColor.white
                    fillColor.setFill()
            
                    // stroke
                    path.lineWidth = 1.0
                    let strokeColor = UIColor.blue
                    strokeColor.setStroke()
            
                    // Move the path to a new location
                    path.apply(CGAffineTransform(translationX: 10, y: 10))
            
                    // fill and stroke the path (always do these last)
                    path.fill()
                    path.stroke()
            
                }
            
                func createBezierPath() -> UIBezierPath {
            
                    // see previous code for creating the Bezier path
                }
            }
            

            这给了我们相同的结果...

            进一步研究

            真的建议查看以下材料。它们最终使我可以理解贝塞尔路径。 (并教我如何发音:/ˈbɛ zi eɪ/。)

            【讨论】:

            • 如果frame的视图发生变化怎么办?当方向发生变化时,我们如何调整形状的大小?
            • @ozgur,至少有两个选项。如我在上面的示例中所示,一种方法是进行缩放(并可能平移)变换。另一种选择是根据新框架重新计算贝塞尔路径。在上面的示例中,我将所有数字硬编码到 Bezier 路径中。但是,当我在实际项目中使用贝塞尔路径时,我会根据帧大小确定贝塞尔值。当框架(或更可能是边界)发生变化时,我会重新计算贝塞尔路径。
            • @ozgur, layoutSubviews 听起来绝对是合适的地方。我会说,如果这有效,然后保持在那里。 Here is an example of where I used a Bezier path.(滚动到底部。)出于某种原因,我没有把它放在layoutSubviews 中,但我现在不记得为什么了。我可能应该将此提交给code review。我不是专家。我刚刚做了上面的答案来学习如何自己做贝塞尔路径。
            • @BohdanSavych,draw 方法已经属于视图,所以你不需要添加它。这是UIView 为绘图而定义的标准方法。我们只是在这里覆盖它,以便我们可以在视图上进行自己的绘图。
            • @Suragch 我一直虔诚地遵循与您相同的方法(主要是因为我在开始绘画时找到了您的答案)但我很好奇 为什么“使用绘制比绘制到图层要慢,所以如果你不需要它,这不是推荐的方法。”我知道它覆盖draw 会在每次更改帧时重绘所有内容,但更改layoutSubviews 中的路径是否相同?
            【解决方案7】:

            在代码生成的 UIView 上绘制 UIBezierPath,您可以使用 UIView 触摸事件,如下所示。为 Touch start point 和 Touch end Point 创建全局变量,如下所示:

            CGPoint startingPoint;
            CGPoint endingPoint;
            

            然后使用 UIView Touchevents 绘制 UIBezierPath 如下:

              /*Touch Start*/
            - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
              {
                  UITouch *touch = [[event allTouches] anyObject];
                  startingPoint = [touch locationInView:self];
             }
            /*Touch End*/
            - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
              {
            
             }
            /*Touch Move*/
            -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
                 UITouch *touch = [touches anyObject];
                  endingPoint = [touch locationInView:self];
                  [self makeLineLayer:self.layer lineFromPointA:startingPoint 
                  toPointB:endingPoint];
               }
            /*Draw UIBezierPath*/
            -(void)makeLineLayer:(CALayer *)layer lineFromPointA:(CGPoint)pointA 
                  toPointB:(CGPoint)pointB
             {
                  CAShapeLayer *line = [CAShapeLayer layer];
                  UIBezierPath *linePath=[UIBezierPath bezierPath];
                  [linePath moveToPoint: pointA];// Start Point
                  [linePath addLineToPoint:pointB];//End Point
                   line.path=linePath.CGPath;
                   line.fillColor = nil;
                   line.opacity = 2.0;
                   line.lineWidth = 4.0;
                   line.strokeColor = [UIColor redColor].CGColor;
                   [layer addSublayer:line];
             }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-08-04
              • 2015-01-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-02-13
              相关资源
              最近更新 更多