¿Cómo se dibuja una línea mediante programación desde un controlador de vista?

82

Tengo un UIViewController. ¿Cómo trazo una línea en una de sus vistas creadas mediante programación?

mkc842
fuente
2
No, no puedes. El dibujo debe realizarse por vista, no por controlador. Tienes que cambiar tu diseño.
Bryan Chen
1
@xlc PUEDES hacerlo desde un VC, como muestra la respuesta de Rob. Si realmente quieres ayudar, explica cuál es el daño en la técnica Bézier.
mkc842
9
"Cerrado como si no fuera una pregunta real". "Es difícil saber qué se pregunta aquí". "No se puede responder razonablemente". ¡Que broma! La pregunta es perfectamente clara y Rob no pareció tener problemas para responderla perfectamente. Si tiene un problema con mi pregunta, ¿por qué no explicar su problema en lugar de cerrarlo por motivos obviamente falsos?
mkc842
¿Podemos ver algún código? Estoy de acuerdo, es una pregunta real, pero yo (quizás nosotros) respondemos mejor al código que demuestra de lo que estás hablando.
bugmagnet

Respuestas:

186

Hay dos técnicas comunes.

  1. Usando CAShapeLayer:

    • Crea un UIBezierPath(reemplaza las coordenadas con lo que quieras):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
      
    • Crea un CAShapeLayerque use eso UIBezierPath:

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [path CGPath];
      shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
      shapeLayer.lineWidth = 3.0;
      shapeLayer.fillColor = [[UIColor clearColor] CGColor];
      
    • Agregue eso CAShapeLayera la capa de su vista:

      [self.view.layer addSublayer:shapeLayer];
      

    En versiones anteriores de Xcode, tenía que agregar manualmente QuartzCore.framework al "Enlace binario con bibliotecas" de su proyecto e importar el <QuartzCore/QuartzCore.h>encabezado en su archivo .m, pero eso ya no es necesario (si tiene "Activar módulos" y "Enlace Frameworks Automáticamente "configuración de compilación activada).

  2. El otro enfoque es crear una subclase UIViewy luego usar llamadas a CoreGraphics en el drawRectmétodo:

    • Cree una UIViewsubclase y defina una drawRectque dibuje su línea.

      Puede hacer esto con 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);
      }
      

      O usando 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];
      }
      
    • Luego, puede usar esta clase de vista como la clase base para su NIB / storyboard o vista, o puede hacer que su controlador de vista lo agregue programáticamente como una subvista:

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

Las interpretaciones Swift de los dos enfoques anteriores son las siguientes:

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

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

    Y agréguelo a su jerarquía de vista:

    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
    

    Arriba, estoy agregando PathViewprogramáticamente, pero también puede agregarlo a través de IB, y simplemente configurarlo pathprogramáticamente.

Robar
fuente
3
Increíble. Gracias por la completa respuesta.
mkc842
@Rob: Esto funciona perfectamente bien. Quiero preguntarle que tengo una distancia lineal AB y un ángulo desde AB, entonces, ¿cómo obtener los puntos de inicio y finalización en esa situación? Por favor, ayúdame.
Manthan
Agregué uigesture a mi vista, y tengo un punto de inicio táctil y un punto de movimiento si agrego eso en el comentario anterior, la línea recta funciona correctamente. pero cuando toco un lado desde el punto de inicio, ocupa el color @Rob
Kishore Kumar
Cuando se utiliza el método CAShapeLayer puede (y probablemente querrá) establezca los remates de las líneas a redondeada de manera que las líneas de aspecto suave, ya que conectan,shapeLayer.lineCap = kCALineCapRound;
Albert Renshaw
O, si tiene varias líneas que desea que se vean sin problemas a medida que se conectan, también puede tener una única UIBezierPath, con llamadas addLineToPoint/ repetidas addLine(to:). Entonces puede elegir si prefiere shapeLayer.lineJoin = kCALineJoinRoundo kCALineJoinBevelo kCALineJoinMiter.
Rob
15

Cree una UIView y agréguela como una subvista de la vista de su controlador de vista. Puede modificar el alto o el ancho de esta subvista para que sea muy pequeño para que se vea como una línea. Si necesita dibujar una línea diagonal, puede modificar la propiedad de transformación de las subvistas.

por ejemplo, dibuje una línea horizontal negra. Esto se llama desde la implementación de su controlador de vista

UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];
Jeff Ames
fuente
Me inclinaría a estar de acuerdo en que para un elemento separador (por ejemplo, una línea entre elementos de la interfaz de usuario) este es el camino de menor resistencia. La alternativa de renderizar en un mapa de bits para respaldar una capa CoreAnimation o implementar un controlador de dibujo en una vista personalizada es mucho más complicada. Probablemente, este hardware también acelera mejor, siempre que sea solo una línea.
marko
4
Tengo curiosidad sobre el razonamiento de por qué esto es una idea terrible. Me gustaría evitar cualquier problema que esta solución pueda causar en mi propio código.
Jeff Ames
@sangony También uso esta técnica en mi código. También me pregunto por qué es una idea terrible. Por favor, ayude a explicar.
Joe Huang
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)
Baraa Al-Tabbaa
fuente
¡Es genial! Gracias.
Zhanserik
8

Aquí hay una técnica interesante que puede resultarle útil: usar bloques para dibujar para evitar subclases en Objective-C

Incluya la subclase de vista de propósito general del artículo en su proyecto, entonces este es el tipo de código que puede poner en su controlador de vista para crear una vista sobre la marcha que dibuje una línea:

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];
Pierre Houston
fuente
6

Puede usar UIImageView para dibujar líneas.

Sin embargo, permite omitir la subclasificación. Y como estoy poco inclinado a Core Graphics, todavía puedo usarlo. Puedes ponerlo en ...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();

Además de la respuesta de Rob, para una rápida, el tercer enfoque es usar un UIImageView- cubrir con él - la vista de xib. (Esa es la apariencia predeterminada de UIImageView cuando se arrastra en xib en xcode 5)

Saludos y +1!

Khunshan
fuente
2

Realmente no debería, pero si por alguna razón tiene sentido para usted, podría crear una subclase de UIView, llamada DelegateDrawViewpor ejemplo, que toma un delegado que implementa un método como

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

y luego en los métodos, [DelegateDrawView drawRect:]debe llamar a su método delegado.

Pero, ¿por qué querrías poner el código de vista en tu controlador?

Es mejor crear una subclase de UIView, que dibuje una línea entre dos de sus esquinas, puede tener una propiedad para establecer cuáles dos y luego colocar la vista donde la desee desde su controlador de vista.

Día de nathan
fuente
1

Dibujar dentro de su vista es muy simple, @ Mr.ROB dijo 2 método Tomé el primer método.

Simplemente copie y pegue el código donde lo desee.

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

Dibujará como una línea, donde el dedo se mueve yendo

Kishore Kumar
fuente