Gibt es ein UIView-Größenänderungsereignis?

102

Ich habe eine Ansicht, die Zeilen und Spalten von Bildansichten enthält.

Wenn die Größe dieser Ansicht geändert wird, muss ich die Positionen der Bildansichten neu anordnen.

Diese Ansicht ist eine Unteransicht einer anderen Ansicht, deren Größe geändert wird.

Gibt es eine Möglichkeit zu erkennen, wann die Größe dieser Ansicht geändert wird?

Live-Liebe
quelle
Wann wird die Größe der Ansicht geändert? Wann dreht sich das Gerät oder wann dreht der Benutzer es mit Multi-Touch?
Evan Mulawski
Die Größe dieser Ansicht wird geändert, wenn der Benutzer auf eine Schaltfläche in einer anderen Ansicht (Hauptansicht) tippt.
Live-Liebe

Antworten:

98

Wie Uli weiter unten kommentiert hat, besteht die richtige Vorgehensweise darin, layoutSubviewsdie imageViews dort zu überschreiben und zu gestalten.

Wenn Sie aus irgendeinem Grund nicht unterklassifizieren und überschreiben können layoutSubviews, boundssollte das Beobachten funktionieren, auch wenn es etwas schmutzig ist. Schlimmer noch, es besteht ein Risiko beim Beobachten - Apple garantiert nicht, dass KVO in UIKit-Klassen funktioniert. Lesen Sie hier die Diskussion mit dem Apple-Ingenieur: Wann wird ein zugeordnetes Objekt freigegeben?

ursprüngliche Antwort:

Sie können die Schlüsselwertbeobachtung verwenden:

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

und implementieren:

- (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:
    }
}
Michal
quelle
2
Das Layout von Unteransichten ist genau das, wofür layoutSubviews gedacht ist. Das Beobachten von Größenänderungen ist zwar funktionell, aber IMO schlechtes Design.
uliwitness
@uliwitness gut, sie ändern dieses Zeug immer wieder. Wie auch immer, jetzt sollten Sie viewWillTransitionusw. usw. verwenden
Dan Rosenstark
viewWillTransition von für UIViewController, nicht für UIView.
Armand
Ist die Verwendung layoutSubviewsimmer noch eine empfohlene Methode, wenn abhängig von der aktuellen Größe der Ansicht unterschiedliche Unteransichten hinzugefügt / entfernt und unterschiedliche Einschränkungen hinzugefügt / entfernt werden müssen?
Chris Prince
1
Nun, ich glaube, ich habe das gerade für meinen Fall beantwortet. Wenn ich das Setup (abhängig von der Größe) durchführe, das ich beim Überschreiben von Grenzen benötige, funktioniert es. Wenn ich es in layoutSubviews mache, tut es das nicht.
Chris Prince
93

In einer UIViewUnterklasse können Eigenschaftsbeobachter verwendet werden:

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

Ohne Unterklassifizierung reicht die Schlüsselwertbeobachtung mit intelligenten Schlüsselpfaden aus :

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}
Rudolf Adamkovič
quelle
2
@Ali Warum denkst du so?
Rudolf Adamkovič
auf Lastrahmen ändert sich, aber es sieht so aus, als ob Grenzen nicht (ich weiß, dass es seltsam ist und vielleicht habe ich etwas falsch gemacht)
Ali
3
@Ali: wie hier schon einige Male erwähnt: frameist eine abgeleitete und zur Laufzeit berechnete Eigenschaft. Überschreiben Sie dies nicht, es sei denn, Sie haben einen sehr klugen und wissenden Grund dafür. sonst: benutze boundsoder (noch besser) layoutSubviews.
Auco
3
Eine großartige Möglichkeit, einen Kreis zu erstellen. Vielen Dank! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy
4
Das Erkennen boundsoder frameÄndern funktioniert nicht garantiert, je nachdem, wo Sie Ihre Ansicht in die Ansichtshierarchie einfügen. Ich würde layoutSubviewsstattdessen überschreiben . Sehen Sie diese und diese Antworten.
HuaTham
31

Erstellen Sie eine Unterklasse von UIView und überschreiben Sie layoutSubviews

Gwang
quelle
11
Das Problem dabei ist, dass Unteransichten nicht nur ihre Größe ändern können, sondern diese Größenänderung auch animieren können. Wenn UIView die Animation ausführt, werden nicht jedes Mal layoutSubviews aufgerufen.
David Jeske
1
@DavidJeske UIViewAnimationOptions verfügt über eine Option namens UIViewAnimationOptionLayoutSubviews. Ich denke das hilft vielleicht. :)
Neal.Marlin
8

Swift 4 Keypath KVO - Auf diese Weise erkenne ich die Autorotation und bewege mich zum iPad-Seitenbereich. Sollte funktionieren funktioniert jede Ansicht. Musste die Schicht des UIView beobachten.

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
quelle
Diese Lösung hat bei mir funktioniert. Ich bin neu bei Swift und bin mir nicht sicher, welche \ .bounds-Syntax Sie hier verwendet haben. Was bedeutet das genau?
WBuck
Hier ist mehr Diskussion: github.com/ole/whats-new-in-swift-4/blob/master/…
Warren Stringer
.layerhat den Trick gemacht! Wissen Sie, warum die Verwendung view.observenicht funktioniert?
d4Rk
@ d4Rk - Ich weiß es nicht. Ich vermute, dass Layer-Grenzen weniger mehrdeutig sind als UIView-Frames. Beispielsweise wirkt sich das contentOffset von UITableView auf die endgültigen Koordinaten seiner Unteransichten aus. Nicht sicher.
Warren Stringer
Dies ist eine großartige Antwort für mich. Mein Fall ist, dass ich AutoLayout verwende, um meine Ansichten darzustellen. ABER UICollectionViewFlowLayout zwingt Sie, eine explizite itemSize anzugeben. Wenn sich jedoch die Größe Ihrer collectionView ändert (wie in meinem Fall, wenn eine Symbolleiste mit AutoLayout angezeigt wird), bleibt die itemSize gleich und wirft = [Der Beobachter ist der sauberste Ansatz, den ich bisher erhalten habe. und der beste !!
Yitzchak
7

Sie können eine Unterklasse von UIView erstellen und die überschreiben

setFrame: (CGRect) Frame

Methode. Dies ist die Methode, die aufgerufen wird, wenn der Rahmen (dh die Größe) der Ansicht geändert wird. Mach so etwas:

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

  // Do your custom code here.
}
rekle
quelle
Sinnvoll und funktional. Guter Anruf.
El Zorko
1
Zu Ihrer Information Dies funktioniert nicht für die UIImageView-Unterklasse. Weitere Informationen hier: stackoverflow.com/questions/19216684/…
William Entriken
Außerdem setFrame:wurde meine UITextViewUnterklasse während der durch Autorotation verursachten Größenänderung nicht aufgerufen layoutSubviews:. Hinweis: Ich verwende das automatische Layout und iOS 7.0.
ma11hew28
3
niemals überschreiben setFrame:. frameist eine abgeleitete Eigenschaft. Siehe meine Antwort
Adlai Holler
7

Ziemlich alt, aber immer noch eine gute Frage. Im Beispielcode von Apple und in einigen privaten UIView-Unterklassen überschreiben sie setBounds ungefähr wie folgt:

-(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];
}

Überschreiben setFrame:ist keine gute Idee. frameabgeleitet von center, boundsund transform, so iOS rufen nicht unbedingt werden setFrame:.

Adlai Holler
quelle
2
Dies ist wahr (theoretisch). Wird setBounds:jedoch auch beim Festlegen der Frame-Eigenschaft nicht aufgerufen (zumindest unter iOS 7.1). Dies könnte eine Optimierung sein, die Apple hinzugefügt hat, um die zusätzliche Meldung zu vermeiden.
Nschum
1
… Und schlechtes Design auf Apples Seite, keine eigenen Accessoren anzurufen.
Osxdirk
1
In der Tat sind beide frame und boundsvon der zugrunde liegenden Ansicht abgeleitet CALayer; Sie rufen einfach den Getter der Schicht an. Und setFrame:setzt den Rahmen der Ebene, während setBounds:Sätze die Schichtgrenzen. Sie können also nicht einfach den einen oder anderen überschreiben. Wird layoutSubviewsauch übermäßig aufgerufen (nicht nur bei Geometrieänderungen), sodass dies möglicherweise auch nicht immer eine gute Option ist. Immer noch auf der Suche ...
big_m
-3

Wenn Sie sich in einer UIViewController-Instanz befinden, viewDidLayoutSubviewsreicht das Überschreiben aus .

override func viewDidLayoutSubviews() {
    // update subviews
}
Dan Rosenstark
quelle
UIView-Größe geändert erforderlich, nicht UIViewController.
Gastón Antonio Montes
@ GastónAntonioMontes danke dafür! Möglicherweise haben Sie eine Beziehung zwischen UIViewInstanzen und UIViewControllerInstanzen bemerkt . Wenn Sie also eine UIViewInstanz ohne VC haben, sind die anderen Antworten großartig. Wenn Sie jedoch zufällig an eine VC angehängt sind, ist dies Ihr Mann. Entschuldigung, es gilt nicht für Ihren Fall.
Dan Rosenstark