Los CALayers no cambiaron de tamaño en el cambio de límites de su UIView. ¿Por qué?

101

Tengo un UIViewque tiene alrededor de 8 CALayersubcapas diferentes agregadas a su capa. Si modifico los límites de la vista (animados), entonces la vista en sí se encoge (lo verifiqué con a backgroundColor), pero el tamaño de las subcapas permanece sin cambios .

¿Cómo solucionar esto?

Geri Borbás
fuente

Respuestas:

149

Usé el mismo enfoque que usó Solin, pero hay un error tipográfico en ese código. El método debe ser:

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

Para mis propósitos, siempre quise que la subcapa tuviera el tamaño completo de la vista principal. Pon ese método en tu clase de vista.

Madera de Chadwick
fuente
8
@fishinear, ¿estás seguro? parece que estás describiendo un marco. los límites deberían, teóricamente, tener siempre 0,0 como origen.
griotspeak
3
@griotspeak: ese no es el caso. Vea la descripción de la propiedad de límites aquí: developer.apple.com/library/ios/#documentation/uikit/reference/… - "De forma predeterminada, el origen del rectángulo de límites se establece en (0, 0) pero puede cambie este valor para mostrar diferentes partes de la vista. "
jdc
Muchas gracias, desde entonces me enteré de algunos casos (vista de desplazamiento, por ejemplo) en los que eso no es cierto, pero me olvidé de este comentario.
griotspeak
31
No olvide llamar a [super layoutSubviews], ya que esto puede causar un comportamiento inesperado en varias clases de UIKit.
Kpmurphy91
2
Esto no funcionó muy bien para mí con un tamaño propio UITextView(scrollEnabled = NO) y un diseño automático. Tenía una vista de texto fijada a su supervista, que a su vez tenía una CALayeren la parte inferior de sí misma (un borde), y quería que actualizara la posición del borde cuando cambiaba la altura de la vista de texto. Por alguna razón, layoutSubiewsse llamó demasiado tarde (y la posición de la capa se animó).
iosdude
26

Dado que CALayer en el iPhone no admite administradores de diseño, creo que debe hacer que la capa principal de su vista sea una subclase CALayer personalizada en la que anula layoutSublayerspara establecer los marcos de todas las subcapas. También debe anular el +layerClassmétodo de su vista para devolver la clase de su nueva subclase CALayer.

Ole Begemann
fuente
Override + layerClass como en el caso de override de capa OpenGL? Eso creo. ¿Establecer los marcos de las subcapas? ¿Establecer a qué? ¿Tengo que calcular todos los fotogramas / posiciones de las subcapas individualmente según el tamaño del fotograma de las supercapas? ¿No existe alguna solución de tipo "restricción" o "vínculo a supercapa"? Dios mío, otro día fuera de plazo. Los CGLayers cambiaron de tamaño, pero estaban demasiado retrasados, los CALayers son lo suficientemente rápidos, pero no se redimensionaron. Tantas sorpresas. Gracias por la respuesta, de todos modos.
Geri Borbás
3
CALayer en Mac tiene administradores de diseño para esto, pero no están disponibles en iPhone. Así que sí, tienes que calcular tú mismo los tamaños de cuadro. Otra opción podría ser utilizar subvistas en lugar de subcapas. Luego, podría configurar las máscaras de tamaño automático de las subvistas en consecuencia.
Ole Begemann
2
Sí, las UIViews son clases demasiado caras para el rendimiento, por eso uso CALayers.
Geri Borbás
Intenté de la forma que sugieres. Funciona y no lo es. Cambio el tamaño de la vista animada, pero las subcapas cambian de tamaño en una animación diferente. Maldita sea, ¿qué hacer ahora? ¿Cuál es el método para anular en la subclase CALayer lo que invoca en CADA (!) Cuadro de la animación de la vista? ¿Hay alguna?
Geri Borbás
También me encontré con esto. Parece que la mejor opción es subclasificar CALayer como dijo Ole, pero parece innecesariamente engorroso.
Dimitri
14

Usé esto en UIView.

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

super.layoutSublayersOfLayer(layer)
}

Funciona para mi.

Runo Sahara
fuente
Sí, esto funcionó para mí en iOS 8. Presumiblemente funcionaría en versiones anteriores. Tenía una capa de fondo degradado y necesitaba cambiar el tamaño de la capa cuando se gira el dispositivo.
EPage_Ed
Funcionó para mí, pero necesitaba una llamada a la super
entropía
¡Esta es la mejor respuesta! Sí, probé layoutSubviews pero solo rompe el diseño de mi UITextField como leftView y rightView desapareciendo y más texto en UITextField desapareciendo. Tal vez he usado la extensión en lugar de la herencia de UIView, pero tenía muchos errores. ¡ESTO FUNCIONA MUY BIEN! THX
Michał Ziobro
Para una capa con dibujo personalizado ( CALayerDelegate's draw(_:in:)), tuve que llamar también _anySubLayer.setNeedsDisplay(). Entonces esto funcionó para mí.
jedwidz
9

Yo tuve el mismo problema. En la capa de una vista personalizada, agregué dos subcapas más. Para cambiar el tamaño de las subcapas (cada vez que cambian los límites de la vista personalizada), implementé el método layoutSubviewsde mi vista personalizada; dentro de este método, simplemente actualizo el marco de cada subcapa para que coincida con los límites actuales de la capa de mi subvista.

Algo como esto:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}
Solin
fuente
16
Para su información, no necesita el if (sublayer1! = Nil), ya que ObjC define los mensajes a nil (en este caso, -setFrame :) como un no-op que devuelve 0.
Andrew Pouliot
7

2017

La respuesta literal a esta pregunta:

"No se cambió el tamaño de CALayers en el cambio de límites de su UIView. ¿Por qué?"

es que para bien o para mal

needsDisplayOnBoundsChange

por defecto es falso en CALayer.

solución,

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

De hecho, te dirijo a este QA

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

lo que explica, qué diablos hace el contentsScaleescenario crítico ; por lo general, debe establecerlo igualmente cuando establece needDisplayOnBoundsChange.

Fattie
fuente
5

Versión Swift 3

En la celda personalizada, agregue las siguientes líneas

Declarar primero

let gradientLayer: CAGradientLayer = CAGradientLayer()

Luego agrega las siguientes líneas

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

Como [Ole] escribió, CALayer no admite el tamaño automático en iOS. Por lo tanto, debe ajustar el diseño manualmente. Mi opción fue ajustar el marco de la capa dentro (iOS 7 y anteriores)

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

o (a partir de iOS 8)

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

En su vista personalizada, necesita declarar la variable para su capa personalizada, no declare la variable en el alcance init. Y simplemente inícielo una vez, no intente establecer un valor nulo y reiniciar

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

        anular func layoutSubviews () {
            super.layoutSubviews ()
    // guard let _fillColor = self._fillColor else {return}
            initializeLayout ()
        }
        private func initializeLayout () { 
            customLayer.removeFromSuperView ()
            customLayer.frame = layer.bounds
                   
            layer.insertSubview (en: 0)
        }
    }
Lê Tấn Thành
fuente