Alterar o anchorPoint da minha CALayer move a exibição

131

Eu quero alterar o anchorPoint, mas manter a visão no mesmo lugar. Eu tentei NSLog-ing self.layer.positione self.centereles 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
Kenny Winker
fonte

Respostas:

139

A seção Camada e geometria e transformações do Guia de programação da animação principal explica o relacionamento entre a posição de uma CALayer e as propriedades do 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. Ao definir a posição da camada, você define o local do centro da camada no sistema de coordenadas do super-revestimento.

Como a posição é relativa ao anchorPoint da camada, alterar esse anchorPoint enquanto mantém a mesma posição move a camada. Para evitar esse movimento, você precisaria ajustar a posição da camada para dar conta do novo anchorPoint. Uma maneira de fazer isso é agarrar os limites da camada, multiplicar a largura e a altura dos limites pelos antigos e novos valores normalizados do anchorPoint, pegar a diferença dos dois anchorPoints e aplicar essa diferença à posição da camada.

Você pode até conseguir contabilizar a rotação dessa maneira usando CGPointApplyAffineTransform()o CGAffineTransform do UIView.

Brad Larson
fonte
4
Veja a função de exemplo do Magnus para ver como a resposta de Brad pode ser implementada no Objective-C.
Jim Jeffers
Obrigado, mas eu não entendo como anchorPointe positionestão relacionados juntos?
dumbfingers
6
@ ss1271 - Como descrevi acima, o anchorPoint de uma camada é a base do seu sistema de coordenadas. Se estiver definido como (0,5, 0,5), quando você definir a posição de uma camada, estará definindo onde seu centro estará no sistema de coordenadas de sua super camada. Se você definir o anchorPoint como (0,0, 0,5), definir a posição definirá onde estará o centro de sua borda esquerda. Novamente, a Apple tem algumas imagens legais na documentação vinculada acima (à qual eu atualizei a referência).
Brad Larson
Eu usei o método fornecido por Magnus. Quando posiciono e enquadro o NSLog, eles parecem corretos, mas minha visão ainda é movida. Alguma idéia do porquê? É como se a nova posição não fosse levada em consideração.
Alexis19
No meu caso, quero adicionar uma animação na camada usando o método videoCompositionCoreAnimationTool da classe AVVideoCompositionCoreAnimationTool, com o método PostProcessingAsVideoLayer da classe AVVideoCompositionCoreAnimationTool. O que acontece é que metade da minha camada continua desaparecendo enquanto eu a estou girando em torno do eixo y.
Nr5
205

Eu tive o mesmo problema. A solução de Brad Larson funcionou muito bem, mesmo quando a vista é girada. 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
}
Magnus
fonte
2
Perfeito e faz todo sentido quando pude ver seu exemplo aqui. Obrigado por isso!
9119 Jim Jeffers
2
Eu uso esse código nos meus métodos awakeFromNib / initWithFrame, mas ainda não funciona, preciso atualizar a exibição? edit: setNeedsDisplay não funciona
Adam Carter
2
Por que você está usando o view.bounds? Você não deveria estar usando layer.bounds?
precisa saber é o seguinte
1
no meu valor da definição de caso para: view.layer.position não muda nada
AndrewK
5
FWIW, tive que agrupar o método setAnchor em um bloco dispatch_async para que ele funcionasse. Não sei por que, como estou chamando dentro de viewDidLoad. O viewDidLoad não está mais na fila principal do encadeamento?
John Fowler
44

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

Depois, redimensiono, onde ele escala 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 aparece se a vista for girada, pois a propriedade frame não é definida se uma CGAffineTransform tiver sido aplicada.

Kenny Winker
fonte
9
Só queria acrescentar por que isso funciona e parece ser a maneira mais fácil: de acordo com os documentos, a propriedade frame é uma propriedade "composta": "O valor do quadro é derivado das propriedades de limites, anchorPoint e posição". É também por isso que a propriedade "frame" não é animada.
DarkDust 24/11/2010
1
Essa é honestamente minha abordagem preferida. Se você não está tentando manipular os limites e posicionar independentemente ou animar qualquer um deles, basta capturar o quadro antes de alterar o ponto de ancoragem. Nenhuma matemática é necessária.
CIFilter
28

Para mim, era mais fácil entender positione anchorPointquando comecei a compará-lo com meu entendimento de frame.origin no UIView. Uma UIView com frame.origin = (20,30) significa que a UIView está a 20 pontos da esquerda e a 30 pontos da parte superior da visualização pai. Esta distância é calculada a partir de qual ponto de um UIView? É calculado a partir do canto superior esquerdo de um UIView.

Na camada anchorPointmarca o ponto (na forma normalizada, ou seja, 0 a 1) de onde essa distância é calculada, por exemplo, layer.position = (20, 30) significa que a camada anchorPointfica a 20 pontos da esquerda e a 30 pontos do topo da camada pai. Por padrão, um ponto de ancoragem da camada é (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.

2cupsOfTech
fonte
1
Por favor, você pode dizer como posso resolver esse salto de ponto de ancoragem, cujo UIView é definido usando o layout automático, ou seja, com o layout automático, não devemos obter ou definir propriedades de quadros e limites.
SJ
17

Existe uma solução tão simples. Isso é baseado na resposta de Kenny. Mas, em vez de aplicar o quadro antigo, use sua origem e o novo para calcular a tradução, depois aplique essa tradução ao centro. Também funciona com vista girada! Aqui está o código, muito mais simples que outras soluções:

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)
}
Arroz frito
fonte
2
Bom e pequeno método ... funciona bem com algumas pequenas correções: Linha 3: P maiúsculo no anchorPoint. Linha 8: TRANS.Y = NEW - OLD
bob
2
Ainda estou confuso, como posso usá-lo. Estou enfrentando o mesmo problema agora por girar minha visão com rotação superior. Estou estranho que onde devo chamar esse método. Se eu estiver chamando no manipulador de reconhecedores de gestos. Faz desaparecer a vista.
JAck
3
Esta resposta é tão doce que agora tenho diabetes.
GeneCode
14

Para quem precisa, aqui está a solução da Magnus no 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
fonte
1
voto positivo para view.setTranslatesAutoresizingMaskIntoConstraints(true). obrigado cara
Oscar Yuandinata 22/03
1
view.setTranslatesAutoresizingMaskIntoConstraints(true)é necessário ao usar restrições de layout automático.
precisa saber é o seguinte
1
Muito obrigado por isso! O translatesAutoresizingMaskIntoConstraintsé exatamente o que estava faltando, poderia ter aprendido
Ziga
5

Aqui está a resposta do usuário945711 ajustada para o NSView no OS X. Além do NSView não ter uma .centerpropriedade, o quadro do NSView não muda (provavelmente porque o NSViews não vem com um CALayer por padrão), mas a origem do quadro do CALayer é alterada quando o anchorPoint é alterado.

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
fonte
4

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

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;
user3156083
fonte
1
Você não pode comparar posição e anchorPoint, escalas anchorPoint entre 0 a 1,0
Edward Anthony
4

Edite e veja o ponto de ancoragem do UIView diretamente no Storyboard (Swift 3)

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

Crie um novo arquivo para incluir no 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
    }
}

Adicione View ao Storyboard e defina a classe personalizada

Classe personalizada

Agora defina o novo ponto de ancoragem para o UIView

Demonstração

A ativação do Mostrar ponto de ancoragem exibirá um ponto vermelho para que você possa ver melhor onde o ponto de ancoragem será visualmente. Você sempre pode desligá-lo mais tarde.

Isso realmente me ajudou no planejamento de transformações em UIViews.

Mark Moeykens
fonte
Ninguém ponto no UIView, eu usei Objective-C com Swift, anexado classe personalizada UIView do seu código e ligado Mostrar anch ... mas ninguém dot
Genevios
1

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
}
AJ9
fonte
1
Obrigado por isso :) Acabei de colar no meu projeto como uma função estática e funciona como um encanto: D
Kjell
0

Expandindo a excelente e completa resposta de 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;
}
Charles Robertson
fonte