Czy jest wydarzenie zmiany rozmiaru UIView?

102

Mam widok, który zawiera wiersze i kolumny z obrazami.

Jeśli rozmiar tego widoku zostanie zmieniony, muszę zmienić rozmieszczenie pozycji widoków obrazu.

Ten widok jest widokiem podrzędnym innego widoku, którego rozmiar zostanie zmieniony.

Czy istnieje sposób na wykrycie, kiedy następuje zmiana rozmiaru tego widoku?

miłość na żywo
źródło
Kiedy następuje zmiana rozmiaru widoku? Kiedy urządzenie się obraca, czy też gdy użytkownik obraca je za pomocą funkcji multi-touch?
Evan Mulawski
Rozmiar tego widoku jest zmieniany, gdy użytkownik naciśnie przycisk w innym widoku (widok główny).
miłość na żywo

Odpowiedzi:

98

Jak skomentował Uli poniżej, właściwym sposobem na to jest nadpisanie layoutSubviewsi rozmieszczenie tam imageViews.

Jeśli z jakiegoś powodu nie możesz podklasować i przesłonić layoutSubviews, obserwacja boundspowinna działać, nawet gdy jesteś trochę brudny. Co gorsza, istnieje ryzyko z obserwacją - Apple nie gwarantuje, że KVO działa na klasach UIKit. Przeczytaj dyskusję z inżynierem Apple tutaj: Kiedy powiązany obiekt zostanie zwolniony?

oryginalna odpowiedź:

Możesz użyć obserwacji klucz-wartość:

[yourView addObserver:self forKeyPath:@"bounds" options:0 context:nil];

i wdrożyć:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == yourView && [keyPath isEqualToString:@"bounds"]) {
        // do your stuff, or better schedule to run later using performSelector:withObject:afterDuration:
    }
}
Michał
źródło
2
Naprawdę, układanie podglądów podrzędnych jest dokładnie tym, do czego służy layoutSubviews, więc obserwowanie zmian rozmiaru, choć funkcjonalne, jest złym projektem IMO.
uliwitness
@uliwitness dobrze, ciągle zmieniają te rzeczy. W każdym razie, teraz powinieneś użyć viewWillTransitionitp. Itd.
Dan Rosenstark
viewWillTransition of for UIViewControllers, a nie UIView.
Armand,
Czy layoutSubviewsnadal jest zalecana metoda, jeśli w zależności od aktualnego rozmiaru widoku trzeba było dodać / usunąć różne widoki podrzędne i dodać / usunąć różne ograniczenia?
Chris Prince
1
Cóż, myślę, że właśnie odpowiedziałem na to w mojej sprawie. Kiedy wykonuję konfigurację (w zależności od rozmiaru), której potrzebuję w nadpisaniu granic, działa. Kiedy robię to w layoutSubviews, tak się nie dzieje.
Chris Prince
93

W UIViewpodklasie można użyć obserwatorów właściwości :

override var bounds: CGRect {
    didSet {
        // ...
    }
}

Bez podklas, obserwacja klucz-wartość pomocą inteligentnych ścieżek klucza wystarczy:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}
Rudolf Adamkovič
źródło
2
@Ali Dlaczego tak myślisz?
Rudolf Adamkovič
przy zmianach ramki ładowania, ale wygląda na to, że granice nie (wiem, że to dziwne i może coś nie tak)
Ali
3
@Ali: jak wspomniano tutaj kilka razy: framejest właściwością pochodną i obliczaną w czasie wykonywania. nie zastępuj tego, chyba że masz bardzo mądry i świadomy powód, aby to zrobić. w przeciwnym razie użyj boundslub (nawet lepiej) layoutSubviews.
auco
3
Taki wspaniały sposób na stworzenie kręgu. Dzięki! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy,
4
Nie ma gwarancji, że wykrywanie boundslub framezmiana będą działać, w zależności od tego, gdzie umieścisz widok w hierarchii widoków. layoutSubviewsZamiast tego zastąpiłbym . Zobacz to i to odpowiedzi.
HuaTham
32

Utwórz podklasę UIView i nadpisz layoutSubviews

gwang
źródło
11
Problem polega na tym, że podglądy podrzędne mogą nie tylko zmienić swój rozmiar, ale mogą animować tę zmianę. Gdy UIView uruchamia animację, nie wywołuje za każdym razem layoutSubviews.
David Jeske
1
@DavidJeske UIViewAnimationOptions ma opcję o nazwie UIViewAnimationOptionLayoutSubviews. Myślę, że to może pomóc. :)
Neal.Marlin
8

Swift 4 keypath KVO - w ten sposób wykrywam automatyczne obracanie i przenoszenie do panelu bocznego iPada. Powinien działać dowolny widok. Musiałem obserwować warstwę UIView.

private var observer: NSKeyValueObservation?

override func viewDidLoad() {
    super.viewDidLoad()
    observer = view.layer.observe(\.bounds) { object, _ in
        print(object.bounds)
    }
    // ...
}
override func viewWillDisappear(_ animated: Bool) {
    observer?.invalidate()
    //...
}
Warren Stringer
źródło
To rozwiązanie zadziałało dla mnie. Jestem nowy w Swift i nie jestem pewien składni \ .bounds, której użyłeś tutaj. Co to dokładnie oznacza?
WBuck
tutaj jest więcej dyskusji: github.com/ole/whats-new-in-swift-4/blob/master/…
Warren Stringer
.layerzałatwił sprawę! Czy wiesz, dlaczego używanie view.observenie działa?
d4Rk
@ d4Rk - nie wiem. Domyślam się, że granice warstwy są mniej niejednoznaczne niż ramki UIView. Na przykład contentOffset UITableView wpłynie na ostateczne współrzędne jego subViews. Niepewny.
Warren Stringer
TO DLA MNIE WIELKA ODPOWIEDŹ. Mój przypadek jest taki, że używam Autoukładu do układania widoków. ALE UICollectionViewFlowLayout wymusza podanie jawnego itemSize. ale kiedy zmienia się rozmiar collectionView (jak w moim przypadku, gdy pokazuję pasek narzędzi z AutoLayout) - itemSize pozostaje taki sam i rzuca = [obserwator jest najczystszym podejściem, jakie do tej pory uzyskałem. i najlepszy !!
Yitzchak
7

Możesz utworzyć podklasę UIView i nadpisać

setFrame: (CGRect) ramka

metoda. Jest to metoda wywoływana w przypadku zmiany ramki (tj. Rozmiaru) widoku. Zrób coś takiego:

- (void) setFrame:(CGRect)frame
{
  // Call the parent class to move the view
  [super setFrame:frame];

  // Do your custom code here.
}
rozpal
źródło
Rozsądna i funkcjonalna. Dobra decyzja.
El Zorko
1
FYI To nie działa dla podklasy UIImageView. Więcej informacji tutaj: stackoverflow.com/questions/19216684/…
William Entriken
Ponadto setFrame:nie został wywołany w mojej UITextViewpodklasie podczas zmiany rozmiaru spowodowanej autorotacją, podczas gdy tak layoutSubviews:było. Uwaga: używam układu automatycznego i iOS 7.0.
ma11hew28
3
nigdy nie zastępuj setFrame:. framejest własnością pochodną. Zobacz moją odpowiedź
Adlai Holler
7

Dość stary, ale wciąż dobre pytanie. W przykładowym kodzie Apple oraz w niektórych ich prywatnych podklasach UIView zastępują one setBounds mniej więcej tak:

-(void)setBounds:(CGRect)newBounds {
    BOOL const isResize = !CGSizeEqualToSize(newBounds.size, self.bounds.size);
    if (isResize) [self prepareToResizeTo:newBounds.size]; // probably saves 
    [super setBounds:newBounds];
    if (isResize) [self recoverFromResizing];
}

Zastępowanie setFrame:NIE jest dobrym pomysłem. framepochodzi z center,bounds i transform, więc iOS niekoniecznie wywoła setFrame:.

Adlai Holler
źródło
2
To prawda (w teorii). Jednak setBounds:nie jest też wywoływana podczas ustawiania właściwości ramki (przynajmniej w iOS 7.1). Może to być optymalizacja, którą Apple dodał, aby uniknąć dodatkowego komunikatu.
nschum
1
… I zły projekt po stronie Apple, aby nie wywoływać własnych akcesoriów.
osxdirk
1
W rzeczywistości oba frame i boundswywodzą się z podstawy widoku CALayer; po prostu dzwonią do gettera warstwy. I setFrame:ustawia ramkę warstwy, jednocześnie setBounds:wyznaczając granice warstwy. Nie możesz więc po prostu zastąpić jednego lub drugiego. Jest również layoutSubviewswywoływany nadmiernie (nie tylko przy zmianach geometrii), więc nie zawsze może być dobrą opcją. Wciąż szukam ...
big_m
-3

Jeśli jesteś w wystąpieniu UIViewController, zastąpienie załatwia sprawę viewDidLayoutSubviews.

override func viewDidLayoutSubviews() {
    // update subviews
}
Dan Rosenstark
źródło
Wymagana zmiana rozmiaru UIView, a nie UIViewController.
Gastón Antonio Montes
@ GastónAntonioMontes dzięki za to! Być może zauważyłeś związek między UIViewinstancjami i UIViewControllerinstancjami. Więc jeśli masz UIViewinstancję bez dołączonego VC, inne odpowiedzi są świetne, ale jeśli zdarzy ci się być przywiązanym do VC, to jest twój człowiek. Przepraszamy, to nie dotyczy twojego przypadku.
Dan Rosenstark