topLayoutGuide в ребенка-представление-контроллер


у меня есть UIPageViewController с прозрачный строка состояния и панель навигации. Его topLayoutGuide на 64 пикселей, как и ожидалось.

однако дочерние контроллеры представления UIPageViewController отчет topLayoutGuide 0 пикселей, даже если они отображаются в строке состояния и панели навигации.

это ожидаемое поведение? Если да, то каков наилучший способ расположить представление контроллера дочернего представления под реальным topLayoutGuide?

(за исключением использования parentViewController.topLayoutGuide, который я бы рассмотрел hack)

11 69

11 ответов:

пока ответ может быть правильно, я все еще обнаружил, что мне нужно путешествовать по дереву сдерживания, чтобы найти правильный контроллер родительского вида и получить то, что вы описываете как "реальный topLayoutGuide". Таким образом, я могу вручную реализовать automaticallyAdjustsScrollViewInsets.

вот как я это делаю:

в моем контроллере табличного представления (подкласс UIViewController на самом деле), у меня есть это:

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

обратите внимание на методы категории в UIViewController, вот как я реализовал они:

@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

надеюсь, что это помогает :)

вы можете добавить ограничение в раскадровке и изменить его в viewWillLayoutSubviews

что-то вроде этого:

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

Я могу ошибаться, но на мой взгляд поведение является правильным. Значение topLayout может использоваться контроллером вида контейнера для компоновки его подвидов.

ссылка говорит:

чтобы использовать верхнюю направляющую компоновки без использования ограничений, получите положение направляющей относительно верхней границы содержащего вида.

в Родительском представлении, относительно содержащего представления, значение будет 64.

в дочернее, относительно содержащего представления (родителя), значение будет равно 0.

в контроллере представления контейнера вы можете использовать свойство следующим образом:

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

контроллер дочернего вида не должен знать, что есть навигация и строка состояния : его родитель уже выложил свои подвиды с учетом этого.

если я создаю новый проект на основе страницы, вставьте его в навигационный контроллер и добавьте этот код в родительское представление контроллеры вроде бы работают нормально:

enter image description here

в документации говорится об использовании topLayoutGuide в viewDidLayoutSubviews, если вы используете подкласс UIViewController, или layoutSubviews, если вы используете подкласс UIView.

Если вы используете его в тех методах, вы должны получить соответствующее ненулевое значение.

документация ссылка на сайт: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide

в случае если у вас есть UIPageViewController как OP делает, и у вас есть, например, контроллеры представления коллекции в качестве детей. Оказывается, исправление для вставки контента просто и работает на iOS 8:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

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

Это было устранено в iOS 8.

Как установить положение topLayoutGuide для контроллера детского вида

по существу, контроллер представления контейнера должен ограничивать (top|bottom|left|right)LayoutGuide как и любой другой вид. (В iOS 7 он уже был полностью ограничен с требуемым приоритетом, поэтому это не сработало.)

Я думаю, что направляющие определенно предназначены для установки для вложенных дочерних контроллеров. Например, предположим, что у вас есть:

  • экран 100x50, с 20-пиксельной строкой состояния в верхней части.
  • контроллер вида верхнего уровня, охватывающий все окно. Его topLayoutGuide составляет 20.
  • вложенный контроллер вида внутри верхнего вида, охватывающий нижние 95 пикселей, например. 5 пикселей вниз от верхней части экрана. Этот вид должен иметь topLayoutGuide 15, так как его верхние 15 пикселей покрываются строкой состояния.

Это имеет смысл: это означает, что контроллер вложенного представления может устанавливать ограничения для предотвращения нежелательного перекрытия, как и на верхнем уровне. Ему не нужно заботиться о том, что он вложен или где на экране его родитель отображает его, и контроллеру родительского представления не нужно знать, как ребенок хочет взаимодействовать со строкой состояния.

Это также похоже на то, что документация-или некоторые из документации, по крайней мере ... говорит:

верхнее руководство по компоновке указывает расстояние в точках между верхней частью представления контроллера вида и нижней частью самой нижней панели, которая накладывается на вид

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

Это ничего не говорит о работе только для просмотра верхнего уровня контроллеры.

но, я не знаю, если это то, что происходит на самом деле. Я определенно видел контроллеры детского вида с ненулевыми topLayoutGuides, но я все еще выясняю причуды. (В моем случае верхний гид должны быть нулевым, так как вид не находится в верхней части экрана, который является то, что я бьюсь головой о в данный момент...)

Это подход для известной длины направляющей. Создайте ограничения не для направляющих, а для верхней части вида с фиксированными константами, предполагая, что расстояние между направляющими будет.

быстрая реализация @NachoSoto ответ:

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

Не уверен, что у кого-то все еще есть проблемы с этим, как я все еще делал несколько минут назад.
Моя проблема такой (Источник gif от https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html).
для краткости, мой pageViewController имеет 3 дочерних viewcontrollers. Первый viewcontroller в порядке, но когда я перехожу к следующему, весь вид неправильно смещен вверх (~20 пикселей, я думаю), но вернется к нормальному после того, как мой палец от экрана.
Я не спал всю ночь, искал решение для этого, но до сих пор не повезло найти. И вдруг мне пришла в голову такая безумная мысль:

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

}];

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

}];

мой listViewControllers имеет 3 дочерних viewcontrollers. Тот, у которого индекс 0, имеет проблему, поэтому я сначала установил его как корень pageviewcontroller, а сразу после этого вернул его к первому контроллеру представления (как я и ожидал). Вуаля, сработало!
надеюсь, что это помогает!

это неудачное поведение, которое, похоже, было исправлено в iOS 11 с помощью обновления API безопасной зоны. Тем не менее, вы всегда будете получать правильное значение от контроллера корневого представления. Например, если вы хотите, чтобы верхняя безопасная высота области pre-iOS 11:

Swift 4

let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length