Ich möchte die anchorPoint
ändern, aber die Ansicht an derselben Stelle belassen. __ Ich habe NSLog
ing self.layer.position
und self.center
ausprobiert, und beide bleiben unabhängig von Änderungen am anchorPoint gleich. Doch meine Sicht bewegt sich!
Irgendwelche Tipps, wie das geht?
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
Im Abschnitt Layer Geometry and Transforms des Programmierhandbuchs für Core-Animation wird die Beziehung zwischen der Position eines CALayers und den Ankerpunkt-Eigenschaften erläutert. Grundsätzlich wird die Position einer Ebene als Position des Ankerpunkts der Ebene angegeben. Standardmäßig ist der Ankerpunkt einer Ebene (0,5, 0,5) und liegt in der Mitte der Ebene. Wenn Sie die Position der Ebene festlegen, legen Sie die Position des Mittelpunkts der Ebene im Koordinatensystem des übergeordneten Layers fest.
Da sich die Position relativ zum Ankerpunkt der Ebene befindet, wird durch Ändern des Ankerpunkts bei gleicher Position die Ebene verschoben. Um diese Bewegung zu verhindern, müssen 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 des neuen Ankerpunkts zu multiplizieren, die Differenz der beiden Ankerpunkte zu nehmen und diese Differenz auf die Position der Ebene anzuwenden.
Sie können sogar die Rotation auf diese Weise berücksichtigen, indem Sie CGPointApplyAffineTransform()
mit der CGAffineTransform Ihrer UIView verwenden.
Ich hatte das gleiche Problem. Die Lösung von Brad Larson hat auch dann gut funktioniert, wenn die Ansicht gedreht wird. 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
}
Der Schlüssel zur Lösung dieses Problems bestand in der Verwendung der Frame-Eigenschaft, die sich seltsamerweise nur ändern kann.
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öße, wo sie vom Ankerpunkt aus skaliert ... __ Dann muss ich den alten Ankerpunkt 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: Blättert aus, wenn die Ansicht gedreht wird, da die Frame-Eigenschaft undefiniert ist, wenn eine CGAffineTransform angewendet wurde.
Für mich war das Verstehen von position
und anchorPoint
am einfachsten, als ich es mit meinem Verständnis von frame.Origin in UIView zu vergleichen begann. Ein UIView mit frame.Origin = (20,30) bedeutet, dass der UIView 20 Punkte von links und 30 Punkte von oben in der übergeordneten Ansicht ist. Diese Entfernung wird ab welchem Punkt einer UIView berechnet? Es wird aus der oberen linken Ecke eines UIView berechnet.
In der Schicht anchorPoint
wird der Punkt (in normalisierter Form, d. H. 0 bis 1) markiert, von dem aus dieser Abstand berechnet wird, z. layer.position = (20, 30) bedeutet, dass die Ebene anchorPoint
20 Punkte von links und 30 Punkte von der obersten Ebene der übergeordneten Ebene entfernt ist. Standardmäßig ist ein Ankerpunkt der Ebene (0,5, 0,5). Der Entfernungsberechnungspunkt befindet sich also genau in der Mitte der Ebene. Die folgende Abbildung hilft, meinen Punkt zu verdeutlichen:
anchorPoint
ist auch der Punkt, um den die Rotation ausgeführt wird, falls Sie eine Transformation auf die Ebene anwenden.
Es gibt so eine einfache Lösung. Dies basiert auf Kennys Antwort. Verwenden Sie statt des alten Rahmens jedoch Origin und die neuen, um den Übergang zu berechnen, und wenden Sie diesen Übergang dann auf die Mitte an. Es funktioniert auch mit gedrehter Ansicht! Hier ist der Code, viel einfacher als andere Lösungen:
- (void) setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
CGPoint oldOrigin = view.frame.Origin;
view.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = view.frame.Origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}
Und die Swift-Version:
func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
let oldOrigin = view.frame.Origin
view.layer.anchorPoint = anchorPoint
let newOrigin = view.frame.Origin
let transition = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)
view.center = CGPoint(x: view.center.x - transition.x, y: view.center.y - transition.y)
}
Für diejenigen, die es brauchen, hier die Lösung von Magnus 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
}
Dies ist eine alternative Lösung, mit der Sie den Ankerpunkt über den Attribut-Inspektor ändern können. Mit einer anderen Eigenschaft können Sie den Ankerpunkt zur Bestätigung anzeigen.
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
}
}
Wenn Sie den Ankerpunkt anzeigen aktivieren, wird ein roter Punkt angezeigt, damit Sie besser sehen können, wo der Ankerpunkt visuell sein wird. Sie können es später jederzeit ausschalten.
Das hat mir wirklich bei der Planung von Transformationen auf UIViews geholfen.
Hier ist die Antwort von user945711 für NSView unter OS X angepasst. Abgesehen davon, dass NSView keine .center
-Eigenschaft hat, ändert sich der Rahmen von NSView nicht (wahrscheinlich, weil NSViews standardmäßig nicht mit CALayer geliefert wird), aber der Ursprung von CALayer ändert sich Der Ankerpunkt wird geändert.
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)
}
Wenn Sie den Ankerpunkt ändern, ändert sich auch seine Position. UNSERE Herkunft ist der Nullpunkt CGPointZero
.
position.x == Origin.x + anchorPoint.x;
position.y == Origin.y + anchorPoint.y;
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
}
Um Magnus 'großartige und gründliche Antwort zu erweitern, 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;
}