CALayers n'a pas été redimensionné lors du changement des limites de son UIView. Pourquoi?

101

J'ai un UIViewqui a environ 8 CALayersous-couches différentes ajoutées à sa couche. Si je modifie les limites de la vue (animées), la vue elle-même se rétrécit (je l'ai vérifiée avec a backgroundColor), mais la taille des sous-couches reste inchangée .

Comment résoudre ça?

Geri Borbás
la source

Réponses:

149

J'ai utilisé la même approche que Solin, mais il y a une faute de frappe dans ce code. La méthode doit être:

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

Pour mes besoins, j'ai toujours voulu que la sous-couche ait la taille complète de la vue parent. Mettez cette méthode dans votre classe de vue.

Bois de Chadwick
la source
8
@fishinear êtes-vous sûr? il semble que vous décrivez le cadre. les bornes devraient, en théorie, toujours avoir 0,0 comme origine.
griotspeak
3
@griotspeak - ce n'est en fait pas le cas. Voir la description de la propriété bounds ici: developer.apple.com/library/ios/#documentation/uikit/reference/… - "Par défaut, l'origine du rectangle des limites est définie sur (0, 0) mais vous pouvez modifiez cette valeur pour afficher différentes parties de la vue. "
jdc
Merci beaucoup, j'ai appris depuis quelques cas (vue de défilement, pour un) où ce n'est pas vrai mais j'ai oublié ce commentaire.
griotspeak
31
N'oubliez pas d'appeler [super layoutSubviews], car cela peut provoquer un comportement inattendu dans diverses classes UIKit.
Kpmurphy91
2
Cela ne fonctionnait pas très bien pour moi avec un auto-dimensionnement UITextView(scrollEnabled = NO) et une mise en page automatique. J'avais une vue de texte épinglée à sa super-vue, qui à son tour avait un CALayeren bas d'elle-même (une bordure), et je voulais qu'elle mette à jour la position de la bordure lorsque la hauteur de la vue du texte changeait. Pour une raison quelconque, il a layoutSubiewsété appelé beaucoup trop tard (et la position du calque était animée).
iosdude
26

Puisque CALayer sur l'iPhone ne prend pas en charge les gestionnaires de mise en page, je pense que vous devez faire de la couche principale de votre vue une sous-classe CALayer personnalisée dans laquelle vous remplacez layoutSublayerspour définir les cadres de toutes les sous-couches. Vous devez également remplacer la +layerClassméthode de votre vue pour renvoyer la classe de votre nouvelle sous-classe CALayer.

Ole Begemann
la source
Override + layerClass comme dans le cas du remplacement de couche OpenGL? Je le pense. Définir les cadres des sous-couches? Mis à quoi? Je dois calculer individuellement toutes les images / positions des sous-couches en fonction de la taille des images des super-couches? N'existe-t-il pas de solution de type «contrainte-like» ou «link-to-superlayer»? Oh, mon Dieu, un autre jour hors du temps. Les CGLayers ont été redimensionnés, mais étaient trop lents, les CALayers sont assez rapides, mais n'ont pas été redimensionnés. Tant de surprise. En tous cas merci de votre réponse.
Geri Borbás
3
CALayer sur Mac a des gestionnaires de mise en page pour cela, mais ils ne sont pas disponibles sur l'iPhone. Alors oui, vous devez calculer vous-même les tailles de cadre. Une autre option pourrait être d'utiliser des sous-vues au lieu de sous-couches. Ensuite, vous pouvez définir les masques de redimensionnement automatique des sous-vues en conséquence.
Ole Begemann
2
Oui, les UIViews sont des classes trop chères en termes de performances, c'est pourquoi j'utilise des CALayers.
Geri Borbás
J'ai essayé la manière que vous avez suggérée. Cela fonctionne, et ce n'est pas le cas. Je redimensionne la vue animée, mais les sous-couches se redimensionnent dans une animation différente. Putain de merde, que faire maintenant? Quelle est la méthode pour remplacer dans la sous-classe CALayer ce qui invoque à CHAQUE (!) Image de l'animation de la vue? Y a-t-il?
Geri Borbás
Je suis juste tombé dedans aussi. On dirait que la meilleure option est de sous-classer CALayer comme l'a dit Ole, mais cela semble inutilement encombrant.
Dimitri
15

J'ai utilisé ceci dans l'UIView.

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

super.layoutSublayersOfLayer(layer)
}

Travaille pour moi.

Runo Sahara
la source
Oui, cela a fonctionné pour moi dans iOS 8. Cela fonctionnerait probablement dans les versions antérieures. J'avais un calque d'arrière-plan dégradé et j'avais besoin de redimensionner le calque lorsque l'appareil était tourné.
EPage_Ed
Cela a fonctionné pour moi, mais il a fallu un appel à la super
entropie
C'est la meilleure réponse! Oui, j'ai essayé layoutSubviews mais cela ne casse que la mise en page de mon UITextField comme leftView et rightView disparaissant et plus de texte dans UITextField disparaissant. Peut-être ai-je utilisé l'extension au lieu de l'héritage de UIView, mais c'était très bogué. CECI FONCTIONNE BIEN! THX
Michał Ziobro
Pour une couche avec le dessin personnalisé ( CALayerDelegates » draw(_:in:)), je devais appeler aussi _anySubLayer.setNeedsDisplay(). Ensuite, cela a fonctionné pour moi.
jedwidz
9

J'ai eu le même problème. Dans la couche d'une vue personnalisée, j'ai ajouté deux autres sous-couches. Afin de redimensionner les sous-couches (chaque fois que les limites de la vue personnalisée changent), j'ai implémenté la méthode layoutSubviewsde ma vue personnalisée; dans cette méthode, je mets simplement à jour l'image de chaque sous-couche pour qu'elle corresponde aux limites actuelles de la couche de ma sous-vue.

Quelque chose comme ça:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}
Solin
la source
16
Pour info, vous n'avez pas besoin de if (sublayer1! = Nil), puisque ObjC définit les messages à nil (dans ce cas, -setFrame :) comme un no-op retournant 0.
Andrew Pouliot
7

2017

La réponse littérale à cette question:

"CALayers n'a pas été redimensionné sur les limites de son UIView. Pourquoi?"

est-ce pour le meilleur ou pour le pire

needsDisplayOnBoundsChange

la valeur par défaut est false dans CALayer.

Solution,

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

En effet, je vous dirige vers ce QA

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

ce qui explique ce que fait le contentsScalecadre critique ; vous devez généralement définir cela de la même manière lorsque vous définissez needsDisplayOnBoundsChange.

Fattie
la source
5

Version Swift 3

Dans la cellule personnalisée, ajoutez les lignes suivantes

Déclarer en premier

let gradientLayer: CAGradientLayer = CAGradientLayer()

Puis ajoutez les lignes suivantes

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

Comme [Ole] l'a écrit, CALayer ne prend pas en charge le redimensionnement automatique sur iOS. Vous devez donc ajuster la mise en page manuellement. Mon option était d'ajuster le cadre du calque dans (iOS 7 et versions antérieures)

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

ou (à partir d'iOS 8)

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

Dans votre vue personnalisée, vous devez déclarer une variable pour votre couche personnalisée, ne déclarez pas de variable dans la portée init. Et lancez-le simplement une fois, n'essayez pas de définir une valeur nulle et de réinitialiser

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

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