Rozmiar CALayers nie został zmieniony po zmianie granic jego UIView. Czemu?

101

Mam jedną, UIViewktóra ma około 8 różnych CALayerpodwarstw dodanych do swojej warstwy. Jeśli zmodyfikuję granice widoku (animowane), to sam widok się kurczy (sprawdziłem to z a backgroundColor), ale rozmiar podwarstw pozostaje niezmieniony .

Jak to rozwiązać?

Geri Borbás
źródło

Odpowiedzi:

149

Użyłem tego samego podejścia, którego użył Solin, ale w tym kodzie jest literówka. Metoda powinna być:

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

Dla moich celów zawsze chciałem, aby podwarstwa miała pełny rozmiar widoku rodzica. Umieść tę metodę w swojej klasie widoku.

Chadwick Wood
źródło
8
@fishinear czy na pewno? wygląda na to, że opisujesz ramkę. granice powinny, teoretycznie, zawsze mieć początek 0,0.
griotspeak
3
@griotspeak - tak naprawdę nie jest. Zobacz opis właściwości bounds tutaj: developer.apple.com/library/ios/#documentation/uikit/reference/… - „Domyślnie początek prostokąta bounds jest ustawiony na (0, 0), ale możesz zmień tę wartość, aby wyświetlić różne części widoku ”.
jdc
Wielkie dzięki, od tego czasu dowiedziałem się o kilku przypadkach (na przykład widok przewijania), w których to nieprawda, ale zapomniałem o tym komentarzu.
griotspeak
31
Nie zapomnij wywołać [super layoutSubviews], ponieważ może to spowodować nieoczekiwane zachowanie w różnych klasach UIKit.
Kpmurphy91
2
Nie działało to dla mnie zbyt dobrze z UITextViewsamodopasowaniem (scrollEnabled = NO) i automatycznym układem. Miałem widok tekstu przypięty do jego superviewu, który z kolei miał CALayeru dołu samego siebie (obramowanie) i chciałem, aby aktualizował położenie obramowania, gdy zmieniła się wysokość widoku tekstu. Z jakiegoś powodu layoutSubiewszostał wywołany o wiele za późno (a pozycja warstwy została animowana).
iosdude
26

Ponieważ CALayer na iPhonie nie obsługuje menedżerów układu, myślę, że musisz uczynić główną warstwę widoku niestandardową podklasą CALayer, w której nadpisujesz, layoutSublayersaby ustawić klatki wszystkich podwarstw. Musisz również nadpisać +layerClassmetodę swojego widoku, aby zwrócić klasę nowej podklasy CALayer.

Ole Begemann
źródło
Override + layerClass jak w przypadku override warstwy OpenGL? Tak myślę. Ustawić ramy podwarstw? Ustaw na co? Muszę obliczyć klatki / pozycje wszystkich podwarstw indywidualnie w zależności od rozmiaru ramy superwarstw? Czy nie ma rozwiązania podobnego do „ograniczenia” lub „łącza do superlayera”? O Boże, kolejny dzień poza ramami czasowymi. Rozmiar CGLayers został zmieniony, ale był zbyt opóźniony, CALayers są wystarczająco szybkie, ale nie zostały zmienione. Tyle niespodzianek. W każdym razie dzięki za odpowiedź.
Geri Borbás
3
CALayer na komputerze Mac ma do tego menedżerów układu, ale nie są one dostępne na iPhonie. Więc tak, musisz sam obliczyć rozmiary ramy. Inną opcją mogłoby być użycie widoków podrzędnych zamiast podwarstw. Następnie możesz odpowiednio ustawić automatyczne zmiany rozmiaru masek podglądów podrzędnych.
Ole Begemann
2
Tak, UIViews to zbyt drogie pod względem wydajności klasy, dlatego używam CALayers.
Geri Borbás
Wypróbowałem sposób, w jaki zasugerowałeś. To działa, a tak nie jest. Zmieniam rozmiar animowanego widoku, ale podwarstwy zmieniają rozmiar w innej animacji. Cholera, co teraz zrobić? Jaka jest metoda nadpisania w podklasie CALayer tego, co wywołuje KAŻDĄ (!) Klatkę animacji widoku? Czy jest jakiś?
Geri Borbás
Właśnie na to wpadłem. Wydaje się, że najlepszą opcją jest podklasa CALayer, jak powiedział Ole, ale wydaje się to niepotrzebnie uciążliwe.
Dimitri
15

Użyłem tego w UIView.

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

super.layoutSublayersOfLayer(layer)
}

Pracuje dla mnie.

Runo Sahara
źródło
Tak, to działało dla mnie w iOS 8. Przypuszczalnie działałoby we wcześniejszych wersjach. Miałem gradientową warstwę tła i musiałem zmienić rozmiar warstwy, gdy urządzenie jest obracane.
EPage_Ed
U mnie zadziałało, ale wymagało połączenia z super
entropią
To najlepsza odpowiedź! Tak, próbowałem layoutSubviews, ale psuje to tylko układ mojego UITextField, jak znikają leftView i rightView, a więcej tekstu w UITextField znika. Może zamiast dziedziczenia UIView użyłem rozszerzenia, ale było to bardzo błędne. TO DZIAŁA świetnie! THX
Michał Ziobro
Na warstwie z niestandardowego rysunku ( CALayerDelegate„s draw(_:in:)), miałem też zadzwonić _anySubLayer.setNeedsDisplay(). Wtedy to zadziałało dla mnie.
jedwidz
9

Miałem ten sam problem. W warstwie widoku niestandardowego dodałem jeszcze dwie podwarstwy. Aby zmienić rozmiar podwarstw (za każdym razem, gdy zmieniają się granice widoku niestandardowego), zaimplementowałem metodę layoutSubviewsmojego widoku niestandardowego; w ramach tej metody po prostu aktualizuję ramkę każdej podwarstwy, aby pasowała do bieżących granic warstwy mojego widoku podrzędnego.

Coś takiego:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}
Solin
źródło
16
Do Twojej wiadomości, nie potrzebujesz if (sublayer1! = Nil), ponieważ ObjC definiuje wiadomości jako nil (w tym przypadku -setFrame :) jako no-op zwracające 0.
Andrew Pouliot
7

2017

Dosłowna odpowiedź na to pytanie:

„CALayers nie zmieniał rozmiaru po zmianie granic jego UIView. Dlaczego?”

czy to na dobre czy na złe

needsDisplayOnBoundsChange

domyślnie false CALayer.

rozwiązanie,

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

Rzeczywiście, kieruję cię do tej kontroli jakości

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

co wyjaśnia, co do cholery contentsScalerobi to ustawienie krytyczne ; zwykle trzeba to ustawić tak samo, gdy ustawiasz needDisplayOnBoundsChange.

Fattie
źródło
5

Wersja Swift 3

W komórce niestandardowej Dodaj następujące wiersze

Najpierw zadeklaruj

let gradientLayer: CAGradientLayer = CAGradientLayer()

Następnie dodaj następujące wiersze

override func layoutSubviews() {
    gradientLayer.frame = self.YourCustomView.bounds
}
Vinay Krishna Gupta
źródło
0

Jak napisał [Ole], CALayer nie obsługuje automatycznego zmiany rozmiaru w systemie iOS. Dlatego powinieneś dostosować układ ręcznie. Moją opcją było dostosowanie ramki warstwy w ramach (iOS 7 i wcześniejsze)

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

lub (od iOS 8)

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinato
DevGansta
źródło
0

W widoku niestandardowym musisz zadeklarować zmienną dla swojej warstwy niestandardowej, nie deklaruj zmiennej w inicjalizacji zakresu. I po prostu zainicjuj to raz, nie próbuj ustawiać wartości null i reinitować

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

        override func layoutSubviews () {
            super.layoutSubviews ()
    // straż niech _fillColor = self._fillColor else {return}
            initializeLayout ()
        }
        private func initializeLayout () { 
            customLayer.removeFromSuperView ()
            customLayer.frame = layer.bounds
                   
            layer.insertSubview (at: 0)
        }
    }
Lê Tấn Thành
źródło