【问题标题】:How do you draw a line programmatically from a view controller?您如何以编程方式从视图控制器中绘制一条线?
【发布时间】:2013-05-26 15:11:54
【问题描述】:

我有一个UIViewController。如何在其中一个以编程方式创建的视图中画一条线?

【问题讨论】:

  • 不,你不能。绘图应该由视图而不是控制器完成。你必须改变你的设计。
  • @xlc 正如 Rob 的回答所示,您可以通过 VC 进行操作。如果您真的想提供帮助,请解释贝塞尔技术的危害。
  • 我们可以看一些代码吗?我同意,这是一个真实的问题,但我(也许我们)对演示您正在谈论的内容的代码反应最好。

标签: ios objective-c uiview uikit


【解决方案1】:

有两种常见的技术。

  1. 使用CAShapeLayer

    • 创建一个UIBezierPath(将坐标替换为您想要的任何内容):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
      
    • 创建一个使用UIBezierPathCAShapeLayer

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [path CGPath];
      shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
      shapeLayer.lineWidth = 3.0;
      shapeLayer.fillColor = [[UIColor clearColor] CGColor];
      
    • CAShapeLayer 添加到视图层:

      [self.view.layer addSublayer:shapeLayer];
      

    在以前版本的 Xcode 中,您必须手动添加 QuartzCore.framework to your project's "Link Binary with Libraries" 并在 .m 文件中导入 <QuartzCore/QuartzCore.h> 标头,但这不再需要(如果您有“启用模块”和“自动链接框架”)构建设置已打开)。

  2. 另一种方法是继承UIView,然后在drawRect方法中使用CoreGraphics调用:

    • 创建一个UIView 子类并定义一个drawRect 来画线。

      您可以使用 Core Graphics 做到这一点:

      - (void)drawRect:(CGRect)rect {
          CGContextRef context = UIGraphicsGetCurrentContext();
      
          CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);
          CGContextSetLineWidth(context, 3.0);
          CGContextMoveToPoint(context, 10.0, 10.0);
          CGContextAddLineToPoint(context, 100.0, 100.0);
          CGContextDrawPath(context, kCGPathStroke);
      }
      

      或者使用UIKit:

      - (void)drawRect:(CGRect)rect {
          UIBezierPath *path = [UIBezierPath bezierPath];
          [path moveToPoint:CGPointMake(10.0, 10.0)];
          [path addLineToPoint:CGPointMake(100.0, 100.0)];
          path.lineWidth = 3;
          [[UIColor blueColor] setStroke];
          [path stroke];
      }
      
    • 然后您可以将此视图类用作您的 NIB/故事板或视图的基类,或者您可以让您的视图控制器以编程方式将其添加为子视图:

      PathView *pathView = [[PathView alloc] initWithFrame:self.view.bounds];
      pathView.backgroundColor = [UIColor clearColor];
      
      [self.view addSubview: pathView];
      

上述两种方法的 Swift 版本如下:

  1. CAShapeLayer:

    // create path
    
    let path = UIBezierPath()
    path.move(to: CGPoint(x: 10, y: 10))
    path.addLine(to: CGPoint(x: 100, y: 100))
    
    // Create a `CAShapeLayer` that uses that `UIBezierPath`:
    
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.strokeColor = UIColor.blue.cgColor
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = 3
    
    // Add that `CAShapeLayer` to your view's layer:
    
    view.layer.addSublayer(shapeLayer)
    
  2. UIView 子类:

    class PathView: UIView {
    
        var path: UIBezierPath?           { didSet { setNeedsDisplay() } }
        var pathColor: UIColor = .blue    { didSet { setNeedsDisplay() } }
    
        override func draw(_ rect: CGRect) {
            // stroke the path
    
            pathColor.setStroke()
            path?.stroke()
        }
    
    }
    

    并将其添加到您的视图层次结构中:

    let pathView = PathView()
    pathView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(pathView)
    
    NSLayoutConstraint.activate([
        pathView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        pathView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        pathView.topAnchor.constraint(equalTo: view.topAnchor),
        pathView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    
    pathView.backgroundColor = .clear
    
    let path = UIBezierPath()
    path.move(to: CGPoint(x: 10, y: 10))
    path.addLine(to: CGPoint(x: 100, y: 100))
    path.lineWidth = 3
    
    pathView.path = path
    

    上面,我以编程方式添加PathView,但您也可以通过 IB 添加它,只需以编程方式设置其path

【讨论】:

  • @Rob:这工作得很好。我想问你,我有一个线距 AB 和一个与 AB 的角度,那么在那种情况下如何获得起点和终点?请帮帮我。
  • 我在我的视图中添加了uigesture,如果我在上面的注释中添加它,我有触摸起点和移动点直线工作正常。但是当我从起点本身触摸侧面时,它占据了颜色@Rob
  • 使用 CAShapeLayer 方法时,您可以(并且可能会想要)将线帽设置为圆角,以便线条在连接时看起来平滑,shapeLayer.lineCap = kCALineCapRound;
  • 或者,如果您想要在连接时看起来平滑的多条线路,您也可以有一个UIBezierPath,重复addLineToPoint/addLine(to:) 调用。然后你可以选择你喜欢shapeLayer.lineJoin = kCALineJoinRound还是kCALineJoinBevel或者kCALineJoinMiter
  • 感谢代码 sn-p,它为我节省了很多时间。如果你打算绘制一个动态视图,对某些事件做出反应,你需要在开始绘制时清理子层:self.layer.sublayers = nil
【解决方案2】:

创建一个 UIView 并将其添加为视图控制器视图的子视图。您可以将此子视图的高度或宽度修改为非常小,使其看起来像一条线。如果需要画对角线可以修改子视图的transform属性。

例如绘制黑色水平线。这是从您的视图控制器的实现中调用的

UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];

【讨论】:

  • 我倾向于同意对于分隔元素(例如 UI 元素之间的线),这是阻力最小的路径。渲染到位图以支持 CoreAnimation 层或在自定义视图中实现绘图处理程序的替代方案要复杂得多。这可能硬件加速也更好,只要它只有一条线。
  • 我很好奇为什么这是一个糟糕的想法。我想避免此解决方案在我自己的代码中可能导致的任何问题。
  • @sangony 我在我的代码中也使用了这种技术。我也想知道为什么这是一个糟糕的主意?请帮忙解释一下。
【解决方案3】:

这里有一个很酷的技巧,你可能会觉得有用:Using blocks for drawing to avoid subclassing in Objective-C

在您的项目中包含文章的通用视图子类,然后您可以将这种代码放入您的视图控制器中以动态创建一个画线的视图:

DrawView* drawableView = [[[DrawView alloc] initWithFrame:CGRectMake(0,0,320,50)] autorelease];
drawableView.drawBlock = ^(UIView* v,CGContextRef context)
{
  CGPoint startPoint = CGPointMake(0,v.bounds.size.height-1);
  CGPoint endPoint = CGPointMake(v.bounds.size.width,v.bounds.size.height-1);

  CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
  CGContextSetLineWidth(context, 1);
  CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
  CGContextAddLineToPoint(context, endPoint.x + 0.5, endPoint.y + 0.5);
  CGContextStrokePath(context);
};
[self.view addSubview:drawableView];

【讨论】:

    【解决方案4】:

    斯威夫特 3:

    let path = UIBezierPath()
    path.move(to: CGPoint(x: 10, y: 10))
    path.addLine(to: CGPoint(x: 100, y: 100))
    
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.strokeColor = UIColor.blue.cgColor
    shapeLayer.lineWidth = 3.0
    
    view.layer.addSublayer(shapeLayer)
    

    【讨论】:

      【解决方案5】:

      你可以使用 UIImageView 在上面画线。

      但是,它允许跳过子分类。而且由于我不太倾向于 Core Graphics 仍然可以使用它。你可以把它放进去 - ViewDidLoad

        UIGraphicsBeginImageContext(self.view.frame.size);
        [self.myImageView.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
      
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), 50, 50);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), 200, 200);
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        CGContextFlush(UIGraphicsGetCurrentContext());
        self.myImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
      

      除了 Rob 的回答,为了快速,第三种方法是使用 UIImageView - 掩盖它 - xib 的视图。 (这是在 xcode 5 中的 xib 上拖动时的默认 UIImageView 外观)

      干杯和+1!

      【讨论】:

        【解决方案6】:

        在你的视图中绘制非常简单,@Mr.ROB 说两种方法我采用第一种方法。

        只需将代码复制粘贴到你们想要的位置即可。

        -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
        {
            UITouch *touch = [[event allTouches] anyObject];
             startingPoint = [touch locationInView:self.view];
        
            NSLog(@"Touch starting point = x : %f Touch Starting Point = y : %f", touchPoint.x, touchPoint.y);
        }
        -(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
        {
        
        }
        -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
        {
            UITouch *touch = [[event allTouches] anyObject];
             touchPoint = [touch locationInView:self.view];
        
            NSLog(@"Touch end point =x : %f Touch end point =y : %f", touchPoint.x, touchPoint.y);
        }
        -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
        {
        
            UITouch *touch = [[event allTouches] anyObject];
            touchPoint = [touch locationInView:self.view];
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
            [path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
            startingPoint=touchPoint;
            CAShapeLayer *shapeLayer = [CAShapeLayer layer];
            shapeLayer.path = [path CGPath];
            shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
            shapeLayer.lineWidth = 3.0;
            shapeLayer.fillColor = [[UIColor redColor] CGColor];
            [self.view.layer addSublayer:shapeLayer];
        
            NSLog(@"Touch moving point =x : %f Touch moving point =y : %f", touchPoint.x, touchPoint.y);
            [self.view setNeedsDisplay];
        
        
        }
        - (void)tapGestureRecognizer:(UIGestureRecognizer *)recognizer {
            CGPoint tappedPoint = [recognizer locationInView:self.view];
            CGFloat xCoordinate = tappedPoint.x;
            CGFloat yCoordinate = tappedPoint.y;
        
            NSLog(@"Touch Using UITapGestureRecognizer x : %f y : %f", xCoordinate, yCoordinate);
        }
        

        它会像一条线一样绘制,手指移动的地方

        【讨论】:

          【解决方案7】:

          你不应该真的,但如果出于某种原因它对你有意义,你可以创建一个 UIView 的子类,例如称为DelegateDrawView,它接受一个实现类似方法的委托

          - (void)delegateDrawView:(DelegateDrawView *)aDelegateDrawView drawRect:(NSRect)dirtyRect
          

          然后在方法中 - [DelegateDrawView drawRect:] 你应该调用你的委托方法。

          但是为什么要将视图代码放在控制器中。

          你最好创建一个 UIView 的子类,在它的两个角之间画一条线,你可以有一个属性来设置哪两个,然后将视图从你的视图控制器定位到你想要的位置。

          【讨论】:

            【解决方案8】:

            斯威夫特 5.4

            使用高度为 1 或 2 磅的 UIView 并将其添加为视图控制器视图的子视图。

            class Separator: UIView {
            
                let line = UIView()
            
                override init(frame: CGRect) {
                    super.init(frame: frame)
                    configure()
                }
            
            
                required init?(coder: NSCoder) {
                    fatalError("init(coder:) has not been implemented")
                }
            
                private func configure() {
                    backgroundColor = .red
            
                    addSubview(line)
                    line.translatesAutoresizingMaskIntoConstraints = false
                    line.backgroundColor = .secondaryLabelColor
            
                    NSLayoutConstraint.activate([
                        line.centerYAnchor.constraint(equalTo: self.centerYAnchor),
                        line.centerXAnchor.constraint(equalTo: self.centerXAnchor),
                        line.heightAnchor.constraint(equalToConstant: Pad.separatorHeight),
                        line.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.8 )
                    ])
                }
            }
            

            然后将其添加到您的视图控制器中:

            
            let separator = Separator()
            view.addSubview(separator)
            separator.translatesAutoresizingMaskIntoConstraints = false
            
            NSLayoutConstraint.activate([
                separator.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                separator.topAnchor.constraint(equalTo: view.bottomAnchor),
                separator.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                separator.heightAnchor.constraint(equalToConstant: 72.0)
                ])
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-05-05
              相关资源
              最近更新 更多