Ziehen Sie UIView um Shape, das aus CGMutablePaths besteht

8

Ich habe eine einfache ovale Form (bestehend aus CGMutablePaths), von der ich möchte, dass der Benutzer ein Objekt um ihn ziehen kann. Ich frage mich nur, wie kompliziert es ist, dies zu tun, muss ich eine Tonne Mathematik und Physik wissen, oder gibt es eine einfache eingebaute Weise, die es mir erlaubt, dies zu tun? IE der Benutzer schleift dieses Objekt um das Oval und umkreist es.

    
Ser Pounce 01.12.2012, 22:58
quelle

1 Antwort

42

Das ist ein interessantes Problem. Wir wollen ein Objekt ziehen, aber darauf beschränken, dass es auf einem CGPath liegt. Sie sagten, Sie hätten "eine einfache ovale Form", aber das ist langweilig. Machen wir es mit einer Zahl 8. Es sieht so aus, wenn wir fertig sind:

Wie machen wir das? Bei einem beliebigen Punkt ist es ziemlich kompliziert, den nächsten Punkt auf einem Bezier-Spline zu finden. Machen wir es mit roher Gewalt. Wir machen nur eine Reihe von Punkten, die eng entlang des Pfades liegen. Das Objekt beginnt an einem dieser Punkte. Während wir versuchen, das Objekt zu ziehen, betrachten wir die benachbarten Punkte. Wenn es näher ist, werden wir das Objekt zu diesem Nachbarpunkt verschieben.

Es ist nicht trivial, eine Reihe von eng benachbarten Punkten entlang einer Bezier-Kurve zu erhalten, aber es gibt eine Möglichkeit, Core Graphics dazu zu bewegen, dies für uns zu tun. Wir können CGPathCreateCopyByDashingPath mit einem kurzen Strichmuster verwenden. Dies erstellt einen neuen Pfad mit vielen kurzen Segmenten. Wir nehmen die Endpunkte jedes Segments als unser Array von Punkten.

Das bedeutet, dass wir über die Elemente eines CGPath iterieren müssen. Die einzige Möglichkeit, über die Elemente eines CGPath zu iterieren, ist die Funktion CGPathApply , die einen Callback übernimmt. Es wäre viel schöner, über Pfadelemente mit einem Block zu iterieren, also fügen wir eine Kategorie zu UIBezierPath hinzu. Wir beginnen mit dem Erstellen eines neuen Projekts mit der Vorlage "Single View Application", mit aktiviertem ARC. Wir fügen eine Kategorie hinzu:

%Vor%

Die Implementierung ist sehr einfach. Wir übergeben den Block einfach als das Argument info der Pfadanwendungsfunktion.

%Vor%

Bei diesem Spielzeugprojekt machen wir alles andere im View-Controller. Wir brauchen einige Instanzvariablen:

%Vor%

Wir brauchen einen ivar, um den Pfad zu halten, dem das Objekt folgt.

%Vor%

Es wäre schön, den Pfad zu sehen, also verwenden wir CAShapeLayer , um ihn anzuzeigen. (Wir müssen das QuartzCore Framework zu unserem Ziel hinzufügen, damit dies funktioniert.)

%Vor%

Wir müssen das Array von Punkten entlang des Pfades irgendwo speichern. Lassen Sie uns ein NSMutableData verwenden:

%Vor%

Wir wollen einen Zeiger auf das Array von Punkten, eingegeben als CGPoint Zeiger:

%Vor%

Und wir müssen wissen, wie viele dieser Punkte es gibt:

%Vor%

Für das "Objekt" haben wir eine ziehbare Ansicht auf dem Bildschirm. Ich nenne es den "Griff":

%Vor%

Wir müssen wissen, auf welchem ​​Pfad der Punkt momentan ist:

%Vor%

Und während die Schwenkgeste aktiv ist, müssen wir verfolgen, wohin der Benutzer versucht hat, den Griff zu ziehen:

%Vor%

Jetzt müssen wir uns an die Arbeit machen und all diese Ivars initialisieren! Wir können unsere Ansichten und Ebenen in viewDidLoad erstellen:

%Vor%

Wir erstellen den Pfadanzeige-Layer wie folgt:

%Vor%

Beachten Sie, dass wir den Pfad des Pfad-Layers noch nicht festgelegt haben! Es ist zu früh, um den Weg zu dieser Zeit zu kennen, weil meine Ansicht noch nicht in ihrer endgültigen Größe angelegt wurde.

Wir zeichnen einen roten Kreis für den Griff:

%Vor%

Wieder ist es zu früh, um genau zu wissen, wo wir den Griff auf den Bildschirm setzen müssen. Wir kümmern uns darum beim Layout-Layout.

Wir müssen auch einen Pan-Gesten-Erkenner an den Handle anhängen:

%Vor%

Bei der Ansichts-Layout-Zeit müssen wir den Pfad basierend auf der Größe der Ansicht erstellen, die Punkte entlang des Pfades berechnen, die Pfadebene den Pfad anzeigen lassen und sicherstellen, dass sich das Handle auf dem Pfad befindet:

%Vor%

In Ihrer Frage haben Sie gesagt, Sie benutzen eine "einfache ovale Form", aber das ist langweilig. Lassen Sie uns eine schöne Figur zeichnen. 8. Das herauszufinden, was ich mache, bleibt als Übung für den Leser übrig:

%Vor%

Als nächstes wollen wir das Array von Punkten auf diesem Pfad berechnen. Wir benötigen eine Hilfsroutine, um den Endpunkt jedes Pfadelements auszuwählen:

%Vor%

Um die Punkte zu finden, müssen wir Core Graphics bitten, den Pfad zu "streichen":

%Vor%

Es stellt sich heraus, dass Core-Grafiken, wenn sie den Pfad streichen, Segmente erstellen können, die sich leicht überlappen. Wir wollen diese eliminieren, indem wir jeden Punkt ausfiltern, der zu nah an seinem Vorgänger liegt. Deshalb definieren wir einen minimalen Abstand zwischen den Punkten:

%Vor%

Um die Filterung durchzuführen, müssen wir den Vorgänger im Auge behalten:

%Vor%

Wir müssen die NSMutableData erstellen, die die CGPoint s enthält:

%Vor%

Endlich sind wir bereit, über die Elemente des gestrichelten Pfads zu iterieren:

%Vor%

Jedes Pfadelement kann ein "move-to", ein "line-to", ein "quadratic-curve-to", ein "curve-to" (welches eine kubische Kurve ist) oder ein "close-to" sein. Pfad". Alle außer close-path definieren einen Segment-Endpunkt, den wir mit unserer Hilfsfunktion von früher aufnehmen:

%Vor%

Wenn der Endpunkt zu nah am vorherigen Punkt liegt, wirf ihn weg:

%Vor%

Andernfalls hängen wir es an die Daten an und speichern sie als Vorgänger des nächsten Endpunkts:

%Vor%

Nun können wir unsere pathPoints_ und pathPointsCount_ ivars initialisieren:

%Vor%

Aber wir haben einen weiteren Punkt, den wir filtern müssen. Der allererste Punkt entlang des Weges könnte dem letzten Punkt zu nahe kommen.Wenn ja, werden wir den letzten Punkt einfach verwerfen, indem wir die Anzahl verringern:

%Vor%

Blammer. Punkt-Array erstellt. Oh ja, wir müssen auch die Pfadschicht aktualisieren. Mach dich bereit:

%Vor%

Jetzt können wir uns sorgen, den Griff herumzuziehen und sicherzustellen, dass er auf dem Pfad bleibt. Der Pan-Gestenerkenner sendet diese Aktion:

%Vor%

Wenn dies der Anfang des Pan (Drag) ist, wollen wir nur den Startpunkt des Handle als den gewünschten Ort speichern:

%Vor%

Andernfalls müssen wir den gewünschten Ort basierend auf dem Ziehen aktualisieren und dann den Griff entlang des Pfads zum neuen gewünschten Ort verschieben:

%Vor%

Wir setzen eine Standardklausel ein, so dass der Clong uns nicht vor den anderen Staaten warnt, die uns nicht interessieren:

%Vor%

Schließlich setzen wir die Übersetzung der Gestenerkennung zurück:

%Vor%

Wie bewegen wir den Griff zu einem Punkt? Wir wollen es entlang des Weges schieben. Zuerst müssen wir herausfinden, in welche Richtung es verschoben werden soll:

%Vor%

Es ist möglich, dass beide Richtungen den Griff weiter von dem gewünschten Punkt wegbewegen, also lassen Sie uns in diesem Fall ausweichen:

%Vor%

OK, so dass mindestens eine der Richtungen den Griff näher bringt. Lass uns herausfinden, welcher:

%Vor%

Aber wir haben nur die nächsten Nachbarn des Startpunkts des Handle überprüft. Wir wollen so weit wie möglich entlang des Pfades in diese Richtung gleiten, solange sich der Griff dem gewünschten Punkt nähert:

%Vor%

Aktualisieren Sie abschließend die Position des Handles auf unseren neu entdeckten Punkt:

%Vor%

Das lässt nur die kleine Sache übrig, den Abstand vom Griff zu einem Punkt zu berechnen, sollte der Griff entlang des Pfades um einen Versatz bewegt werden. Dein alter Freund hypotf berechnet die euklidische Entfernung, also musst du nicht:

%Vor%

(Sie könnten die Dinge beschleunigen, indem Sie quadratische Abstände verwenden, um die Quadratwurzeln zu vermeiden, die hypotf berechnet.)

Noch ein kleines Detail: Der Index in das Punkte-Array muss sich in beide Richtungen bewegen. Darum haben wir uns auf die mysteriöse Methode handlePathPointIndexWithOffset: verlassen:

%Vor%

Flosse. Ich habe den gesamten Code in einen Begriff zum einfachen Herunterladen . Viel Spaß.

    
rob mayoff 02.12.2012, 06:51
quelle

Tags und Links