topLayoutGuide im untergeordneten Ansichts-Controller


70

Ich habe eine UIPageViewControllermit durchscheinende Statusleiste und Navigationsleiste. Es topLayoutGuideist wie erwartet 64 Pixel.

Die untergeordneten Ansichtssteuerungen des UIPageViewControllerBerichts haben jedoch eine topLayoutGuideGröße von 0 Pixel, selbst wenn sie in der Statusleiste und in der Navigationsleiste angezeigt werden.

Ist das das erwartete Verhalten? Wenn ja, wie lässt sich eine Ansicht eines untergeordneten Ansichtscontrollers am besten unter der realen Position positionieren topLayoutGuide?

(kurz vor der Verwendung parentViewController.topLayoutGuide, was ich als Hack betrachten würde)


3
Ich frage mich, ob sie nicht topLayoutGuidegründlich genug für verschachtelte ViewController-Containments implementiert wurden. Wirft die Frage auf, wie man damit umgehen könnte, wenn ein benutzerdefinierter Container implementiert wird ...
Mike Pollard

1
Sind Sie sicher, dass Sie die Eigenschaft topLayoutGuide entweder in viewWillLayoutSubviews oder viewDidLayoutSubviews testen? Wenn ich mich richtig erinnere, wird nicht garantiert, dass in den viewWillAppear / viewDidAppear-Methoden ein korrekter Wert zurückgegeben wird ...
Carlos P

2
Ich teste es in viewWillLayoutSubviews, ja.
Hpique

Ja, ich habe auch dafür keine Lösung gefunden. Es gibt viele Fehler mit untergeordneten Ansichtssteuerungen und erweiterten Kanten. iOS7 ist einfach nicht bereit.
Leo Natan

3
Ich denke, da ist definitiv etwas Funky los. Ich habe einen UINavigationController mit einem UITabbarController im Inneren. Die ursprünglich ausgewählte Registerkarte erhält immer den richtigen Abstand, der unter der Navigationsleiste angezeigt wird. Wenn Sie jedoch zu den anderen Registerkarten wechseln, werden die oberen Zellen unter der oberen Leiste angezeigt.
Stephen Darlington

Antworten:


49

Obwohl diese Antwort möglicherweise richtig ist, musste ich den Containment-Baum nach oben verschieben, um den richtigen übergeordneten Ansichts-Controller zu finden und das zu erhalten, was Sie als "real topLayoutGuide" bezeichnen. Auf diese Weise kann ich manuell implementieren automaticallyAdjustsScrollViewInsets.

So mache ich es:

In meinem Table View Controller (eine Unterklasse von UIViewControllertatsächlich) habe ich Folgendes:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    _tableView.frame = self.view.bounds;

    const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
                                                                                               0.0,
                                                                                               self.ms_navigationBarBottomLayoutGuide.length,
                                                                                               0.0) : UIEdgeInsetsZero;
    _tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}

Beachten Sie die Kategoriemethoden in UIViewController, so habe ich sie implementiert:

@implementation UIViewController (MSLayoutSupport)

- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
    if (self.parentViewController &&
        ![self.parentViewController isKindOfClass:UINavigationController.class]) {
        return self.parentViewController.ms_navigationBarTopLayoutGuide;
    } else {
        return self.topLayoutGuide;
    }
}

- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
    if (self.parentViewController &&
        ![self.parentViewController isKindOfClass:UINavigationController.class]) {
        return self.parentViewController.ms_navigationBarBottomLayoutGuide;
    } else {
        return self.bottomLayoutGuide;
    }
}

@end

Hoffe das hilft :)


1
Ich hoffe, diese Antwort wird zu inifitum gewählt ... Ich habe 10 Stellen mit falschen, hartcodierten Werten in der gesamten App entfernt, die von faulen Entwicklern geschrieben wurde. Fehler behoben und keine Hardcode-Ness mehr.
Mazyod

Tolle Antwort, danke. Nur um die Dinge rein zu machen: UIEdgeInsets-Werte sind von CGFloat und nicht von doppelter Art. Ich würde sie besser als 0.f oder 0.0f anstelle von 0.0 setzen :).
Vive

Dies funktioniert in 99% der Fälle perfekt. Aber nicht, wenn Sie einen UINavigationController als childViewController haben.
Loadex

2
Vielen Dank. Ich musste ein hinzufügen [_tableView setContentOffset:CGPointMake(0, 0 - _tableView.contentInset.top) animated:NO];, damit es richtig nach oben scrollen konnte
Conor

Dies ersparte mir gerade große Kopfschmerzen :)
Tim Johnsen

11

Ich könnte mich irren, aber meiner Meinung nach ist das Verhalten korrekt. Der Wert topLayout kann vom Container View Controller verwendet werden, um die Unteransichten seiner Ansicht zu gestalten.

Die Referenz sagt:

Um eine Top-Layout-Anleitung ohne Verwendung von Einschränkungen zu verwenden, ermitteln Sie die Position der Anleitung relativ zur oberen Grenze der enthaltenen Ansicht .

Im übergeordneten Element beträgt der Wert relativ zur enthaltenen Ansicht 64.

Im untergeordneten Element ist der Wert relativ zur enthaltenen Ansicht (dem übergeordneten Element) 0.

Im Container View Controller können Sie die Eigenschaft folgendermaßen verwenden:

- (void) viewWillLayoutSubviews {

    CGRect viewBounds = self.view.bounds;
    CGFloat topBarOffset = self.topLayoutGuide.length;

    for (UIView *view in [self.view subviews]){
        view.frame = CGRectMake(viewBounds.origin.x, viewBounds.origin.y+topBarOffset, viewBounds.size.width, viewBounds.size.height-topBarOffset);
    }
}

Der untergeordnete Ansichtscontroller muss nicht wissen, dass es eine Navigations- und eine Statusleiste gibt: Das übergeordnete Element hat seine Unteransichten unter Berücksichtigung dieser bereits angelegt.

Wenn ich ein neues seitenbasiertes Projekt erstelle, es in einen Navigationscontroller einbetten und diesen Code zu den übergeordneten Ansichtscontrollern hinzufügen, scheint es einwandfrei zu funktionieren:

Geben Sie hier die Bildbeschreibung ein


19
Wenn es sich bei den untergeordneten Ansichtssteuerungen jedoch um UIScrollViewUnterklassen handelt, wird der Inhalt nicht unter der durchscheinenden Navigationsleiste angezeigt, wenn Sie den Rahmen so einstellen. Dies ist der ganze Zweck einer durchscheinenden Navigationsleiste .
kmikael

Sie nennen nicht super, das ist sehr ordentlich LOL
Pronebird

Diese Lösung funktionierte für mich im Fall des Seitencontrollers, wenn jemand einen Seitencontroller hat und der Ansicht für das Kind Einschränkungen hinzufügte
Majid Bashir

11

Sie können dem Storyboard eine Einschränkung hinzufügen und diese in viewWillLayoutSubviews ändern

etwas wie das:

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
}

1
Es ist nicht sinnvoll, wenn Sie Kanten unter der Navigationsleiste erweitern möchten.
Pronebird

Diese Antwort behebt die Probleme mit den wichtigsten Layout-Anleitungen in meinen untergeordneten Ansichtssteuerungen ohne Navigationsleiste. Swift 3 Version:override func viewWillLayoutSubviews() {self.topGuideConstraint.constant = (self.parent ?? self).topLayoutGuide.length}
LOP_Luke

8

In der Dokumentation wird angegeben, dass topLayoutGuide in viewDidLayoutSubviews verwendet werden soll, wenn Sie eine UIViewController-Unterklasse verwenden, oder layoutSubviews, wenn Sie eine UIView-Unterklasse verwenden.

Wenn Sie es in diesen Methoden verwenden, sollten Sie einen geeigneten Wert ungleich Null erhalten.

Dokumentationslink: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide


3

Für den Fall, dass Sie UIPageViewControllerwie OP haben und Sie zum Beispiel Sammlungsansichts-Controller als Kinder haben. Es stellt sich heraus, dass die Korrektur für das Einfügen von Inhalten einfach ist und unter iOS 8 funktioniert:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    UIEdgeInsets insets = self.collectionView.contentInset;
    insets.top = self.parentViewController.topLayoutGuide.length;
    self.collectionView.contentInset = insets;
    self.collectionView.scrollIndicatorInsets = insets;
}

2

Dies wurde in iOS 8 behoben.

So legen Sie die Position topLayoutGuide für den untergeordneten Ansichtscontroller fest

Im Wesentlichen sollte der Container-Ansichts-Controller den untergeordneten Ansichts-Controller (top|bottom|left|right)LayoutGuidewie jede andere Ansicht einschränken . (In iOS 7 war die erforderliche Priorität bereits vollständig eingeschränkt, sodass dies nicht funktionierte.)


Möchten Sie einen Code dafür freigeben? Ich bin dick im Schädel, wenn es um Constraint-basiertes Layout geht :)
Stian Høiland

Tatsächlich funktioniert dies auch in iOS 7, solange Sie zuerst die vorhandenen Einschränkungen im Layout-Handbuch entfernen, bevor Sie Ihre eigenen hinzufügen.
Archaeopterasa

@Archaeopterasa können Sie dies bitte näher erläutern? Auf welche bestehenden Einschränkungen im Layout-Handbuch beziehen Sie sich?
Petar

@ Jesse Rusak was genau wurde geändert? Wie können wir dieselbe Änderung auf Apps anwenden, auf denen iOS 7 ausgeführt wird, um dasselbe Verhalten zu erzielen? Ich habe eine spezielle Frage dazu gestellt: stackoverflow.com/questions/28024425/…
Petar

@ pe60t0 Die automatisch hinzugefügten Einschränkungen können entfernt werden (diejenigen, die in der Debug-Ausgabe als _UILayoutSupportConstraint anstelle von NSLayoutConstraint angezeigt werden). In meinem Code wollte ich die Layoutanleitung eines untergeordneten Ansichtscontrollers so einschränken, dass sie mit der des übergeordneten Ansichtscontrollers übereinstimmt. Daher habe ich zuerst die Einschränkungsliste durchgesehen und die Liste entfernt, die seine Höhe festgelegt hat, und konnte sie dann wie eine normale Ansicht einschränken. (Der Haken, da es im Autolayout immer einen gibt: Orientierungsänderungen sind möglicherweise chaotisch und erfordern eine sorgfältige Reihenfolge der Operationen.)
Archaeopterasa

1

Ich denke, die Anleitungen sind definitiv für verschachtelte untergeordnete Controller gedacht. Angenommen, Sie haben:

  • Ein 100x50-Bildschirm mit einer 20-Pixel-Statusleiste oben.
  • Ein Ansichtscontroller der obersten Ebene, der das gesamte Fenster abdeckt. Sein topLayoutGuide ist 20.
  • Ein Controller für verschachtelte Ansichten in der Draufsicht, der die unteren 95 Pixel abdeckt, z. 5 Pixel nach unten vom oberen Bildschirmrand. Diese Ansicht sollte einen topLayoutGuide von 15 haben, da die oberen 15 Pixel von der Statusleiste abgedeckt werden.

Das wäre sinnvoll: Dies bedeutet, dass der Controller für verschachtelte Ansichten Einschränkungen festlegen kann, um unerwünschte Überlappungen zu vermeiden, genau wie bei einem Controller der obersten Ebene. Es muss nicht wichtig sein, dass es verschachtelt ist oder wo es auf dem Bildschirm von seinem übergeordneten Element angezeigt wird, und der übergeordnete Ansichts-Controller muss nicht wissen, wie das untergeordnete Element mit der Statusleiste interagieren möchte.

Das scheint auch das zu sein, was die Dokumentation - oder zumindest ein Teil der Dokumentation - sagt:

Die obere Layoutanleitung gibt den Abstand in Punkten zwischen dem oberen Rand der Ansicht eines Ansichtscontrollers und dem unteren Rand der untersten Leiste an, die die Ansicht überlagert

( https://developer.apple.com/library/ios/documentation/UIKit/Reference/UILayoutSupport_Protocol/Reference/Reference.html )

Das sagt nichts darüber aus, nur für Top-Level-View-Controller zu arbeiten.

Aber ich weiß nicht, ob das tatsächlich passiert. Ich habe definitiv untergeordnete View-Controller mit topLayoutGuides ungleich Null gesehen, aber ich finde immer noch die Macken heraus. (In meinem Fall ist die obere Führung sollte Null sein, da die Ansicht nicht am oberen Rand des Bildschirms ist, das ist , was ich schlug meinen Kopf gegen zur Zeit ...)


0

Dies ist der Ansatz für die bekannte Führungslänge. Erstellen Sie Einschränkungen nicht für Hilfslinien, sondern für die Ansicht oben mit festen Konstanten unter der Annahme, dass die Führungsentfernung gleich ist.


0

Schnelle Implementierung von @NachoSoto Antwort:

extension UIViewController {

    func navigationBarTopLayoutGuide() -> UILayoutSupport {
        if let parentViewController = self.parentViewController {
            if !parentViewController.isKindOfClass(UINavigationController) {
                return parentViewController.navigationBarTopLayoutGuide()
            }
        }

        return self.topLayoutGuide
    }

    func navigationBarBottomLayoutGuide() -> UILayoutSupport {
        if let parentViewController = self.parentViewController {
            if !parentViewController.isKindOfClass(UINavigationController) {
                return parentViewController.navigationBarBottomLayoutGuide()
            }
        }

        return self.bottomLayoutGuide
    }
}

0

Ich bin mir nicht sicher, ob noch jemand ein Problem damit hat, wie ich es noch vor ein paar Minuten getan habe.
Mein Problem ist wie folgt (Quell-GIF von https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html ).
Kurz gesagt, mein pageViewController verfügt über 3 untergeordnete Viewcontroller. Der erste Ansichtscontroller ist in Ordnung, aber wenn ich zum nächsten schiebe, ist die gesamte Ansicht falsch nach oben versetzt (~ 20 Pixel, denke ich), wird aber wieder normal, nachdem mein Finger vom Bildschirm entfernt ist.
Ich blieb die ganze Nacht wach und suchte nach einer Lösung dafür, hatte aber immer noch kein Glück, eine zu finden. Dann kam mir plötzlich diese verrückte Idee:

[pageViewController setViewControllers:@[listViewControllers[1]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {

}];

[pageViewController setViewControllers:@[listViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {

}];

Meine listViewController haben 3 untergeordnete Ansichtscontroller. Der am Index 0 hat ein Problem, daher habe ich ihn zuerst als Stammverzeichnis des Seitenaufrufcontrollers festgelegt und gleich danach (wie erwartet) wieder auf den ersten Ansichtscontroller zurückgesetzt. Voila, es hat funktioniert!
Ich hoffe es hilft!


0

Dies ist ein unglückliches Verhalten, das in iOS 11 mit der API-Überarbeitung für den sicheren Bereich behoben worden zu sein scheint. Das heißt, Sie erhalten immer den richtigen Wert vom Root-View-Controller. Wenn Sie beispielsweise die obere Höhe des sicheren Bereichs vor iOS 11 möchten:

Swift 4

let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.