Wie zeichnet man programmgesteuert eine Linie aus einem View Controller?

82

Ich habe eine UIViewController. Wie zeichne ich eine Linie in einer der programmgesteuert erstellten Ansichten?

mkc842
quelle
2
Nein, kannst du nicht. Das Zeichnen sollte nach Ansicht und nicht nach Steuerung erfolgen. Sie müssen Ihr Design ändern.
Bryan Chen
1
@xlc Sie können es von einem VC aus tun, wie Robs Antwort zeigt. Wenn Sie tatsächlich hilfreich sein möchten, erklären Sie, was der Schaden in der Bezier-Technik ist.
mkc842
9
"Geschlossen als keine wirkliche Frage." "Es ist schwer zu sagen, was hier gefragt wird." "Kann nicht vernünftig beantwortet werden." Was für ein Witz! Die Frage ist völlig klar und Rob schien keine Probleme damit zu haben, sie perfekt zu beantworten. Wenn Sie ein Problem mit meiner Frage haben, warum erklären Sie Ihr Problem nicht, anstatt es aus offensichtlich falschen Gründen zu schließen?
mkc842
Können wir Code sehen? Ich stimme zu, es ist eine echte Frage, aber ich (vielleicht wir) antworte am besten auf Code, der zeigt, wovon Sie sprechen.
Bugmagnet

Antworten:

184

Es gibt zwei gängige Techniken.

  1. Verwenden von CAShapeLayer:

    • Erstellen Sie eine UIBezierPath(ersetzen Sie die Koordinaten durch was auch immer Sie wollen):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
    • Erstellen Sie eine CAShapeLayer, die das verwendet UIBezierPath:

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [path CGPath];
      shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
      shapeLayer.lineWidth = 3.0;
      shapeLayer.fillColor = [[UIColor clearColor] CGColor];
    • Fügen CAShapeLayerSie das der Ebene Ihrer Ansicht hinzu:

      [self.view.layer addSublayer:shapeLayer];

    In früheren Versionen von Xcode mussten Sie QuartzCore.framework manuell zum "Link Binary with Libraries" Ihres Projekts hinzufügen und den <QuartzCore/QuartzCore.h>Header in Ihre .m-Datei importieren. Dies ist jedoch nicht mehr erforderlich (wenn Sie über "Enable Modules" und "Link" verfügen Frameworks automatisch "Build-Einstellungen aktiviert).

  2. Der andere Ansatz besteht darin UIView, CoreGraphics- Aufrufe in der drawRectMethode zu unterklassifizieren und dann zu verwenden :

    • Erstellen Sie eine UIViewUnterklasse und definieren Sie eine drawRect, die Ihre Linie zeichnet.

      Sie können dies mit Core Graphics tun:

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

      Oder mit 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];
      }
    • Anschließend können Sie diese Ansichtsklasse entweder als Basisklasse für Ihre NIB / Ihr Storyboard oder Ihre Ansicht verwenden oder Ihren Ansichts-Controller programmgesteuert als Unteransicht hinzufügen lassen:

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

Die Swift-Wiedergaben der beiden obigen Ansätze lauten wie folgt:

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

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

    Und fügen Sie es Ihrer Ansichtshierarchie hinzu:

    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

    Oben PathViewfüge ich programmgesteuert hinzu , aber Sie können es auch über IB hinzufügen und es einfach pathprogrammgesteuert einstellen .

rauben
quelle
3
Genial. Danke für die gründliche Antwort.
mkc842
@Rob: Das funktioniert einwandfrei. Ich möchte Sie fragen, ob ich einen Linienabstand AB und einen Winkel von AB habe. Wie bekomme ich dann Start- und Endpunkte in dieser Situation? Bitte hilf mir.
Manthan
Ich habe Uigesture zu meiner Ansicht hinzugefügt, und ich habe Berührungsstartpunkt und Bewegungspunkt, wenn ich das im obigen Kommentar hinzufüge. Gerade Linie funktioniert richtig. Aber wenn ich seitlich vom Startpunkt selbst berühre, nimmt es die Farbe @Rob
Kishore Kumar
Wenn Sie die CAShapeLayer-Methode verwenden, können (und möchten Sie wahrscheinlich) die Linienkappen auf abgerundet setzen, damit die Linien beim Verbinden glatt aussehen.shapeLayer.lineCap = kCALineCapRound;
Albert Renshaw
Wenn Sie mehrere Leitungen haben, die beim Verbinden reibungslos aussehen sollen, können Sie auch eine einzige UIBezierPathmit wiederholten addLineToPoint/ addLine(to:)Anrufen verwenden. Dann können Sie auswählen, ob Sie shapeLayer.lineJoin = kCALineJoinRoundoder kCALineJoinBeveloder bevorzugen kCALineJoinMiter.
Rob
13

Erstellen Sie eine UIView und fügen Sie sie als Unteransicht der Ansicht Ihres View Controllers hinzu. Sie können die Höhe oder Breite dieser Unteransicht so ändern, dass sie sehr klein ist, sodass sie wie eine Linie aussieht. Wenn Sie eine diagonale Linie zeichnen müssen, können Sie die Transformationseigenschaft für Unteransichten ändern.

zB schwarze horizontale Linie zeichnen. Dies wird in der Implementierung Ihres View Controllers aufgerufen

UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];
Jeff Ames
quelle
Ich würde gerne zustimmen, dass dies für ein Trennelement (z. B. eine Linie zwischen UI-Elementen) der Weg des geringsten Widerstands ist. Die Alternative, entweder in eine Bitmap zu rendern, um eine CoreAnimation-Ebene zu sichern, oder einen Zeichenhandler in einer benutzerdefinierten Ansicht zu implementieren, ist weitaus aufwändiger. Diese Hardware beschleunigt sich wahrscheinlich auch besser, vorausgesetzt, es handelt sich nur um eine Zeile.
Marko
4
Ich bin gespannt, warum dies eine schreckliche Idee ist. Ich möchte Probleme vermeiden, die diese Lösung in meinem eigenen Code verursachen könnte.
Jeff Ames
@sangony Ich verwende diese Technik auch in meinem Code. Ich frage mich auch, warum das eine schreckliche Idee ist. Bitte helfen Sie zu erklären.
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
quelle
Es ist toll! Danke dir.
Zhanserik
8

Hier ist eine coole Technik, die Sie vielleicht nützlich finden: Verwenden von Blöcken zum Zeichnen, um Unterklassen in Objective-C zu vermeiden

Fügen Sie die Unterklasse für allgemeine Ansichten des Artikels in Ihr Projekt ein. Dann können Sie diese Art von Code in Ihren Ansichts-Controller einfügen, um im laufenden Betrieb eine Ansicht zu erstellen, die eine Linie zeichnet:

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
quelle
6

Mit UIImageView können Sie Linien zeichnen.

Es erlaubt jedoch, die Unterklassifizierung zu überspringen. Und da ich wenig zu Core Graphics neige, kann ich es immer noch verwenden. Sie können es einfach eingeben -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();

Ergänzung zu Robs Antwort: Für einen schnellen Schritt besteht der dritte Ansatz darin, UIImageViewdie Ansicht von xib zu vertuschen. (Dies ist das Standard-UIImageView-Erscheinungsbild beim Ziehen auf xib in xcode 5)

Prost und +1!

Khunshan
quelle
2

Sie sollten es nicht wirklich tun, aber wenn es aus irgendeinem Grund für Sie sinnvoll ist, können Sie eine Unterklasse von UIView erstellen, die DelegateDrawViewbeispielsweise aufgerufen wird und einen Delegaten benötigt, der eine Methode wie implementiert

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

und dann in den Methoden - [DelegateDrawView drawRect:]Sie sollten Ihre Delegate-Methode aufrufen.

Aber warum sollten Sie Ansichtscode in Ihren Controller einfügen?

Es ist besser, eine Unterklasse von UIView zu erstellen, die eine Linie zwischen zwei ihrer Ecken zeichnet. Sie können eine Eigenschaft festlegen, um welche zwei festzulegen, und dann die Ansicht an der gewünschten Position von Ihrem Ansichts-Controller aus positionieren.

Nathan Day
quelle
1

In Ihre Ansicht zu zeichnen ist sehr einfach. @ Mr.ROB sagte 2 Methode, ich habe die erste Methode gewählt.

Kopieren Sie einfach den Code, wo Sie ihn haben möchten.

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

Es wird wie eine Linie gezeichnet, wo sich der Finger bewegt

Kishore Kumar
quelle