Как программно нарисовать линию из контроллера представления?

82

У меня есть UIViewController. Как нарисовать линию в одном из программно созданных представлений?

mkc842
источник
2
Нет, не можешь. Рисование должно выполняться представлением, а не контроллером. Вы должны изменить свой дизайн.
Брайан Чен
1
@xlc Вы МОЖЕТЕ сделать это из ВК, как показывает ответ Роба. Если вы действительно хотите быть полезным, объясните, чем вредна техника Безье.
mkc842
Можем ли мы увидеть код? Я согласен, это реальный вопрос, но я (возможно, мы) лучше всего отвечаю на код, демонстрирующий то, о чем вы говорите.
bugmagnet

Ответы:

186

Есть два распространенных метода.

  1. Использование CAShapeLayer:

    • Создайте UIBezierPath(замените координаты на все, что хотите):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
      
    • Создайте, CAShapeLayerкоторый использует это UIBezierPath:

      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 в свой проект «Связать двоичный<QuartzCore/QuartzCore.h> файл с библиотеками» и импортировать заголовок в свой .m файл, но в этом больше нет необходимости (если у вас есть «Включить модули» и «Связать Frameworks "Автоматически" включены настройки сборки).

  2. Другой подход - создать подкласс, UIViewа затем использовать вызовы CoreGraphics в drawRectметоде:

    • Создайте 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программно установить .

Роб
источник
@ Роб: Это отлично работает. Я хочу спросить вас, что у меня есть расстояние по прямой AB и угол от AB, тогда как получить начальную и конечную точки в этой ситуации? Пожалуйста, помогите мне.
Manthan
Я добавил uigesture к моему представлению, и у меня есть начальная точка касания и точка перемещения, если я добавлю это в приведенном выше комментарии, прямая линия работает правильно. но когда я касаюсь сбоку от самой начальной точки, она занимает цвет @Rob
Kishore Kumar
При использовании метода CAShapeLayer вы можете (и, вероятно, захотите) установить концы линий на закругленные, чтобы линии выглядели плавными при соединении,shapeLayer.lineCap = kCALineCapRound;
Альберт Реншоу
Или, если у вас есть несколько линий, которые вы хотите, чтобы они выглядели гладко при соединении, вы также можете иметь одну UIBezierPathс повторяющимися addLineToPoint/ addLine(to:)вызовами. Затем вы можете выбрать, предпочитаете ли вы shapeLayer.lineJoin = kCALineJoinRoundили kCALineJoinBevelили kCALineJoinMiter.
Роб
Спасибо за фрагмент кода, он сэкономил мне время. Если вы собираетесь рисовать динамическое представление, реагирующее на некоторые события, вам нужно будет очистить подслой в начале рисования следующим образом: self.layer.sublayers = nil
Вилмир
15

Создайте UIView и добавьте его как часть представления вашего контроллера представления. Вы можете изменить высоту или ширину этого подвида, сделав его очень маленьким, чтобы он выглядел как линия. Если вам нужно нарисовать диагональную линию, вы можете изменить свойство преобразования вложенных представлений.

например, нарисуйте черную горизонтальную линию. Это вызывается из реализации вашего контроллера представления

UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];
Джефф Эймс
источник
Я склонен согласиться с тем, что для разделительного элемента (например, линии между элементами пользовательского интерфейса) это путь наименьшего сопротивления. Гораздо более сложна альтернатива рендеринга в растровое изображение для поддержки слоя CoreAnimation или реализации обработчика отрисовки в пользовательском представлении. Это, наверное, и железо быстрее разгоняется, если только одна строка.
marko
4
Мне любопытно, почему это ужасная идея. Я бы хотел избежать любых проблем, которые это решение могло бы вызвать в моем собственном коде.
Джефф Эймс
@sangony Я тоже использую эту технику в своем коде. Мне также интересно, почему это ужасная идея? Пожалуйста, помогите объяснить.
Джо Хуанг
9

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)
Бараа аль-Таббаа
источник
8

Вот классная техника, которая может вам пригодиться: использование блоков для рисования, чтобы избежать создания подклассов в 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();

В дополнение к ответу Роба, третий подход - использовать UIImageView - прикрыть его - представлением xib. (Это внешний вид UIImageView по умолчанию при перетаскивании на xib в xcode 5)

Ура и +1!

хуншан
источник
2

На самом деле вы этого не должны, но если по какой-то причине это имеет для вас смысл, вы можете создать подкласс UIView, DelegateDrawViewнапример, вызываемый , который принимает делегат, который реализует такой метод, как

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

а потом в методах - [DelegateDrawView drawRect:] вы должны вызвать свой метод делегата.

Но зачем вам помещать код представления в свой контроллер?

Вам лучше создать подкласс UIView, который рисует линию между двумя своими углами, вы можете иметь свойство, чтобы установить, какие два, а затем расположить представление там, где вы хотите, от контроллера представления.

Натан Дэй
источник
1

Рисовать внутри вашего представления очень просто, @ Mr.ROB сказал, что 2 метода, я использовал первый метод.

Просто скопируйте и вставьте код в нужное место.

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

Он будет рисовать как линию, по которой движется палец

Кишор Кумар
источник