从视图控制器编程上如何画一条线?

85

我有一个UIViewController。如何在其中一个编程创建的视图中绘制一条线?


3
不行。绘图应该由视图而不是控制器完成。你需要改变你的设计。 - Bryan Chen
1
@xlc,你可以从VC中实现它,正如Rob的回答所示。如果你真的想有所帮助,请解释一下贝塞尔曲线技术的危害在哪里。 - mkc842
我们能看到一些代码吗?我同意,这是一个真正的问题,但是我(也许我们)最喜欢通过代码来展示你所说的内容。 - bugmagnet
8个回答

190

有两种常见技术。

  1. Using CAShapeLayer:

    • Create a UIBezierPath (replace the coordinates with whatever you want):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
      
    • Create a CAShapeLayer that uses that UIBezierPath:

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [path CGPath];
      shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
      shapeLayer.lineWidth = 3.0;
      shapeLayer.fillColor = [[UIColor clearColor] CGColor];
      
    • Add that CAShapeLayer to your view's layer:

      [self.view.layer addSublayer:shapeLayer];
      

    In previous versions of Xcode, you had to manually add QuartzCore.framework to your project's "Link Binary with Libraries" and import the <QuartzCore/QuartzCore.h> header in your .m file, but that's not necessary anymore (if you have the "Enable Modules" and "Link Frameworks Automatically" build settings turned on).

  2. The other approach is to subclass UIView and then use CoreGraphics calls in the drawRect method:

    • Create a UIView subclass and define a drawRect that draws your line.

      You can do this with 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);
      }
      

      Or using 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];
      }
      
    • Then you can either use this view class as the base class for your NIB/storyboard or view, or you can have your view controller programmatically add it as a subview:

      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 subclass:

    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()
        }
    
    }
    

    And add it to your view hierarchy:

    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
    

    Above, I'm adding PathView programmatically, but you can add it via IB, too, and just set its path programmatically.


2
@Rob:这个完美地运作了。我想问一下,如果我有一条线段AB和一个与AB的夹角,那么在这种情况下如何获得起点和终点?请帮帮我。 - Manthan
1
我在我的视图中添加了手势,我有触摸起点和移动点。如果我将其添加到上面的评论中,直线可以正常工作。但是,当我从起始点的侧面触摸时,它会占据颜色@Rob。 - Kishore Kumar
1
使用CAShapeLayer方法时,您可以(并且可能希望)将线端设置为圆形,以使连接的线看起来平滑,shapeLayer.lineCap = kCALineCapRound; - Albert Renshaw
1
或者,如果您有多行要连接并且希望它们看起来平滑,您也可以使用单个UIBezierPath,其中包含重复的addLineToPoint/addLine(to:)调用。然后,您可以选择 shapeLayer.lineJoin = kCALineJoinRoundkCALineJoinBevelkCALineJoinMiter - Rob
1
感谢您提供代码片段,它为我节省了不少时间。如果您打算绘制一个动态视图以响应某些事件,则需要在绘制的开始处清除子层,使用以下代码:self.layer.sublayers = nil - Vilmir

17

创建一个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 层,或在自定义视图中实现绘制处理程序,但这需要更多的工作量。只要是一条线,这种方法可能也更容易进行硬件加速。 - marko
5
我对为什么这是一个可怕的想法感到好奇。我希望避免这个解决方案可能会在我的代码中引起的任何问题。 - Jeff Ames
@sangony 我在我的代码中也使用了这种技术。我也想知道为什么这是一个糟糕的想法?请帮忙解释一下。 - Joe Huang

11

Swift 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)

9

这是一个可能会对你有用的很酷的技巧: 使用块绘图来避免在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];

6

您可以使用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中拖动UIImageView时的默认外观)
祝好并+1!

2
在您的视图内绘制非常简单,@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);
}

它会像一条线一样绘制,随着手指移动而走。

2

虽然不太建议这样做,但如果你确实有理由这么做的话,可以创建一个名为DelegateDrawView的UIView子类,该子类接受一个实现了如下方法的代理:

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

然后在方法 [DelegateDrawView drawRect:] 中,您应该调用委托方法。

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

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


2

Swift 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)
    ])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接