Je souhaite modifier la anchorPoint
, tout en conservant la vue au même endroit . J'ai essayé NSLog
- code self.layer.position
et self.center
et ils restent tous les deux identiques quelles que soient les modifications apportées à anchorPoint. Pourtant, ma vue bouge!
Aucune astuce sur comment le faire?
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);
La sortie est:
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
La section Géométrie et transformations de couche du Guide de programmation de Core Animation explique la relation entre la position d'une couche CAL et les propriétés anchorPoint. Fondamentalement, la position d'un calque est spécifiée en fonction de l'emplacement du point d'ancrage du calque. Par défaut, le point d'ancrage d'un calque est (0.5, 0.5), situé au centre du calque. Lorsque vous définissez la position du calque, vous définissez l'emplacement du centre du calque dans le système de coordonnées de son super-calque.
Comme la position est relative au point d'ancrage du calque, modifier ce point d'ancrage tout en conservant la même position déplace le calque. Afin d'empêcher ce mouvement, vous devrez ajuster la position de la couche pour prendre en compte le nouveau point d'ancrage. Une façon de procéder consiste à saisir les limites du calque, à multiplier la largeur et la hauteur des limites par les valeurs normalisées de l'ancien et du nouveau point d'ancrage, à prendre la différence des deux points d'ancrage et à appliquer cette différence à la position du calque.
Vous pourriez même être en mesure de rendre compte de la rotation de cette manière en utilisant CGPointApplyAffineTransform()
avec CGAffineTransform de votre UIView.
J'ai eu le même problème. La solution de Brad Larson a fonctionné à merveille même lorsque la vue est tournée. Voici sa solution traduite en code.
-(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;
}
Et l'équivalent Swift:
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
}
La solution à ce problème consistait à utiliser la propriété frame, qui est étrangement la seule chose qui change.
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
Ensuite, je redimensionne, où il est redimensionné à partir de anchorPoint . Ensuite, je dois restaurer l’ancre anchorPoint;
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
EDIT: ceci disparaît si la vue est tournée, car la propriété frame n'est pas définie si un CGAffineTransform a été appliqué.
Pour moi, comprendre position
et anchorPoint
était plus facile lorsque j'ai commencé à le comparer avec ma compréhension de frame.Origin dans UIView. Un UIView avec frame.Origin = (20,30) signifie que le UIView est à 20 points de la gauche et 30 points du haut de sa vue parente. Cette distance est calculée à partir de quel point de UIView? Son calculé à partir du coin en haut à gauche d'un UIView.
Dans la couche anchorPoint
marque le point (sous forme normalisée, à savoir 0 à 1) à partir duquel cette distance est calculée, par ex. layer.position = (20, 30) signifie que le calque anchorPoint
est à 20 points de la gauche et à 30 points du haut de son calque parent. Par défaut, un point d'ancrage de la couche est (0.5, 0.5), de sorte que le point de calcul de la distance est situé au centre de la couche. La figure suivante aidera à clarifier mon point:
anchorPoint
se trouve également être le point autour duquel la rotation aura lieu si vous appliquez une transformation au calque.
Il y a une telle solution simple. Ceci est basé sur la réponse de Kenny. Mais au lieu d'appliquer l'ancien cadre, utilisez l'origine et les nouveaux pour calculer la transition, puis appliquez cette transition au centre. Cela fonctionne aussi avec la vue tournée! Voici le code, beaucoup plus simple que d'autres solutions:
- (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);
}
Et la version Swift:
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)
}
Pour ceux qui en ont besoin, voici la solution de Magnus dans 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
}
Il s'agit d'une solution alternative qui vous permet de modifier le point d'ancrage via l'inspecteur d'attributs et possède une autre propriété permettant d'afficher le point d'ancrage pour confirmation.
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
}
}
Si vous activez Afficher le point d’ancrage, un point rouge apparaît pour vous permettre de mieux visualiser l’emplacement visuel du point d’ancrage. Vous pouvez toujours l'éteindre plus tard.
Cela m’a vraiment aidé lors de la planification de la transformation d’UIViews.
Voici la réponse de user945711 ajustée pour NSView sous OS X. Outre que NSView ne possédant pas de propriété .center
, le cadre de NSView ne change pas (probablement parce que NSViews ne comporte pas de CALayer par défaut), mais le cadre de CALayer Origin change lorsque le anchorPoint est changé.
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)
}
Si vous changez anchorPoint, sa position changera aussi, sauf si votre origine est zéro CGPointZero
.
position.x == Origin.x + anchorPoint.x;
position.y == Origin.y + anchorPoint.y;
Pour 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
}
En développant la réponse géniale et détaillée de Magnus, j'ai créé une version qui fonctionne sur les sous-couches:
-(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;
}