I CALayers non sono stati ridimensionati in base alla modifica dei limiti di UIView. Perché?


101

Ho un UIViewche ha circa 8 diversi CALayersottolivelli aggiunti al suo livello. Se modifico i limiti della vista (animato), la vista stessa si restringe (l'ho controllata con a backgroundColor), ma la dimensione dei sottolivelli rimane invariata .

Come risolverlo?

Risposte:


149

Ho usato lo stesso approccio usato da Solin, ma c'è un errore di battitura in quel codice. Il metodo dovrebbe essere:

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

Per i miei scopi, ho sempre desiderato che il sottolivello avesse le dimensioni complete della vista padre. Metti quel metodo nella tua classe di visualizzazione.


8
@fishinear sei sicuro? sembra che tu stia descrivendo frame. i limiti dovrebbero, in teoria, avere sempre 0,0 come origine.
griotspeak

3
@griotspeak - in realtà non è così. Consulta la descrizione della proprietà bounds qui: developer.apple.com/library/ios/#documentation/uikit/reference/… - "Per impostazione predefinita, l'origine del rettangolo dei limiti è impostata su (0, 0) ma puoi modificare questo valore per visualizzare diverse parti della vista. "
jdc

Molte grazie, da allora ho appreso di alcuni casi (visualizzazione a scorrimento, per esempio) in cui ciò non è vero, ma ho dimenticato questo commento.
griotspeak

31
Non dimenticare di chiamare [super layoutSubviews], poiché ciò può causare comportamenti imprevisti in varie classi UIKit.
Kpmurphy91

2
Questo non ha funzionato molto bene per me con un auto-dimensionamento UITextView(scrollEnabled = NO) e un layout automatico. Avevo una vista di testo bloccata alla sua superview, che a sua volta aveva un CALayernella parte inferiore di se stessa (un bordo), e volevo che aggiornasse la posizione del bordo quando l'altezza della vista di testo cambiava. Per qualche motivo è layoutSubiewsstato chiamato troppo tardi (e la posizione del livello è stata animata).
iosdude

26

Poiché CALayer su iPhone non supporta i gestori di layout, penso che tu debba rendere il livello principale della tua vista una sottoclasse CALayer personalizzata in cui sovrascrivi layoutSublayersper impostare i frame di tutti i sottolivelli. È inoltre necessario sovrascrivere il +layerClassmetodo della visualizzazione per restituire la classe della nuova sottoclasse CALayer.


Override + layerClass come nel caso di override layer OpenGL? Penso di sì. Impostare i frame dei sottolivelli? Impostato su cosa? Devo calcolare individualmente tutti i frame / posizioni dei sublayer a seconda della dimensione del frame dei superlayer? Non esiste una soluzione simile a "vincolo" o "collegamento a superlayer"? Oh, Dio, un altro giorno fuori dal tempo. I livelli CGL sono stati ridimensionati, ma erano troppo lenti, i livelli CAL sono abbastanza veloci, ma non sono stati ridimensionati. Tante sorprese. Grazie comunque per la risposta.
Geri Borbás

3
CALayer su Mac ha gestori di layout per questo, ma non sono disponibili su iPhone. Quindi sì, devi calcolare tu stesso le dimensioni del telaio. Un'altra opzione potrebbe essere quella di utilizzare le visualizzazioni secondarie invece dei sottolivelli. Quindi è possibile impostare di conseguenza le maschere di ridimensionamento automatico delle visualizzazioni secondarie.
Ole Begemann

2
Sì, le visualizzazioni UIV sono classi troppo costose per le prestazioni, ecco perché uso CALayer.
Geri Borbás

Ho provato come mi hai suggerito. Funziona e non lo è. Ridimensiono la vista animata, ma i sottolivelli si ridimensionano in un'animazione diversa. Maledizione, cosa fare adesso? Qual è il metodo per sovrascrivere nella sottoclasse CALayer ciò che invoca su OGNI (!) Fotogramma dell'animazione della vista? C'è qualche?
Geri Borbás

Mi sono imbattuto anche in questo. Sembra che l'opzione migliore sia quella di sottoclassare CALayer come ha detto Ole, ma sembra inutilmente ingombrante.
Dimitri

15

L'ho usato in UIView.

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

super.layoutSublayersOfLayer(layer)
}

Per me va bene.


Sì, questo ha funzionato per me in iOS 8. Presumibilmente avrebbe funzionato nelle versioni precedenti. Avevo un livello di sfondo sfumato e avevo bisogno di ridimensionare il livello quando il dispositivo viene ruotato.
EPage_Ed

Ha funzionato per me, ma aveva bisogno di una chiamata alla super
entropia

Questa è la migliore risposta! Sì, ho provato layoutSubviews ma interrompe solo il layout del mio UITextField come leftView e rightView che scompaiono e più testo in UITextField scompare. Forse ho usato l'estensione invece dell'ereditarietà di UIView, ma era molto bacato. QUESTO FUNZIONA GRANDE! THX
Michał Ziobro

Per un livello con il disegno personalizzato ( CALayerDelegate's draw(_:in:)), ho dovuto chiamare anche _anySubLayer.setNeedsDisplay(). Allora questo ha funzionato per me.
jedwidz

9

Ho avuto lo stesso problema. In un livello della visualizzazione personalizzata ho aggiunto altri due sottolivelli. Per ridimensionare i sottolivelli (ogni volta che cambiano i confini della vista personalizzata), ho implementato il metodo layoutSubviewsdella mia vista personalizzata; all'interno di questo metodo aggiorno semplicemente il frame di ogni sottolivello in modo che corrisponda ai confini correnti del livello della mia sottoview.

Qualcosa come questo:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}

16
Cordiali saluti, non hai bisogno dell'if (sublayer1! = Nil), poiché ObjC definisce i messaggi a zero (in questo caso, -setFrame :) come no-op che restituisce 0.
Andrew Pouliot

7

2017

La risposta letterale a questa domanda:

"I CALayers non sono stati ridimensionati in base alla modifica dei limiti di UIView. Perché?"

è questo nel bene e nel male

needsDisplayOnBoundsChange

il valore predefinito è false in CALayer.

soluzione,

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

In effetti, ti indirizzo a questo QA

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

il che spiega, cosa diavolo fa l' contentsScaleimpostazione critica ; di solito è necessario impostarlo allo stesso modo quando si imposta needsDisplayOnBoundsChange.


5

Versione Swift 3

Nella cella personalizzata, aggiungi le seguenti righe

Dichiara prima

let gradientLayer: CAGradientLayer = CAGradientLayer()

Quindi aggiungi le seguenti righe

override func layoutSubviews() {
    gradientLayer.frame = self.YourCustomView.bounds
}

0

Come ha scritto [Ole], CALayer non supporta il ridimensionamento automatico su iOS. Quindi dovresti regolare il layout manualmente. La mia opzione era di regolare la cornice del livello all'interno (iOS 7 e versioni precedenti)

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

o (a partire da iOS 8)

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

0

Nella tua visualizzazione personalizzata, devi dichiarare la variabile per il tuo livello personalizzato, non dichiarare la variabile nell'ambito di init. E basta iniziarlo una volta, non provare a impostare un valore nullo e reinizializzare

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

        override func layoutSubviews () {
            super.layoutSubviews ()
    // guard let _fillColor = self._fillColor else {return}
            initializeLayout ()
        }
        private func initializeLayout () { 
            customLayer.removeFromSuperView ()
            customLayer.frame = layer.bounds
                   
            layer.insertSubview (at: 0)
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.