Jak programowo narysować linię z kontrolera widoku?

82

Mam UIViewController. Jak narysować linię w jednym z jej widoków utworzonych programowo?

mkc842
źródło
2
Nie, nie możesz. Rysowanie powinno odbywać się za pomocą widoku, a nie kontrolera. Musisz zmienić swój projekt.
Bryan Chen
1
@xlc MOŻESZ to zrobić z VC, jak pokazuje odpowiedź Roba. Jeśli naprawdę chcesz być pomocny, wyjaśnij, na czym polega szkoda w technice beziera.
mkc842
Czy możemy zobaczyć kod? Zgadzam się, to prawdziwe pytanie, ale ja (może my) najlepiej odpowiadam na kod demonstrujący, o czym mówisz.
bugmagnet

Odpowiedzi:

186

Istnieją dwie powszechne techniki.

  1. Używając CAShapeLayer:

    • Utwórz UIBezierPath(zamień współrzędne na dowolne):

      UIBezierPath *path = [UIBezierPath bezierPath];
      [path moveToPoint:CGPointMake(10.0, 10.0)];
      [path addLineToPoint:CGPointMake(100.0, 100.0)];
      
    • Utwórz, CAShapeLayerktóry używa tego UIBezierPath:

      CAShapeLayer *shapeLayer = [CAShapeLayer layer];
      shapeLayer.path = [path CGPath];
      shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
      shapeLayer.lineWidth = 3.0;
      shapeLayer.fillColor = [[UIColor clearColor] CGColor];
      
    • Dodaj to CAShapeLayerdo warstwy widoku:

      [self.view.layer addSublayer:shapeLayer];
      

    W poprzednich wersjach Xcode trzeba było ręcznie dodać QuartzCore.framework do elementu „Połącz<QuartzCore/QuartzCore.h> plik binarny z bibliotekami” projektu i zaimportować nagłówek do pliku .m, ale nie jest to już konieczne (jeśli masz opcje „Włącz moduły” i „Połącz Struktury automatycznie „włączone ustawienia kompilacji).

  2. Drugim podejściem jest podklasa, UIViewa następnie użycie wywołań CoreGraphics w drawRectmetodzie:

    • Utwórz UIViewpodklasę i zdefiniuj, drawRectktóra rysuje twoją linię.

      Możesz to zrobić z 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);
      }
      

      Lub za pomocą 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];
      }
      
    • Następnie możesz użyć tej klasy widoku jako klasy bazowej dla swojego NIB / serii ujęć lub widoku, albo możesz programowo dodać ją jako widok podrzędny do kontrolera widoku:

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

Szybkie wersje dwóch powyższych podejść są następujące:

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

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

    I dodaj go do swojej hierarchii widoków:

    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
    

    Powyżej PathViewdodaję programowo, ale możesz też dodać go przez IB i po prostu ustawić pathprogramowo.

Obrabować
źródło
@Rob: Działa to doskonale. Chcę cię zapytać, czy mam odległość w linii AB i kąt od AB, więc jak uzyskać punkt początkowy i końcowy w tej sytuacji? Proszę pomóż mi.
Manthan
dodałem uigesture do mojego widoku i mam dotykowy punkt początkowy i ruchomy, jeśli dodam, że w powyższym komentarzu linia prosta działa poprawnie. ale kiedy dotykam boku od samego punktu początkowego, zajmuje kolor @Rob
Kishore Kumar
Korzystając z metody CAShapeLayer, możesz (i prawdopodobnie zechcesz) ustawić zakończenia linii na zaokrąglone, aby linie wyglądały gładko podczas łączenia,shapeLayer.lineCap = kCALineCapRound;
Albert Renshaw
Lub, jeśli masz wiele linii, które chcesz wyglądać gładko, gdy się łączą, możesz również mieć jedną UIBezierPath, z powtórzonymi addLineToPoint/ addLine(to:)wywołania. Następnie możesz wybrać, czy wolisz, shapeLayer.lineJoin = kCALineJoinRoundczy kCALineJoinBevellub kCALineJoinMiter.
Rob
Dzięki za fragment kodu, zaoszczędził mi to sporo czasu. Jeśli zamierzasz narysować dynamiczny widok, reagując na niektóre zdarzenia, będziesz musiał wyczyścić podwarstwę na początku losowania za pomocą tego: self.layer.sublayers = nil
Vilmir
15

Utwórz widok UIView i dodaj go jako widok podrzędny widoku kontrolera widoku. Możesz zmodyfikować wysokość lub szerokość tego widoku podrzędnego, aby był bardzo mały, aby wyglądał jak linia. Jeśli chcesz narysować linię ukośną, możesz zmodyfikować właściwość transformacji widoków podrzędnych.

np. narysuj czarną poziomą linię. Jest to wywoływane z implementacji kontrolera widoku

UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];
Jeff Ames
źródło
Byłbym skłonny zgodzić się, że dla elementu separatora (np. Linii między elementami UI) jest to ścieżka najmniejszego oporu. Alternatywa renderowania do mapy bitowej w celu wykonania kopii CoreAnimation lub implementacji modułu obsługi rysowania w widoku niestandardowym jest znacznie bardziej skomplikowana. Ten sprzęt prawdopodobnie również przyspiesza lepiej, pod warunkiem, że jest to tylko jedna linia.
marko
4
Ciekaw jestem, dlaczego jest to okropny pomysł. Chciałbym uniknąć problemów, które to rozwiązanie może spowodować we własnym kodzie.
Jeff Ames
@sangony Używam tej techniki również w moim kodzie. Zastanawiam się też, dlaczego to okropny pomysł? Proszę o pomoc w wyjaśnieniu.
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
źródło
8

Oto fajna technika, która może Ci się przydać: Używanie bloków do rysowania, aby uniknąć tworzenia podklas w Objective-C

Uwzględnij podklasę widoku ogólnego przeznaczenia w swoim projekcie, a następnie w kontrolerze widoku możesz umieścić taki kod, aby w locie utworzyć widok rysujący linię:

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
źródło
6

Możesz użyć UIImageView do rysowania linii.

Pozwala jednak na pominięcie podklas. A ponieważ jestem mało skłonny do Core Graphics, nadal mogę go używać. Możesz to po prostu włożyć -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();

Dodatek do odpowiedzi Roba, Trzecim podejściem jest użycie UIImageView- zakryj to - widoku xib. (To jest domyślny wygląd UIImageView po przeciągnięciu na xib w xcode 5)

Pozdrawiam i +1!

khunshan
źródło
2

Nie powinieneś, ale jeśli z jakiegoś powodu ma to dla ciebie sens, możesz utworzyć podklasę UIView, nazywaną DelegateDrawViewna przykład, która przyjmuje delegata, który implementuje metodę taką jak

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

a następnie w metodach - [DelegateDrawView drawRect:]należy wywołać metodę delegata.

Ale dlaczego miałbyś chcieć umieścić kod widoku w kontrolerze.

Lepiej jest utworzyć podklasę UIView, która rysuje linię między dwoma jej rogami, możesz ustawić właściwość, aby ustawić które z nich, a następnie ustawić widok tam, gdzie chcesz, od kontrolera widoku.

Nathan Day
źródło
1

Aby narysować wnętrze twojego widoku jest bardzo proste, @ Mr.ROB powiedział 2 metodę, którą wybrałem pierwszą metodę.

Po prostu skopiuj, wklej kod tam, gdzie chcesz.

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

Będzie rysować jak linia, po której porusza się palec

Kishore Kumar
źródło