Wie kann ich die aktuelle Breite und Höhe einer Ansicht ermitteln, wenn ich Autolayout-Einschränkungen verwende?

108

Ich spreche nicht über die Frame-Eigenschaft, da Sie daraus nur die Größe der Ansicht in der xib erhalten können. Ich spreche davon, wann die Größe der Ansicht aufgrund ihrer Einschränkungen geändert wird (möglicherweise nach einer Drehung oder als Reaktion auf ein Ereignis). Gibt es eine Möglichkeit, die aktuelle Breite und Höhe zu ermitteln?

Ich habe versucht, die Einschränkungen zu durchlaufen, um nach Einschränkungen für Breite und Höhe zu suchen, aber das ist nicht sehr sauber und schlägt fehl, wenn es intrinsische Einschränkungen gibt (da ich nicht zwischen den beiden unterscheiden kann). Dies funktioniert auch nur, wenn sie tatsächlich Einschränkungen für Breite und Höhe haben, was sie nicht tun, wenn sie sich beim Ändern der Größe auf andere Einschränkungen verlassen.

Warum ist das so schwierig für mich? ARG!

yuf
quelle
Ich hätte gedacht, dass die Grenzen der Ansicht zu einem bestimmten Zeitpunkt ihre aktuelle Breite und Höhe behalten. Das wird während des Layoutprozesses angepasst.
Phillip Mills
Hmm, ich hätte nie gedacht, Grenzen zu überprüfen. Ich werde das jetzt tun.
Yuf

Antworten:

246

Die Antwort lautet [view layoutIfNeeded].

Hier ist der Grund:

Sie erhalten weiterhin die aktuelle Breite und Höhe der Ansicht, indem Sie view.bounds.size.widthund view.bounds.size.height(oder den Rahmen, der gleichwertig ist, sofern Sie nicht mit dem spielen view.transform) überprüfen .

Wenn Sie die Breite und Höhe wünschen, die durch Ihre vorhandenen Einschränkungen impliziert werden, besteht die Antwort nicht darin, die Einschränkungen manuell zu überprüfen, da Sie dazu die gesamte Logik zur Lösung von Einschränkungen des automatischen Layoutsystems erneut implementieren müssten, um diese zu interpretieren Einschränkungen. Stattdessen sollten Sie nur das automatische Layout bitten, dieses Layout zu aktualisieren , damit es die Einschränkungen löst und den Wert von view.bounds mit der richtigen Lösung aktualisiert. Anschließend überprüfen Sie die view.bounds.

Wie können Sie das automatische Layout bitten, das Layout zu aktualisieren? Rufen [view setNeedsLayout]Sie an, wenn das automatische Layout das Layout in der nächsten Runde der Run-Schleife aktualisieren soll.

Allerdings , wenn Sie wollen , dass es das Layout sofort aktualisieren, so können Sie sofort die neuen Grenzen Zugriff Wert später im aktuellen Funktion oder an einem anderen Punkt vor der Wende der Laufschleife, dann müssen Sie anrufen [view setNeedsLayout]und [view layoutIfNeeded].

Sie haben eine zweite Frage gestellt: "Wie kann ich eine Höhen- / Breitenbeschränkung ändern, wenn ich keinen direkten Verweis darauf habe?".

Wenn Sie die Einschränkung in IB erstellen, besteht die beste Lösung darin, ein IBOutlet in Ihrem Ansichtscontroller oder Ihrer Ansicht zu erstellen, damit Sie einen direkten Verweis darauf haben. Wenn Sie die Einschränkung im Code erstellt haben, sollten Sie zum Zeitpunkt der Erstellung eine Referenz in einer internen schwachen Eigenschaft beibehalten. Wenn jemand anderes die Einschränkung erstellt hat, müssen Sie sie finden, indem Sie die Eigenschaft view.constraints in der Ansicht und möglicherweise die gesamte Ansichtshierarchie untersuchen und eine Logik implementieren, die die entscheidende NSLayoutConstraint findet. Dies ist wahrscheinlich der falsche Weg, da Sie auch effektiv bestimmen müssen, welche bestimmte Einschränkung die Grenzgröße bestimmt, wenn nicht garantiert wird, dass eine einfache Antwort auf diese Frage vorliegt. Der endgültige Grenzwert könnte die Lösung für ein sehr kompliziertes System mit mehreren Einschränkungen sein.

Algen
quelle
16
Eine ausgezeichnete Antwort und auch gut erklärt. Rettete mich nach ein oder zwei Stunden, in denen ich mir die Haare ausgezogen hatte.
Ben Kreeger
2
layoutIfNeededist großartig und macht den Rahmen sofort verfügbar. Es wird jedoch auch das Rendern der Einschränkungen für alle Ansichten in den Unterbäumen der Ansicht erzwingen, für die es aufgerufen wird. Wenn Sie beispielsweise Ansichten programmgesteuert hinzufügen und layoutIfNeededalle in Ihrer rekursiven Routine aufrufen , wird Ihre Ansichtshierarchie möglicherweise sehr langsam gerendert. (Ich habe das auf die harte Tour gelernt.) Wie in der hervorragenden Antwort erwähnt, ist 'setNeedsLayout' effizienter und stellt den Frame beim nächsten Layoutdurchlauf zur Verfügung, was idealerweise in etwa 1/60 Sekunde geschieht.
Shmim
1
Ich weiß, das ist alt, aber wie nennt man diese Methode? In initWithCoder?
MayNotBe
4
Warum das Layout erzwingen, um die Grenzen zu erreichen? Dies scheint ineffizient zu sein und mit einer komplexen Schnittstelle wird dies möglicherweise teuer. Greifen Sie stattdessen über viewDidLayoutSubviews oder sogar viewWillLayoutSubviews auf die neuesten Grenzen zu und lassen Sie Cocoa sein eigenes Layout-Timing verwalten. Legen Sie eine Eigenschaft fest, wenn Sie auf den Wert oder das Flag zugreifen müssen, um mehrere Aufrufe zu vermeiden, wenn dies ein Problem darstellt.
smileBot
1
Ja, Sie müssen das Layout nur erzwingen, wenn Sie vor dem Einschalten der Ausführungsschleife Zugriff auf den automatisch vom Layout berechneten Wert benötigen - beispielsweise im Rahmen des aktuellen Funktionsaufrufs.
Algen
8

Ich hatte ein ähnliches Problem, bei dem ich einen oberen und unteren Rand zu einem UITableViewhinzufügen musste, dessen Größe basierend auf den in der Einrichtung festgelegten Einschränkungen geändert wurde UIStoryboard. Ich konnte mit auf die aktualisierten Einschränkungen zugreifen - (void)viewDidLayoutSubviews. Dies ist nützlich, damit Sie eine Ansicht nicht unterordnen und ihre Layoutmethode überschreiben müssen.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Ohne die Methode von der Methode aus aufzurufen viewDidLayoutSubview, wird nur der obere Rand korrekt gezeichnet, da sich der untere Rand irgendwo außerhalb des Bildschirms befindet.

Brandon Read
quelle
3
viewDidLayoutSubviews werden einige Male aufgerufen, und Sie erstellen Tonnen von Ebenen ... Sie müssen einige Existenzprüfungen hinzufügen, bevor Sie eine weitere Ebene hinzufügen .
Kalzem
Kommentar einige Jahre zu spät ... Sie sollten diese Methode auch in der Oberklasse aufrufen, wenn Sie vor allem anderen überschreiben:[super viewDidLayoutSubviews];
Alejandro Iván
5

Für diejenigen, die möglicherweise noch mit solchen Problemen konfrontiert sind, insbesondere mit TableviewCell.

Überschreiben Sie einfach die Methode:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

Bei UITableViewCell oder UICollectionViewCell erstellen Sie eine Unterklasse der Zelle und überschreiben dieselbe Methode:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
quelle
Nur so kann ich die tatsächliche endgültige Größe meiner Ansicht ermitteln
Benoit Jadinon,
Dies war die einzige Möglichkeit, die endgültige Größe für eine meiner eingeschränkten Ansichten zu ermitteln, da ich zuvor versucht hatte, sie auf awakeFromNib abzurufen, was den Unteransichten keine Zeit gab, die Größe zuerst zu ändern. Danke dir!
Azin Mehrnoosh
3

Verwenden -(void)viewWillAppear:(BOOL)animatedund anrufen [self.view layoutIfNeeded];- Es funktioniert, ich habe versucht.

Denn wenn Sie es verwenden -(void)viewDidLayoutSubviews, wird es definitiv funktionieren, aber diese Methode wird jedes Mal aufgerufen, wenn Ihre Benutzeroberfläche Aktualisierungen / Änderungen erfordert. Welches wird schwer zu verwalten sein. Softkey ist, dass Sie eine Bool-Variable verwenden, um eine solche Aufrufschleife zu vermeiden. bessere Verwendung viewWillAppear. Remember viewWillAppearwird auch aufgerufen, wenn die Ansicht wieder geladen wird (ohne Neuzuweisung).


quelle
2

Der Rahmen ist noch gültig. Am Ende verwendet die Ansicht ihre Rahmeneigenschaft, um sich selbst zu gestalten. Dieser Frame wird basierend auf allen Einschränkungen berechnet. Die Einschränkungen werden nur für das anfängliche Layout verwendet (und jedes Mal, wenn layoutSubviews in einer Ansicht wie nach einer Drehung aufgerufen wird). Danach befinden sich die Positionsinformationen in der Frame-Eigenschaft. Oder siehst du etwas anderes?

borrrden
quelle
1
Ich sehe anders. Die Rahmenwerte ändern sich auch nach direkter Änderung der Breiten- / Höhenbeschränkungen nicht (durch Ändern ihrer Konstanten. Ich habe ein IBOutlet in der xib)
yuf
Mein Problem ist eindeutig, da ich die Einschränkung ändere und dann den Frame in derselben Funktion verwenden muss. Durch Aufrufen von setNeedsLayout wird es nicht sofort aktualisiert. Ich denke, ich muss zuerst auf das Layout warten und dann den Rahmen verwenden. Meine zweite Frage lautet: Wie kann ich eine Höhen- / Breitenbeschränkung ändern, wenn ich keinen direkten Verweis darauf habe?
Yuf
1
Sie sollten dann dispatch_async verwenden, damit Sie Ihren Code bis zum nächsten Frame verzögern können. Was den zweiten Teil betrifft ... ich weiß nicht ... müssten Sie alle Einschränkungen durchlaufen und nach der zutreffenden suchen ... IBOutlets sind viel einfacher.
Borrrden
Richtig für IBOutlets, aber ich möchte eine Funktion in einer Kategorie für eine UIView schreiben, mit der ich keine IBs zum Arbeiten habe.
Yuf