Durch Ändern des Ankerpunkts meines CALayers wird die Ansicht verschoben

131

Ich möchte das ändern anchorPoint, aber die Ansicht an der gleichen Stelle behalten. Ich habe versucht , NSLog-ing self.layer.positionund self.centerund sie beide gleich bleiben , unabhängig von Änderungen an der anchor. Doch meine Sicht bewegt sich!

Irgendwelche Tipps dazu?

self.layer.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);
self.layer.anchorPoint = CGPointMake(1, 1);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);

Die Ausgabe ist:

2009-12-27 20:43:24.161 Type[11289:207] center point: 272.500000 242.500000
2009-12-27 20:43:24.162 Type[11289:207] center point: 272.500000 242.500000
Kenny Winker
quelle

Antworten:

139

Im Abschnitt Ebenengeometrie und Transformationen des Programmierhandbuchs für Kernanimationen wird die Beziehung zwischen der Position eines CALayers und den Eigenschaften von anchorPoint erläutert. Grundsätzlich wird die Position einer Ebene in Bezug auf die Position des Ankerpunkts der Ebene angegeben. Standardmäßig ist der Ankerpunkt einer Ebene (0,5, 0,5), der in der Mitte der Ebene liegt. Wenn Sie die Position der Ebene festlegen, legen Sie die Position der Mitte der Ebene im Koordinatensystem der Überlagerung fest.

Da die Position relativ zum Ankerpunkt der Ebene ist, wird die Ebene durch Ändern dieses Ankerpunkts unter Beibehaltung derselben Position verschoben. Um diese Bewegung zu verhindern, müssten Sie die Position der Ebene anpassen, um den neuen Ankerpunkt zu berücksichtigen. Eine Möglichkeit, dies zu tun, besteht darin, die Grenzen der Ebene zu erfassen, die Breite und Höhe der Grenzen mit den normalisierten Werten des alten und neuen Ankerpunkts zu multiplizieren, die Differenz der beiden Ankerpunkte zu nehmen und diese Differenz auf die Position der Ebene anzuwenden.

Auf diese Weise können Sie möglicherweise sogar die Rotation berücksichtigen, indem CGPointApplyAffineTransform()Sie die CGAffineTransform Ihres UIView verwenden.

Brad Larson
quelle
4
Sehen Sie sich die Beispielfunktion von Magnus an, um zu sehen, wie Brads Antwort in Objective-C implementiert werden kann.
Jim Jeffers
Danke, aber ich verstehe nicht, wie die anchorPointund positionmiteinander zusammenhängen?
Dummköpfe
6
@ ss1271 - Wie ich oben beschrieben habe, ist der anchorPoint einer Ebene die Basis ihres Koordinatensystems. Wenn es auf (0,5, 0,5) eingestellt ist, legen Sie beim Festlegen der Position für eine Ebene fest, wo ihr Mittelpunkt im Koordinatensystem ihrer Überlagerung liegt. Wenn Sie den anchorPoint auf (0.0, 0.5) setzen, wird durch Festlegen der Position die Mitte des linken Randes festgelegt. Auch hier hat Apple einige schöne Bilder in der oben verlinkten Dokumentation (auf die ich den Verweis aktualisiert habe).
Brad Larson
Ich habe die von Magnus bereitgestellte Methode verwendet. Wenn ich NSLog positioniere und rahme, sehen sie korrekt aus, aber meine Ansicht wird immer noch verschoben. Irgendeine Idee warum? Es ist, als würde die neue Position nicht berücksichtigt.
Alexis
In meinem Fall möchte ich der Ebene mithilfe der videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer-Methode der AVVideoCompositionCoreAnimationTool-Klasse eine Animation hinzufügen. Was passiert ist, dass die Hälfte meiner Schicht immer wieder verschwindet, während ich sie um die y-Achse drehe.
Nr. 5
205

Ich hatte das gleiche Problem. Die Lösung von Brad Larson hat auch bei gedrehter Ansicht hervorragend funktioniert. Hier ist seine Lösung in Code übersetzt.

-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
    CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, 
                                   view.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, 
                                   view.bounds.size.height * view.layer.anchorPoint.y);

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);

    CGPoint position = view.layer.position;

    position.x -= oldPoint.x;
    position.x += newPoint.x;

    position.y -= oldPoint.y;
    position.y += newPoint.y;

    view.layer.position = position;
    view.layer.anchorPoint = anchorPoint;
}

Und das schnelle Äquivalent:

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

SWIFT 4.x.

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x,
                           y: view.bounds.size.height * anchorPoint.y)


    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x,
                           y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}
Magnus
quelle
2
Perfekt und macht Sinn, als ich Ihr Beispiel hier sehen konnte. Danke dafür!
Jim Jeffers
2
Ich verwende diesen Code in meinen awakeFromNib / initWithFrame-Methoden, aber er funktioniert immer noch nicht. Muss ich die Anzeige aktualisieren? edit: setNeedsDisplay funktioniert nicht
Adam Carter
2
Warum verwenden Sie view.bounds? Sollten Sie nicht layer.bounds verwenden?
Zakdances
1
In meinem Fall ändert der Wert auf: view.layer.position nichts
AndrewK
5
FWIW, ich musste die setAnchor-Methode in einen dispatch_async-Block einbinden, damit sie funktioniert. Ich bin mir nicht sicher warum, wie ich es in viewDidLoad nenne. Befindet sich viewDidLoad nicht mehr in der Haupt-Thread-Warteschlange?
John Fowler
44

Der Schlüssel zur Lösung dieses Problems war die Verwendung der Frame-Eigenschaft, die sich seltsamerweise nur ändert.

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 1, y: 1)
self.frame = oldFrame

Dann mache ich meine Größenänderung, wo sie vom anchorPoint skaliert wird. Dann muss ich den alten anchorPoint wiederherstellen;

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(0.5,0.5)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.frame = oldFrame

BEARBEITEN: Dies wird ausgeblendet, wenn die Ansicht gedreht wird, da die Frame-Eigenschaft undefiniert ist, wenn eine CGAffineTransform angewendet wurde.

Kenny Winker
quelle
9
Ich wollte nur hinzufügen, warum dies funktioniert und der einfachste Weg zu sein scheint: Laut den Dokumenten ist die Rahmeneigenschaft eine "zusammengesetzte" Eigenschaft: "Der Wert des Rahmens wird aus den Eigenschaften" Grenzen "," Ankerpunkt "und" Position "abgeleitet." Das ist auch der Grund, warum die Eigenschaft "frame" nicht animierbar ist.
DarkDust
1
Dies ist ehrlich gesagt mein bevorzugter Ansatz. Wenn Sie nicht versuchen, die Grenzen und Positionen unabhängig voneinander zu manipulieren oder zu animieren, reicht es normalerweise aus, nur den Rahmen zu erfassen, bevor Sie den Ankerpunkt ändern. Keine Mathematik nötig.
CIFilter
28

Für mich verständlich positionund anchorPointam einfachsten, als ich anfing, es mit meinem Verständnis von frame.origin in UIView zu vergleichen. Eine UIView mit frame.origin = (20,30) bedeutet, dass die UIView 20 Punkte von links und 30 Punkte von oben in der übergeordneten Ansicht entfernt ist. Diese Entfernung wird von welchem ​​Punkt eines UIView berechnet? Es wird aus der oberen linken Ecke eines UIView berechnet.

In Ebenenmarkierungen anchorPointbedeutet der Punkt (in normalisierter Form, dh 0 bis 1), von dem aus dieser Abstand berechnet wird, z. B. Ebene. Position = (20, 30) bedeutet, dass die Ebene anchorPoint20 Punkte von links und 30 Punkte von der Oberseite ihrer übergeordneten Ebene entfernt ist. Standardmäßig ist ein Ebenenankerpunkt (0,5, 0,5), sodass der Entfernungsberechnungspunkt genau in der Mitte der Ebene liegt. Die folgende Abbildung hilft mir, meinen Standpunkt zu verdeutlichen:

Geben Sie hier die Bildbeschreibung ein

anchorPoint Dies ist auch der Punkt, um den die Drehung erfolgt, wenn Sie eine Transformation auf die Ebene anwenden.

2cupsOfTech
quelle
1
Können Sie uns sagen, wie ich diesen Ankerpunktsprung lösen kann, wenn UIView mithilfe des automatischen Layouts festgelegt wird? Mit dem automatischen Layout wird nicht angenommen, dass wir Eigenschaften für Rahmen und Grenzen erhalten oder festlegen.
SJ
17

Es gibt so eine einfache Lösung. Dies basiert auf Kennys Antwort. Anstatt den alten Rahmen anzuwenden, verwenden Sie seinen Ursprung und den neuen, um die Übersetzung zu berechnen, und wenden Sie diese Übersetzung dann auf die Mitte an. Es funktioniert auch mit gedrehter Ansicht! Hier ist der Code, viel einfacher als andere Lösungen:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
   let oldOrigin = view.frame.origin
   view.layer.anchorPoint = anchorPoint
   let newOrigin = view.frame.origin

   let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)

   view.center = CGPoint(x: view.center.x - translation.x, y: view.center.y - translation.y)
}
Gebratener Reis
quelle
2
Schöne kleine Methode ... funktioniert gut mit ein paar kleinen Korrekturen: Zeile 3: Großbuchstabe P in anchorPoint. Zeile 8: TRANS.Y = NEU - ALT
Bob
2
Trotzdem bin ich verwirrt, wie ich es benutzen kann. Ich stehe gerade vor dem gleichen Problem, weil ich meine Ansicht mit uigesturerotation gedreht habe. Ich bin seltsam, wo ich diese Methode nennen soll. Wenn ich den Gestenerkennungs-Handler anrufe. Es lässt die Sicht verschwinden.
JAck
3
Diese Antwort ist so süß, dass ich jetzt Diabetes habe.
GeneCode
14

Für diejenigen, die es brauchen, ist hier Magnus 'Lösung in Swift:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
    var newPoint: CGPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint: CGPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position: CGPoint = view.layer.position

    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.setTranslatesAutoresizingMaskIntoConstraints(true)     // Added to deal with auto layout constraints
    view.layer.anchorPoint = anchorPoint
    view.layer.position = position
}
Corey Davis
quelle
1
upvote für view.setTranslatesAutoresizingMaskIntoConstraints(true). danke Mann
Oscar Yuandinata
1
view.setTranslatesAutoresizingMaskIntoConstraints(true)ist erforderlich, wenn Einschränkungen für das automatische Layout verwendet werden.
SamirChen
1
Vielen Dank dafür! Das translatesAutoresizingMaskIntoConstraintsist genau das, was mir gefehlt hat, hätte ich inzwischen lernen können
Ziga
5

Hier ist die Antwort von Benutzer 945711, die für NSView unter OS X angepasst wurde. Abgesehen davon, dass NSView keine .centerEigenschaft hat, ändert sich der Frame von NSView nicht (wahrscheinlich, weil NSViews standardmäßig nicht mit einem CALayer geliefert werden), aber der Ursprung des CALayer-Frames ändert sich, wenn der Ankerpunkt geändert wird.

func setAnchorPoint(anchorPoint: NSPoint, view: NSView) {
    guard let layer = view.layer else { return }

    let oldOrigin = layer.frame.origin
    layer.anchorPoint = anchorPoint
    let newOrigin = layer.frame.origin

    let transition = NSMakePoint(newOrigin.x - oldOrigin.x, newOrigin.y - oldOrigin.y)
    layer.frame.origin = NSMakePoint(layer.frame.origin.x - transition.x, layer.frame.origin.y - transition.y)
}
iMaddin
quelle
4

Wenn Sie anchorPoint ändern, ändert sich auch seine Position, es sei denn, Ihr Ursprung ist Nullpunkt CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;
user3156083
quelle
1
Sie können Position und anchorPoint nicht vergleichen, anchorPoint skaliert zwischen 0 und 1.0
Edward Anthony
4

Bearbeiten und sehen Sie den Ankerpunkt von UIView direkt im Storyboard (Swift 3)

Dies ist eine alternative Lösung, mit der Sie den Ankerpunkt über den Attributinspektor ändern können und die über eine andere Eigenschaft verfügt, um den Ankerpunkt zur Bestätigung anzuzeigen.

Erstellen Sie eine neue Datei, die Sie in Ihr Projekt aufnehmen möchten

import UIKit

@IBDesignable
class UIViewAnchorPoint: UIView {

    @IBInspectable var showAnchorPoint: Bool = false
    @IBInspectable var anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5) {
        didSet {
            setAnchorPoint(anchorPoint: anchorPoint)
        }
    }

    override func draw(_ rect: CGRect) {
        if showAnchorPoint {
            let anchorPointlayer = CALayer()
            anchorPointlayer.backgroundColor = UIColor.red.cgColor
            anchorPointlayer.bounds = CGRect(x: 0, y: 0, width: 6, height: 6)
            anchorPointlayer.cornerRadius = 3

            let anchor = layer.anchorPoint
            let size = layer.bounds.size

            anchorPointlayer.position = CGPoint(x: anchor.x * size.width, y: anchor.y * size.height)
            layer.addSublayer(anchorPointlayer)
        }
    }

    func setAnchorPoint(anchorPoint: CGPoint) {
        var newPoint = CGPoint(x: bounds.size.width * anchorPoint.x, y: bounds.size.height * anchorPoint.y)
        var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y)

        newPoint = newPoint.applying(transform)
        oldPoint = oldPoint.applying(transform)

        var position = layer.position
        position.x -= oldPoint.x
        position.x += newPoint.x

        position.y -= oldPoint.y
        position.y += newPoint.y

        layer.position = position
        layer.anchorPoint = anchorPoint
    }
}

Fügen Sie dem Storyboard die Ansicht hinzu und legen Sie die benutzerdefinierte Klasse fest

Benutzerdefinierte Klasse

Stellen Sie nun den neuen Ankerpunkt für die UIView ein

Demonstration

Wenn Sie den Show-Ankerpunkt aktivieren, wird ein roter Punkt angezeigt, damit Sie besser sehen können, wo sich der Ankerpunkt visuell befindet. Sie können es später jederzeit ausschalten.

Dies hat mir bei der Planung von Transformationen in UIViews sehr geholfen.

Mark Moeykens
quelle
Kein Punkt auf UIView, ich habe Objective-C mit Swift verwendet, eine benutzerdefinierte UIView-Klasse aus Ihrem Code angehängt und Show
anchor aktiviert
1

Für Swift 3:

func setAnchorPoint(_ anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}
AJ9
quelle
1
Vielen Dank dafür :) Ich habe es gerade als statische Funktion in mein Projekt eingefügt und arbeite wie ein Zauber: D
Kjell
0

Ausgehend von Magnus 'großartiger und gründlicher Antwort habe ich eine Version erstellt, die auf Unterebenen funktioniert:

-(void)setAnchorPoint:(CGPoint)anchorPoint forLayer:(CALayer *)layer
{
    CGPoint newPoint = CGPointMake(layer.bounds.size.width * anchorPoint.x, layer.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(layer.bounds.size.width * layer.anchorPoint.x, layer.bounds.size.height * layer.anchorPoint.y);
    CGPoint position = layer.position;
    position.x -= oldPoint.x;
    position.x += newPoint.x;
    position.y -= oldPoint.y;
    position.y += newPoint.y;
    layer.position = position;
    layer.anchorPoint = anchorPoint;
}
Charles Robertson
quelle