web-dev-qa-db-fra.com

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

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
124
Kenny Winker

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.

133
Brad Larson

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

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é.

43
Kenny Winker

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:

enter image description here

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

26
2cupsOfTech

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)
}
15
Fried Rice

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
}
9
Corey Davis

Editez et visualisez le point d'ancrage d'UIView sur le 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 possède une autre propriété permettant d'afficher le point d'ancrage pour confirmation.

Créer 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 le cours personnalisé

 Custom Class

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

 Demonstration

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.

5
Mark Moeykens

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)
}
5
iMaddin

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;
4
user3156083

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

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;
}
0
Charles Robertson