Rysowanie ścieżki UIBezierPath na wygenerowanym kodzie UIView

Mam UIView dodane w kodzie w czasie wykonywania.

Chcę w nim narysować UIBezierPath, ale czy to oznacza, że muszę nadpisać drawRect dla UIView?

A może jest inny sposób rysowania na zamówienie UIView?

Oto kod do generowania UIView:

UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)];
shapeView.clipsToBounds = YES;

A oto funkcja do tworzenia i zwracania UIBezierPath:

- (UIBezierPath*)createPath
{
    UIBezierPath* path = [[UIBezierPath alloc]init];
    [path moveToPoint:CGPointMake(100.0, 50.0)];
    [path addLineToPoint:CGPointMake(200.0,50.0)];
    [path addLineToPoint:CGPointMake(200.0, 200.0)];
    [path addLineToPoint:CGPointMake(100.0, 200.0)];
    [path closePath];
    return path;
}
Author: Suragch, 2014-01-23

6 answers

Nie tak dawno temu nawet nie wiedziałem, jak wymawiać Béziera, nie mówiąc już o tym, jak używać ścieżek Béziera do tworzenia niestandardowych kształtów. Oto, czego się nauczyłem. Okazuje się, że nie są tak przerażające, jak się na początku wydają.

Jak narysować ścieżkę Béziera w widoku niestandardowym

Oto główne kroki:

  1. Zaprojektuj zarys pożądanego kształtu.
  2. podziel ścieżkę konturu na segmenty linii, łuków i krzywe.
  3. Zbuduj tę ścieżkę programowo.
  4. Narysuj ścieżkę w drawRect lub używając CAShapeLayer.

Design shape outline

Możesz zrobić wszystko, ale jako przykład wybrałem poniższy kształt. To może być wyskakujący klawisz na klawiaturze.

Tutaj wpisz opis obrazka

Podziel ścieżkę na segmenty

Spójrz wstecz na swój kształt i podziel go na prostsze elementy linii (dla linii prostych), łuków (dla okręgów i okrągłych narożniki), i krzywe (dla czegokolwiek innego).

Oto jak wyglądałby nasz przykładowy projekt:]}

Tutaj wpisz opis obrazka

  • czarne są segmentami linii
  • Jasnoniebieski to segmenty łukowe
  • czerwone są krzywe
  • pomarańczowe kropki są punktami kontrolnymi krzywych
  • zielone kropki są punktami między segmentami ścieżki
  • kropkowane linie pokazują obwiedniowy prostokąt
  • ciemnoniebieskie liczby są segmentami w kolejności, w jakiej będą być dodawane programowo

Zbuduj ścieżkę programowo

Dowolnie zaczniemy w lewym dolnym rogu i będziemy pracować zgodnie z ruchem wskazówek zegara. Użyję siatki na obrazku, aby uzyskać wartości x i y dla punktów. Zakoduję wszystko tutaj, ale oczywiście nie zrobiłbyś tego w prawdziwym projekcie.

Podstawowy proces to:

  1. Utwórz nowy UIBezierPath
  2. Wybierz punkt początkowy na ścieżce z moveToPoint
  3. Dodaj segmenty do ścieżka
    • linia: addLineToPoint
    • arc: addArcWithCenter
    • krzywa: addCurveToPoint
  4. Zamknij ścieżkę za pomocą closePath

Oto kod do utworzenia ścieżki na powyższym obrazku.

func createBezierPath() -> UIBezierPath {

    // create a new path
    let path = UIBezierPath()

    // starting point for the path (bottom left)
    path.move(to: CGPoint(x: 2, y: 26))

    // *********************
    // ***** Left side *****
    // *********************

    // segment 1: line
    path.addLine(to: CGPoint(x: 2, y: 15))

    // segment 2: curve
    path.addCurve(to: CGPoint(x: 0, y: 12), // ending point
        controlPoint1: CGPoint(x: 2, y: 14),
        controlPoint2: CGPoint(x: 0, y: 14))

    // segment 3: line
    path.addLine(to: CGPoint(x: 0, y: 2))

    // *********************
    // ****** Top side *****
    // *********************

    // segment 4: arc
    path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle
        radius: 2, // this will make it meet our path line
        startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
        endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
        clockwise: true) // startAngle to endAngle goes in a clockwise direction

    // segment 5: line
    path.addLine(to: CGPoint(x: 8, y: 0))

    // segment 6: arc
    path.addArc(withCenter: CGPoint(x: 8, y: 2),
                          radius: 2,
                          startAngle: CGFloat(3*M_PI_2), // straight up
        endAngle: CGFloat(0), // 0 radians = straight right
        clockwise: true)

    // *********************
    // ***** Right side ****
    // *********************

    // segment 7: line
    path.addLine(to: CGPoint(x: 10, y: 12))

    // segment 8: curve
    path.addCurve(to: CGPoint(x: 8, y: 15), // ending point
        controlPoint1: CGPoint(x: 10, y: 14),
        controlPoint2: CGPoint(x: 8, y: 14))

    // segment 9: line
    path.addLine(to: CGPoint(x: 8, y: 26))

    // *********************
    // **** Bottom side ****
    // *********************

    // segment 10: line
    path.close() // draws the final line to close the path

    return path
}

Uwaga: część powyższego kodu można zredukować poprzez dodanie linii i łuku w jednym poleceniu (ponieważ łuk ma domniemany punkt początkowy). Zobacz tutaj aby uzyskać więcej szczegółów.

Narysuj ścieżkę

Możemy narysować ścieżkę w warstwie lub w drawRect.

Metoda 1: rysowanie ścieżki w warstwie

Nasza klasa niestandardowa wygląda tak. Dodajemy naszą ścieżkę Beziera do nowej CAShapeLayer, Gdy widok jest inicjowany.
import UIKit
class MyCustomView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    func setup() {

        // Create a CAShapeLayer
        let shapeLayer = CAShapeLayer()

        // The Bezier path that we made needs to be converted to 
        // a CGPath before it can be used on a layer.
        shapeLayer.path = createBezierPath().cgPath

        // apply other properties related to the path
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 1.0
        shapeLayer.position = CGPoint(x: 10, y: 10)

        // add the new layer to our custom view
        self.layer.addSublayer(shapeLayer)
    }

    func createBezierPath() -> UIBezierPath {

        // see previous code for creating the Bezier path
    }
}

I tworzenie naszego widoku w kontrolerze widoku w ten sposób

override func viewDidLoad() {
    super.viewDidLoad()

    // create a new UIView and add it to the view controller
    let myView = MyCustomView()
    myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
    myView.backgroundColor = UIColor.yellow
    view.addSubview(myView)

}

Dostajemy...

Tutaj wpisz opis obrazka

Hmm, to trochę za małe, bo zakodowałem wszystkie liczby. Mogę jednak przeskalować rozmiar ścieżki, jak to:
let path = createBezierPath()
let scale = CGAffineTransform(scaleX: 2, y: 2)
path.apply(scale)
shapeLayer.path = path.cgPath

Tutaj wpisz opis obrazka

Metoda 2: Narysuj ścieżkę w draw

Używanie draw jest wolniejsze niż rysowanie na warstwie, więc nie jest to zalecana metoda, jeśli jej nie potrzebujesz.

Oto poprawiony kod dla naszego niestandardowego widoku:

import UIKit
class MyCustomView: UIView {

    override func draw(_ rect: CGRect) {

        // create path (see previous code)
        let path = createBezierPath()

        // fill
        let fillColor = UIColor.white
        fillColor.setFill()

        // stroke
        path.lineWidth = 1.0
        let strokeColor = UIColor.blue
        strokeColor.setStroke()

        // Move the path to a new location
        path.apply(CGAffineTransform(translationX: 10, y: 10))

        // fill and stroke the path (always do these last)
        path.fill()
        path.stroke()

    }

    func createBezierPath() -> UIBezierPath {

        // see previous code for creating the Bezier path
    }
}

Co daje nam ten sam wynik...

Tutaj wpisz opis obrazka

Dalsze badania

I naprawdę zalecam zapoznanie się z poniższymi materiałami. Są tym, co w końcu udało mi się zrozumieć ścieżki Béziera. (I nauczył mnie jak to wymawiać: / ˈbɛ zi eɪ/.)

 183
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 01:39:20

Byłoby łatwiej, gdybyś użył CAShapeLayer, w ten sposób:

CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];

I ustaw jego path:

[shapeView setPath:[self createPath].CGPath];

Na koniec dodaj:

[[self.view layer] addSublayer:shapeView];
 54
Author: Peres,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-23 15:14:20

Możesz użyć CAShapeLayer, Aby to zrobić.

W ten sposób...
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [self createPath].CGPath;
shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc...
shapeLayer.lineWidth = 2.0; //etc...
shapeLayer.position = CGPointMake(100, 100); //etc...
[self.layer addSublayer:shapeLayer];

Spowoduje to dodanie i narysowanie ścieżki bez konieczności nadpisywania drawRect.

 15
Author: Fogmeister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-22 00:58:02

Istnieje wiele sposobów, aby osiągnąć to, co chcesz. Te, które widziałem najbardziej to: override drawRect, narysuj swój kształt w CAShapeLayer, a następnie dodaj go jako podwarstwę do widoku, lub Narysuj swoją ścieżkę w innym kontekście, zapisz to jako obraz, a następnie dodaj go do widoku.

Wszystkie z nich są rozsądnymi wyborami, a to, który z nich jest najlepszy, zależy od wielu innych czynników, takich jak: czy będziesz stale dodawać kształty, jak często to się nazywa itp.

 5
Author: Travis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:27

Jak zauważyły inne plakaty, użycie warstwy kształtu jest dobrym sposobem.

Warstwy kształtu a prawdopodobnie zapewnią lepszą wydajność niż zastąpienie funkcji drawRect.

Jeśli chcesz samodzielnie narysować ścieżkę, to tak, musisz nadpisać drawRect dla swojej niestandardowej klasy widoku.

 2
Author: Duncan C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-23 15:18:06

Tak, musisz nadpisać drawrect, jeśli chcesz coś narysować.Tworzenie ścieżki UIBezierPath można wykonać w dowolnym miejscu, ale aby coś narysować należy to zrobić wewnątrz metody drawrect

Powinieneś wywoływać setNeedsDisplay, jeśli nadpisujesz drawRect w podklasie UIView, która jest w zasadzie niestandardowym widokiem rysującym coś na ekranie, jak linie, obraz , prostokąt.

 1
Author: Lithu T.V,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-23 15:22:40