Alterar o anchorPoint do meu CALayer move a vista

Eu quero alterar o anchorPoint , mas manter a visão no mesmo lugar. Eu tentei self.layer.position -ing self.layer.position e self.center e ambos permanecem os mesmos, independentemente das alterações no anchorPoint. No entanto, minha visão se move!

Alguma dica sobre como fazer isso?

 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); 

A saída é:

 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 

A seção Geometria de Camada e Transformações do Guia de Programação de Animação Principal explica a relação entre a posição de uma CALayer e as propriedades anchorPoint. Basicamente, a posição de uma camada é especificada em termos da localização do anchorPoint da camada. Por padrão, o anchorPoint de uma camada é (0,5, 0,5), que fica no centro da camada. Quando você define a posição da camada, você está definindo a localização do centro da camada no sistema de coordenadas da super camada.

Como a posição é relativa ao anchorPoint da camada, a alteração desse anchorPoint, mantendo a mesma posição, move a camada. Para evitar esse movimento, você precisaria ajustar a posição da camada para considerar o novo âncora. Uma maneira de fazer isso é pegar os limites da camada, multiplicar a largura e a altura dos limites pelos valores normalizados do anchorPoint antigo e novo, tirar a diferença dos dois anchorPoints e aplicar essa diferença à posição da camada.

Você pode até mesmo ser capaz de contabilizar a rotação dessa maneira usando CGPointApplyAffineTransform() com o CGAffineTransform do seu UIView.

Eu tive o mesmo problema. A solução de Brad Larson funcionou bem mesmo quando a vista foi rodada. Aqui está sua solução traduzida em código.

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

E o equivalente rápido:

 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 } 

A chave para resolver isso foi usar a propriedade frame, que é estranhamente a única coisa que muda.

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 

Então eu faço o meu redimensionamento, onde é escalável a partir do anchorPoint. Então eu tenho que restaurar o antigo 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: isso é extinto se a exibição for girada, pois a propriedade de quadro é indefinida se uma CGAffineTransform tiver sido aplicada.

Para mim, entender a position e anchorPoint foi mais fácil quando comecei a compará-la com o meu entendimento de frame.origin no UIView. Um UIView com frame.origin = (20,30) significa que o UIView está a 20 pontos da esquerda e a 30 pontos do topo da sua vista pai. Essa distância é calculada a partir de qual ponto de um UIView? É calculado a partir do canto superior esquerdo de um UIView.

Na camada anchorPoint marca o ponto (na forma normalizada ie 0 a 1) de onde esta distância é calculada, por exemplo, layer.position = (20, 30) significa que a camada anchorPoint é 20 pontos da esquerda e 30 pontos da parte superior da sua mãe. camada. Por padrão, uma camada anchorPoint é (0.5, 0.5), portanto, o ponto de cálculo da distância fica bem no centro da camada. A figura a seguir ajudará a esclarecer meu ponto:

insira a descrição da imagem aqui

anchorPoint também é o ponto em torno do qual a rotação ocorrerá caso você aplique uma transformação à camada.

Existe uma solução tão simples. Isto é baseado na resposta de Kenny. Mas, em vez de aplicar o quadro antigo, use sua origem e os novos para calcular a transição e aplique essa transição ao centro. Trabalha com visão girada também! Aqui está o código, muito mais simples que outras soluções:

 - (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); } 

E a versão 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) } 

Para quem precisa, aqui está a solução da Magnus em 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 } 

Editar e ver o ponto de ancoragem de UIView no storyboard (Swift 3)

Essa é uma solução alternativa que permite alterar o ponto de ancoragem por meio do Inspetor de Atributos e possui outra propriedade para exibir o ponto de ancoragem para confirmação.

Criar novo arquivo para include em seu projeto

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

Adicionar visualização ao storyboard e definir a class personalizada

Classe personalizada

Agora defina o novo ponto de ancoragem para o UIView

Demonstração

Ativar o Show Anchor Point mostrará um ponto vermelho para que você possa ver melhor onde o ponto de ancoragem será visualmente. Você sempre pode desligá-lo depois.

Isso realmente me ajudou ao planejar transformações em UIViews.

Aqui está a resposta de user945711 ajustada para NSView no OS X. Além do NSView não ter uma propriedade .center , o frame do NSView não muda (provavelmente porque NSViews não vem com um CALayer por padrão) mas a origem do frame CALayer muda quando o anchorPoint é mudou.

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

Se você alterar anchorPoint, sua posição também será alterada, A menos que sua origem seja zero ponto CGPointZero .

 position.x == origin.x + anchorPoint.x; position.y == origin.y + anchorPoint.y; 

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

Expandindo a grande e completa resposta do Magnus, criei uma versão que funciona em subcamadas:

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