CALayers não foi redimensionado na mudança de limites de seu UIView. Por quê?

100

Eu tenho um UIViewque tem cerca de 8 CALayersubcamadas diferentes adicionadas à sua camada. Se eu modificar os limites da visualização (animados), a visualização em si encolhe (marquei com a backgroundColor), mas o tamanho das subcamadas permanece o mesmo .

Como resolver isso?

Geri Borbás
fonte

Respostas:

149

Usei a mesma abordagem que o Solin usou, mas há um erro de digitação nesse código. O método deve ser:

- (void)layoutSubviews {
  [super layoutSubviews];
  // resize your layers based on the view's new bounds
  mylayer.frame = self.bounds;
}

Para meus propósitos, sempre quis que a subcamada tivesse o tamanho total da visualização pai. Coloque esse método em sua classe de visualização.

Chadwick Wood
fonte
8
@fishinear tem certeza? parece que você está descrevendo um quadro. os limites devem, teoricamente, sempre ter 0,0 como sua origem.
griotspeak
3
@griotspeak - não é o caso. Veja a descrição da propriedade bounds aqui: developer.apple.com/library/ios/#documentation/uikit/reference/… - "Por padrão, a origem do retângulo de limites é definida como (0, 0), mas você pode altere este valor para exibir diferentes partes da visualização. "
jdc
Muito obrigado, desde então soube de alguns casos (visualização de rolagem, por exemplo) em que isso não é verdade, mas esqueci este comentário.
griotspeak
31
Não se esqueça de chamar [super layoutSubviews], pois isso pode causar um comportamento inesperado em várias classes de UIKit.
Kpmurphy91
2
Isso não funcionou muito bem para mim com dimensionamento UITextViewautomático (scrollEnabled = NO) e layout automático. Eu tinha uma visualização de texto fixada em sua supervisão, que por sua vez tinha um CALayerna parte inferior de si mesma (uma borda), e queria atualizar a posição da borda quando a altura da visualização de texto mudasse. Por algum motivo, layoutSubiewsfoi chamado tarde demais (e a posição da camada foi animada).
iosdude
26

Como CALayer no iPhone não oferece suporte a gerenciadores de layout, acho que você deve tornar a camada principal de sua visualização uma subclasse CALayer personalizada na qual você sobrescreve layoutSublayerspara definir os quadros de todas as subcamadas. Você também deve substituir o +layerClassmétodo de sua visão para retornar a classe de sua nova subclasse CALayer.

Ole Begemann
fonte
Override + layerClass como no caso de override de camada OpenGL? Pense assim. Definir os quadros das subcamadas? Definir para quê? Tenho que calcular todos os frames / posições das subcamadas individualmente dependendo do tamanho do frame das supercamadas? Não existe uma solução do tipo "restrição" ou "link para supercamada"? Oh, Deus, mais um dia fora do prazo. CGLayers foi redimensionado, mas estava muito lento, CALayers são rápidos o suficiente, mas não foram redimensionados. Tantas surpresas. Obrigado pela resposta de qualquer maneira.
Geri Borbás
3
CALayer no Mac tem gerenciadores de layout para isso, mas eles não estão disponíveis no iPhone. Portanto, sim, você mesmo deve calcular os tamanhos de quadro. Outra opção pode ser usar subvisualizações em vez de subcamadas. Então, você pode definir as máscaras de redimensionamento automático das subvisualizações de acordo.
Ole Begemann,
2
Sim, UIViews são classes muito caras de desempenho, é por isso que eu uso CALayers.
Geri Borbás
Eu tentei do jeito que você sugeriu. Funciona e não é. Eu redimensiono a visualização animada, mas as subcamadas são redimensionadas em uma animação diferente. Inferno sangrento, o que fazer agora? Qual é o método para substituir na subclasse CALayer o que invoca em CADA (!) Quadro da animação da visualização? Existe algum?
Geri Borbás
Também esbarrei nisso também. Parece que a melhor opção é criar uma subclasse de CALayer, como Ole disse, mas parece desnecessariamente complicado.
Dimitri
14

Usei isso no UIView.

-(void)layoutSublayersOfLayer:(CALayer *)layer
{
    if (layer == self.layer)
    {
        _anySubLayer.frame = layer.bounds;
    }

super.layoutSublayersOfLayer(layer)
}

Funciona para mim.

Runo Sahara
fonte
Sim, isso funcionou para mim no iOS 8. Provavelmente funcionaria nas versões anteriores. Eu tinha uma camada de fundo gradiente e precisava redimensionar a camada quando o dispositivo era girado.
EPage_Ed
Trabalhou para mim, mas tinha necessidade de uma chamada para super
entropia
Esta é a melhor resposta! Sim, eu tentei layoutSubviews, mas ele só quebra o layout do meu UITextField como leftView e rightView desaparecendo e mais texto em UITextField desaparecendo. Talvez eu tenha usado extensão em vez de herança de UIView, mas era muito problemático. ISSO FUNCIONA MUITO BEM! THX
Michał Ziobro
Para uma camada com desenho ( CALayerDelegates draw(_:in:)) personalizado ( s ), tive que chamar também _anySubLayer.setNeedsDisplay(). Então isso funcionou para mim.
jedwidz
9

Eu tive o mesmo problema. Na camada de uma visualização personalizada, adicionei mais duas subcamadas. Para redimensionar as subcamadas (toda vez que os limites da visualização personalizada mudam), implementei o método layoutSubviewsda minha visualização personalizada; dentro desse método, eu apenas atualizo o quadro de cada subcamada para corresponder aos limites atuais da camada da minha subvisualização.

Algo assim:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}
Solin
fonte
16
Para sua informação, você não precisa do if (sublayer1! = Nil), uma vez que ObjC define as mensagens como nulo (neste caso, -setFrame :) como um no-op retornando 0.
Andrew Pouliot
7

2017

A resposta literal a esta pergunta:

"CALayers não foram redimensionados com a alteração dos limites do UIView. Por quê?"

isso é para melhor ou para pior

needsDisplayOnBoundsChange

o padrão é falso em CALayer.

solução,

class CircularGradientViewLayer: CALayer {
    
    override init() {
        
        super.init()
        needsDisplayOnBoundsChange = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override open func draw(in ctx: CGContext) {
        go crazy drawing in .bounds
    }
}

Na verdade, eu direciono você para este controle de qualidade

https://stackoverflow.com/a/47760444/294884

o que explica o que diabos o contentsScalecenário crítico faz; você geralmente precisa definir isso igualmente ao definir needsDisplayOnBoundsChange.

Fattie
fonte
5

Versão Swift 3

Na célula personalizada, adicione as seguintes linhas

Declare primeiro

let gradientLayer: CAGradientLayer = CAGradientLayer()

Em seguida, adicione as seguintes linhas

override func layoutSubviews() {
    gradientLayer.frame = self.YourCustomView.bounds
}
Vinay Krishna Gupta
fonte
0

Como [Ole] escreveu, CALayer não oferece suporte a redimensionamento automático no iOS. Portanto, você deve ajustar o layout manualmente. Minha opção era ajustar o quadro da camada dentro (iOS 7 e anterior)

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 

ou (a partir do iOS 8)

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinato
DevGansta
fonte
0

Em sua visualização personalizada, você precisa declarar a variável para sua camada personalizada, não declare a variável no init do escopo. E apenas inicie uma vez, não tente definir um valor nulo e reinicie

    class CustomView: UIView {
        var customLayer: CALayer = CALayer ()

        override func layoutSubviews () {
            super.layoutSubviews ()
    // guarda let _fillColor = self._fillColor else {return}
            initializeLayout ()
        }
        função privada initializeLayout () { 
            customLayer.removeFromSuperView ()
            customLayer.frame = layer.bounds
                   
            layer.insertSubview (em: 0)
        }
    }
Lê Tấn Thành
fonte