TopLayoutGuide в контроллере детского просмотра

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

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

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

(не используя parentViewController.topLayoutGuide, что я считаю взломом)

Ответ 1

В то время как этот ответ может быть правильным, я все же обнаружил, что мне нужно перемещать дерево сдерживания, чтобы найти правильный родительский контроллер представления и получить то, что вы называете "real 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

Надеюсь, что это поможет:)

Ответ 2

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

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

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

Ответ 3

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

В ссылке говорится:

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

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

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

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

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

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

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

enter image description here

Ответ 4

В документации говорится, что использовать 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

Ответ 5

В случае, если у вас есть 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;
}

Ответ 6

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

Как установить верхнюю позицию topLayoutGuide для контроллера детского просмотра

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

Ответ 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, но я все еще разбираюсь в причудах. (В моем случае верхняя направляющая должна быть нулевой, так как представление не находится в верхней части экрана, и это то, с чем я стучу головой в данный момент...)

Ответ 8

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

Ответ 9

Быстрая реализация ответа @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
    }
}

Ответ 10

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

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

}];

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

}];

В моем спискеViewControllers есть 3 дочерних контроллера. У объекта с индексом 0 есть проблема, поэтому я сначала установил его как root из pageviewcontroller, и сразу после этого вернул его в первый контроллер представления (как я и ожидал). Вуала, это сработало!
Надеюсь, это поможет!