ios - Como faço para ajustar o ponto de ancoragem de um CALayer quando o Auto Layout está sendo usado?




autolayout (8)

Nota : As coisas mudaram desde que esta pergunta foi feita; veja here para uma boa visão geral recente.

Antes do layout automático, você poderia alterar o ponto de ancoragem da camada de uma vista sem mover a vista, armazenando o quadro, definindo o ponto de ancoragem e restaurando o quadro.

Em um mundo de layout automático, não definimos mais os quadros, mas as restrições não parecem adequadas à tarefa de ajustar a posição de uma vista de volta para onde queremos. Você pode cortar as restrições para reposicionar sua visualização, mas em rotação ou outros eventos de redimensionamento, eles se tornam inválidos novamente.

A idéia brilhante a seguir não funciona, pois cria um "Pareamento inválido de atributos de layout (esquerda e largura)":

layerView.layer.anchorPoint = CGPointMake(1.0, 0.5);
// Some other size-related constraints here which all work fine...
[self.view addConstraint:
    [NSLayoutConstraint constraintWithItem:layerView
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:layerView 
                                 attribute:NSLayoutAttributeWidth 
                                multiplier:0.5 
                                  constant:20.0]];

Minha intenção aqui era definir a borda esquerda de layerView , a visão com o ponto de ancoragem ajustado, para metade de sua largura mais 20 (a distância que eu quero inserir da borda esquerda da layerView visão).

É possível alterar o ponto de ancoragem, sem alterar a localização de uma vista, em uma vista que é definida com layout automático? Preciso usar valores codificados e editar a restrição em cada rotação? Eu espero que não.

Eu preciso mudar o ponto de ancoragem para que, quando eu aplicar uma transformação à vista, eu tenha o efeito visual correto.


É um grande tópico e eu não li todos os comentários, mas estava enfrentando o mesmo problema.

Eu tive uma visão do XIB com autolayout. E eu queria atualizar sua propriedade de transformação. Incorporar a exibição em uma visualização de contêiner não resolve meu problema porque o autolayout estava agindo de forma estranha na exibição do contêiner. É por isso que acabei de adicionar a segunda visualização de contêiner para conter a visualização de contêiner que contém minha visualização e estava aplicando transformações nela.


Esta pergunta e respostas me inspirou a resolver meus próprios problemas com o Autolayout e o dimensionamento, mas com scrollviews. Eu criei um exemplo da minha solução no github:

https://github.com/hansdesmedt/AutoLayout-scrollview-scale

Este é um exemplo de um UIScrollView com paginação personalizada feita completamente no AutoLayout e é escalável (CATransform3DMakeScale) com toque longo e toque para aumentar o zoom. iOS 6 e 7 compatível.


Eu acho um jeito simples. E funciona no iOS 8 e no iOS 9.

Como ajustar anchorPoint ao usar o layout baseado em quadro:

let oldFrame = layerView.frame
layerView.layer.anchorPoint = newAnchorPoint
layerView.frame = oldFrame

Quando você ajusta a âncora da vista com o layout automático, você faz a mesma coisa, mas de maneira restrita. Quando o anchorPoint muda de (0,5, 0,5) para (1, 0,5), o layerView se move para a esquerda com uma distância de metade do comprimento da largura da vista, então você precisa compensar isso.

Eu não entendo a restrição na questão. Então, suponha que você adicione uma restrição centerX relativa ao superView centerX com uma constante: layerView.centerX = superView.centerX + constante

layerView.layer.anchorPoint = CGPoint(1, 0.5)
let centerXConstraint = .....
centerXConstraint.constant = centerXConstraint.constant + layerView.bounds.size.width/2

Eu tinha um Isuue parecido e acabei de ouvir o Back da Autolayout Team na Apple. Eles sugerem para usar o Container View Approach sugere Matt, mas eles criam uma subclasse de UIView para substituir layoutSubviews e aplicar o código de layout personalizado lá - funciona como um encanto

O arquivo de cabeçalho se parece com isso para que você possa vincular sua subvisão de escolha diretamente do Interface Builder

#import <UIKit/UIKit.h>

@interface BugFixContainerView : UIView
@property(nonatomic,strong) IBOutlet UIImageView *knobImageView;
@end

e o m File aplica o código especial assim:

#import "BugFixContainerView.h"

@implementation BugFixContainerView
- (void)layoutSubviews
{
    static CGPoint fixCenter = {0};
    [super layoutSubviews];
    if (CGPointEqualToPoint(fixCenter, CGPointZero)) {
        fixCenter = [self.knobImageView center];
    } else {
        self.knobImageView.center = fixCenter;
    }
}
@end

Como você pode ver, ele captura o ponto central da Vista quando é chamado pela primeira vez e reutiliza essa Posição em outras chamadas para colocar a Visualização de acordo. Isso substitui o código de autolayout nesse sentido, que ocorre após [super layoutSubviews]; que contém o código autolayout.

Assim, não há mais necessidade de evitar o Autolayout, mas você pode criar seu próprio autolayout quando os Behaviors padrão não forem mais apropriados. É claro que você pode aplicar coisas muito mais complicadas do que as do Exemplo, mas isso foi tudo de que eu precisei, pois meu App só pode usar o Modo Retrato.


Minha solução atual é ajustar manualmente a posição da camada em viewDidLayoutSubviews . Este código também pode ser usado em layoutSubviews para uma subclasse view, mas no meu caso minha visão é uma visão de nível superior dentro de um view controller, então isso significava que eu não precisava criar uma subclasse UIView.

Parece muito esforço para que outras respostas sejam bem-vindas.

-(void)viewDidLayoutSubviews
{
    for (UIView *view in self.view.subviews)
    {
        CGPoint anchorPoint = view.layer.anchorPoint;
        // We're only interested in views with a non-standard anchor point
        if (!CGPointEqualToPoint(CGPointMake(0.5, 0.5),anchorPoint))
        {
            CGFloat xDifference = anchorPoint.x - 0.5;
            CGFloat yDifference = anchorPoint.y - 0.5;
            CGPoint currentPosition = view.layer.position;

            // Use transforms if we can, otherwise manually calculate the frame change
            // Assuming a transform is in use since we are changing the anchor point. 
            if (CATransform3DIsAffine(view.layer.transform))
            {
                CGAffineTransform current = CATransform3DGetAffineTransform(view.layer.transform);
                CGAffineTransform invert = CGAffineTransformInvert(current);
                currentPosition = CGPointApplyAffineTransform(currentPosition, invert);
                currentPosition.x += (view.bounds.size.width * xDifference);
                currentPosition.y += (view.bounds.size.height * yDifference);
                currentPosition = CGPointApplyAffineTransform(currentPosition, current);
            }
            else
            {
                CGFloat transformXRatio = view.bounds.size.width / view.frame.size.width;

                if (xDifference < 0)
                    transformXRatio = 1.0/transformXRatio;

                CGFloat transformYRatio = view.bounds.size.height / view.frame.size.height;
                if (yDifference < 0)
                    transformYRatio = 1.0/transformYRatio;

                currentPosition.x += (view.bounds.size.width * xDifference) * transformXRatio;
                currentPosition.y += (view.bounds.size.height * yDifference) * transformYRatio;
            }
            view.layer.position = currentPosition;
        }

    }
}

Se você estiver usando o layout automático, não vejo como a configuração manual da posição será veiculada a longo prazo, pois o layout automático acabará por atrapalhar o valor da posição que você definiu ao calcular seu próprio layout.

Em vez disso, o que é necessário é modificar as próprias restrições de layout para compensar as alterações produzidas pela configuração do anchorPoint. A função a seguir faz isso para exibições não transformadas.

/**
  Set the anchorPoint of view without changing is perceived position.

 @param view view whose anchorPoint we will mutate
 @param anchorPoint new anchorPoint of the view in unit coords (e.g., {0.5,1.0})
 @param xConstraint an NSLayoutConstraint whose constant property adjust's view x.center
 @param yConstraint an NSLayoutConstraint whose constant property adjust's view y.center

  As multiple constraints can contribute to determining a view's center, the user of this
 function must specify which constraint they want modified in order to compensate for the
 modification in anchorPoint
 */
void SetViewAnchorPointMotionlesslyUpdatingConstraints(UIView * view,CGPoint anchorPoint,
                                                       NSLayoutConstraint * xConstraint,
                                                       NSLayoutConstraint * yConstraint)
{
  // assert: old and new anchorPoint are in view's unit coords
  CGPoint const oldAnchorPoint = view.layer.anchorPoint;
  CGPoint const newAnchorPoint = anchorPoint;

  // Calculate anchorPoints in view's absolute coords
  CGPoint const oldPoint = CGPointMake(view.bounds.size.width * oldAnchorPoint.x,
                                 view.bounds.size.height * oldAnchorPoint.y);
  CGPoint const newPoint = CGPointMake(view.bounds.size.width * newAnchorPoint.x,
                                 view.bounds.size.height * newAnchorPoint.y);

  // Calculate the delta between the anchorPoints
  CGPoint const delta = CGPointMake(newPoint.x-oldPoint.x, newPoint.y-oldPoint.y);

  // get the x & y constraints constants which were contributing to the current
  // view's position, and whose constant properties we will tweak to adjust its position
  CGFloat const oldXConstraintConstant = xConstraint.constant;
  CGFloat const oldYConstraintConstant = yConstraint.constant;

  // calculate new values for the x & y constraints, from the delta in anchorPoint
  // when autolayout recalculates the layout from the modified constraints,
  // it will set a new view.center that compensates for the affect of the anchorPoint
  CGFloat const newXConstraintConstant = oldXConstraintConstant + delta.x;
  CGFloat const newYConstraintConstant = oldYConstraintConstant + delta.y;

  view.layer.anchorPoint = newAnchorPoint;
  xConstraint.constant = newXConstraintConstant;
  yConstraint.constant = newYConstraintConstant;
  [view setNeedsLayout];
}

Eu admito que isso provavelmente não é tudo o que você esperava, já que geralmente a única razão pela qual você deseja modificar o anchorPoint é definir uma transformação. Isso exigiria uma função mais complexa que atualizasse as restrições de layout para refletir todas as alterações de quadro que poderiam ser causadas pela própria propriedade de transformação. Isso é complicado porque as transformações podem fazer muito para o quadro. Uma conversão de escala ou rotação tornaria o quadro maior, por isso precisaríamos atualizar qualquer restrição de largura ou altura, etc.

Se você estiver usando apenas a transformação para uma animação temporária, o que está acima pode ser suficiente, pois não acredito que o layout automático impeça a animação em andamento de apresentar imagens que representem violações puramente transitórias das restrições.


tl; dr Digamos que você alterou o ponto de ancoragem para (0, 0). O ponto de ancoragem agora está no canto superior esquerdo. Toda vez que você vê o centro da palavra no layout automático, você deve pensar no canto superior esquerdo .

Quando você ajusta seu anchorPoint, basta alterar a semântica do AutoLayout. O layout automático não interferirá no seu anchorPoint nem vice-versa. Se você não entende isso, você vai ter um mau momento .

Exemplo:

Figura A. Nenhuma modificação do ponto de ancoragem

#Before changing anchor point to top-left
view.size == superview.size
view.center == superview.center

Figura B. Ponto de ancoragem alterado para o canto superior esquerdo

view.layer.anchorPoint = CGPointMake(0, 0)
view.size == superview.size
view.center == superview.topLeft                <----- L0-0K, center is now top-left

A figura A e a figura B parecem exatamente iguais. Nada mudou. Apenas a definição de que centro se refere a mudou.


tl: dr: Você pode criar uma saída para uma das restrições para que ela possa ser removida e adicionada novamente.

Eu criei um novo projeto e adicionei uma visão com um tamanho fixo no centro. As restrições são mostradas na imagem abaixo.

Em seguida, adicionei uma saída para a vista que vai girar e para a restrição center x alignment.

@property (weak, nonatomic) IBOutlet UIView *rotatingView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *xAlignmentContstraint;

Mais tarde, no viewDidAppear eu calculo o novo ponto de ancoragem

UIView *view = self.rotatingView;

CGPoint rotationPoint = // The point I'm rotating around... (only X differs)
CGPoint anchorPoint = CGPointMake((rotationPoint.x-CGRectGetMinX(view.frame))/CGRectGetWidth(view.frame),
                                  (rotationPoint.y-CGRectGetMinY(view.frame))/CGRectGetHeight(view.bounds));

CGFloat xCenterDifference = rotationPoint.x-CGRectGetMidX(view.frame);

view.layer.anchorPoint = anchorPoint;

Então eu removo a restrição que eu tenho uma saída para, criar um novo que é compensado e adicioná-lo novamente. Depois disso, eu digo a visão com a restrição alterada que precisa para atualizar as restrições.

[self.view removeConstraint:self.xAlignmentContstraint];
self.xAlignmentContstraint = [NSLayoutConstraint constraintWithItem:self.rotatingView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0
                                                           constant:xDiff];
[self.view addConstraint:self.xAlignmentContstraint];
[self.view needsUpdateConstraints];

Por fim, apenas adiciono a animação de rotação à vista rotativa.

CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotate.toValue = @(-M_PI_2);
rotate.autoreverses = YES;
rotate.repeatCount = INFINITY;
rotate.duration = 1.0;
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 

[view.layer addAnimation:rotate forKey:@"myRotationAnimation"];

A camada rotativa parece estar centralizada (o que deveria), mesmo quando se gira o dispositivo ou faz com que ele atualize as restrições. A nova restrição e o ponto de ancoragem alterado se anulam visualmente.





autolayout