Changer le point d'ancrage de mon CALayer déplace la vue

131

Je veux modifier le anchorPoint, mais garder la vue au même endroit. J'ai essayé NSLog-ing self.layer.positionet self.centerils restent tous les deux les mêmes, 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
Kenny Winker
la source

Réponses:

139

La section Layer Geometry and Transforms du Guide de programmation d'animation de base explique la relation entre la position d'un CALayer 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), qui se trouve au centre du calque. Lorsque vous définissez la position du calque, vous définissez alors l'emplacement du centre du calque dans le système de coordonnées de son super-calque.

La position étant 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'éviter ce mouvement, vous devrez ajuster la position du calque pour tenir compte du 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 anchorPoint, à prendre la différence des deux anchorPoints et à appliquer cette différence à la position du calque.

Vous pourriez même être en mesure de rendre compte de la rotation de cette façon en utilisant CGPointApplyAffineTransform()avec CGAffineTransform de votre UIView.

Brad Larson
la source
4
Voir l'exemple de fonction de Magnus pour voir comment la réponse de Brad peut être implémentée dans Objective-C.
Jim Jeffers
Merci mais je ne comprends pas comment les anchorPointet positionsont liés ensemble?
dumbfingers
6
@ ss1271 - Comme je l'ai décrit ci-dessus, le point d'ancrage d'une couche est la base de son système de coordonnées. S'il est défini sur (0,5, 0,5), lorsque vous définissez la position d'un calque, vous définissez l'emplacement de son centre dans le système de coordonnées de sa super-couche. Si vous définissez le point d'ancrage sur (0,0, 0,5), la définition de la position définira l'emplacement du centre de son bord gauche. Encore une fois, Apple a quelques belles images dans la documentation ci-dessus (dont j'ai mis à jour la référence).
Brad Larson
J'ai utilisé la méthode fournie par Magnus. Quand je positionne et encadre NSLog, ils semblent corrects, mais ma vue est toujours déplacée. Une idée pourquoi? C'est comme si la nouvelle position n'était pas prise en compte.
Alexis
Dans mon cas, je souhaite ajouter une animation sur la couche à l'aide de la méthode videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer de la classe AVVideoCompositionCoreAnimationTool. Ce qui se passe, c'est que la moitié de ma couche continue de disparaître pendant que je la fais pivoter autour de l'axe y.
nr5
205

J'ai eu le même problème. La solution de Brad Larson a très bien fonctionné 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 rapide:

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
la source
2
Parfait et prend tout son sens une fois que j'ai pu voir votre exemple ici. Merci pour cela!
Jim Jeffers
2
J'utilise ce code dans mes méthodes awakeFromNib / initWithFrame, mais cela ne fonctionne toujours pas, dois-je mettre à jour l'affichage? edit: setNeedsDisplay ne fonctionne pas
Adam Carter
2
Pourquoi utilisez-vous view.bounds? Ne devriez-vous pas utiliser layer.bounds?
zakdances
1
dans mon cas, définir la valeur sur: view.layer.position ne change rien
AndrewK
5
FWIW, j'ai dû envelopper la méthode setAnchor dans un bloc dispatch_async pour que cela fonctionne. Je ne sais pas pourquoi comme je l'appelle à l'intérieur de viewDidLoad. ViewDidLoad n'est plus sur la file d'attente principale du thread?
John Fowler
44

La clé pour résoudre ce problème était d'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 fais mon redimensionnement, où il se met à l'échelle du point d'ancrage. Ensuite, je dois restaurer l'ancien point d'ancrage;

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: cela s'efface si la vue est tournée, car la propriété frame n'est pas définie si un CGAffineTransform a été appliqué.

Kenny Winker
la source
9
Je voulais juste ajouter pourquoi cela fonctionne et semble être le moyen le plus simple: selon la documentation, la propriété frame est une propriété "composée": "La valeur de frame est dérivée des propriétés bounds, anchorPoint et position." C'est aussi pourquoi la propriété "frame" n'est pas animable.
DarkDust
1
C'est honnêtement mon approche préférée. Si vous n'essayez pas de manipuler les limites et de positionner indépendamment ou d'animer l'une ou l'autre, il suffit généralement de capturer l'image avant de changer le point d'ancrage. Aucun calcul nécessaire.
CIFilter
28

Pour moi, comprendre positionet anchorPointc'était plus facile quand j'ai commencé à le comparer avec ma compréhension de frame.origin dans UIView. Un UIView avec frame.origin = (20,30) signifie que l'UIView est à 20 points de la gauche et à 30 points du haut de sa vue parent. Cette distance est calculée à partir de quel point d'un UIView? Il est calculé à partir du coin supérieur gauche d'un UIView.

Dans le calque anchorPointmarque le point (sous forme normalisée, c'est-à-dire de 0 à 1) à partir duquel cette distance est calculée, par exemple layer.position = (20, 30) signifie que le calque anchorPointest à 20 points de gauche et 30 points du haut de son calque parent. Par défaut, le point d'ancrage d'un calque est (0,5, 0,5), de sorte que le point de calcul de distance se trouve juste au centre du calque. La figure suivante aidera à clarifier mon point:

entrez la description de l'image ici

anchorPoint se trouve également être le point autour duquel la rotation se produira si vous appliquez une transformation au calque.

2cupsOfTech
la source
1
S'il vous plaît pouvez-vous dire comment puis-je résoudre ce saut de point d'ancrage qui est UIView est défini à l'aide de la mise en page automatique, ce qui signifie qu'avec la mise en page automatique, nous ne sommes pas supposés obtenir ou définir les propriétés des cadres et des limites.
SJ
17

Il existe une solution si simple. Ceci est basé sur la réponse de Kenny. Mais au lieu d'appliquer l'ancien cadre, utilisez son origine et le nouveau pour calculer la traduction, puis appliquez cette traduction au centre. Cela fonctionne aussi avec la vue pivotée! Voici le code, beaucoup plus simple que les autres solutions:

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)
}
Riz sauté
la source
2
Jolie petite méthode ... fonctionne bien avec quelques corrections mineures: Ligne 3: P majuscule dans anchorPoint. Ligne 8: TRANS.Y = NEW - OLD
bob
2
je suis toujours dans la confusion comment puis-je l'utiliser. Je suis confronté au même problème en ce moment pour faire pivoter ma vue avec uigesturerotation. Je suis curieux de savoir où dois-je appeler cette méthode. Si j'appelle le gestionnaire de reconnaissance de mouvement. Cela fait disparaître la vue.
JAck
3
Cette réponse est si douce que je suis maintenant diabétique.
GeneCode
14

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
}
Corey Davis
la source
1
vote favorable pour view.setTranslatesAutoresizingMaskIntoConstraints(true). merci l'homme
Oscar Yuandinata
1
view.setTranslatesAutoresizingMaskIntoConstraints(true)est nécessaire lorsque vous utilisez des contraintes de mise en page automatique.
SamirChen
1
Merci beaucoup pour cela! C'est translatesAutoresizingMaskIntoConstraintsexactement ce que je manquais, j'aurais pu apprendre maintenant
Ziga
5

Voici la réponse de user945711 ajustée pour NSView sur OS X. Outre que NSView n'a pas de .centerpropriété, le cadre de NSView ne change pas (probablement parce que NSViews n'est pas fourni avec un CALayer par défaut) mais l'origine du cadre CALayer change lorsque le point d'ancrage est modifié.

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
la source
4

Si vous changez anchorPoint, sa position changera également, À MOINS QUE votre origine ne soit le point zéro CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;
user3156083
la source
1
Vous ne pouvez pas comparer la position et le point d'ancrage, les échelles d'ancrage entre 0 et 1,0
Edward Anthony
4

Modifier et voir le point d'ancrage d'UIView à droite sur Storyboard (Swift 3)

Il s'agit d'une solution alternative qui vous permet de modifier le point d'ancrage via l'inspecteur d'attributs et qui possède une autre propriété pour afficher le point d'ancrage pour confirmation.

Créez un nouveau fichier à inclure dans votre projet

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
    }
}

Ajouter une vue au storyboard et définir la classe personnalisée

Classe personnalisée

Maintenant, définissez le nouveau point d'ancrage pour l'UIView

Manifestation

L'activation de Afficher le point d'ancrage affichera un point rouge afin que vous puissiez mieux voir où le point d'ancrage sera visuellement. Vous pouvez toujours le désactiver plus tard.

Cela m'a vraiment aidé lors de la planification des transformations sur UIViews.

Mark Moeykens
la source
Pas un point sur UIView, j'ai utilisé Objective-C avec Swift, une classe personnalisée UIView attachée à partir de votre code et activé Afficher l'ancre ... mais pas un point
Genevios
1

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
}
AJ9
la source
1
Merci pour cela :) Je viens de le coller dans mon projet en tant que fonction statique et fonctionne comme un charme: D
Kjell
0

En développant la réponse excellente et approfondie 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;
}
Charles Robertson
la source