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

Краткая версия:

У меня возникла проблема с руководством по компоновке верхнего макета при использовании в сочетании с пользовательским переходом и UINavigationController в iOS7. В частности, ограничение между верхним макетом и текстом не выполняется. Кто-нибудь столкнулся с этой проблемой?


Длинная версия:

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

right

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

wrong

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

Сказав, что _autolayoutTrace сообщает, что объекты UILayoutGuide страдают от AMBIGUOUS LAYOUT, когда я запускаю:

(lldb) po [[UIWindow keyWindow] _autolayoutTrace]

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

В терминах того, насколько точно я делаю переход, я указал объект, который соответствует UIViewControllerAnimatedTransitioning в методе animationControllerForOperation:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController*)fromVC
                                                 toViewController:(UIViewController*)toVC
{
    if (operation == UINavigationControllerOperationPush)
        return [[PushAnimator alloc] init];

    return nil;
}

и

@implementation PushAnimator

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
    return 0.5;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* toViewController   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    [[transitionContext containerView] addSubview:toViewController.view];
    CGFloat width = fromViewController.view.frame.size.width;

    toViewController.view.transform = CGAffineTransformMakeTranslation(width, 0);

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromViewController.view.transform = CGAffineTransformMakeTranslation(-width / 2.0, 0);
        toViewController.view.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        fromViewController.view.transform = CGAffineTransformIdentity;
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}

@end

Я также выполнил описанное выше, установив frame представления, а не transform, с тем же результатом.

Я также попытался вручную убедиться, что ограничения повторно применяются, вызвав layoutIfNeeded. Я также пробовал setNeedsUpdateConstraints, setNeedsLayout и т.д.

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

Ответ 1

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

Непосредственная проверка ограничений в игре показывает, что iOS добавляет ограничение высоты к topLayoutGuide со значением, равным высоте навигационной панели навигационного контроллера. Кроме того, в iOS 7 использование настраиваемого перехода анимации оставляет ограничение с высотой 0. Они исправили это в iOS 8.

Это решение, которое я придумал для исправления ограничения (это в Swift, но эквивалент должен работать в Obj-C). Я тестировал, что он работает на iOS 7 и 8.

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
    let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    destinationVC.view.frame = transitionContext.finalFrameForViewController(destinationVC)
    let container = transitionContext.containerView()
    container.addSubview(destinationVC.view)

    // Custom transitions break topLayoutGuide in iOS 7, fix its constraint
    if let navController = destinationVC.navigationController {
        for constraint in destinationVC.view.constraints() as [NSLayoutConstraint] {
            if constraint.firstItem === destinationVC.topLayoutGuide
                && constraint.firstAttribute == .Height
                && constraint.secondItem == nil
                && constraint.constant == 0 {
                constraint.constant = navController.navigationBar.frame.height
            }
        }
    }

    // Perform your transition animation here ...
}

Ответ 2

Удалось устранить проблему, добавив эту строку:

toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];

To:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView {

    // Add the toView to the container
    UIView* containerView = [transitionContext containerView];
    [containerView addSubview:toView];
    [containerView sendSubviewToBack:toView];


    // animate
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    [UIView animateWithDuration:duration animations:^{
        fromView.alpha = 0.0;
    } completion:^(BOOL finished) {
        if ([transitionContext transitionWasCancelled]) {
            fromView.alpha = 1.0;
        } else {
            // reset from- view to its original state
            [fromView removeFromSuperview];
            fromView.alpha = 1.0;
        }
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];

}

Из документации Apple для [finalFrameForViewController]: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForViewController:

Ответ 3

Я боролся с той же проблемой. Помещение этого в viewDidLoad моего toViewController действительно помогло мне:

self.edgesForExtendedLayout = UIRectEdgeNone;

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

Ответ 4

FYI, я в конечном итоге использовал вариант ответа Alex, программно изменяя константу ограничения высоты верхней вершины макета в методе animateTransition. Я просто размещаю это, чтобы поделиться версией Objective-C (и устранить тест constant == 0).

CGFloat navigationBarHeight = toViewController.navigationController.navigationBar.frame.size.height;

for (NSLayoutConstraint *constraint in toViewController.view.constraints) {
    if (constraint.firstItem == toViewController.topLayoutGuide
        && constraint.firstAttribute == NSLayoutAttributeHeight
        && constraint.secondItem == nil
        && constraint.constant < navigationBarHeight) {
        constraint.constant += navigationBarHeight;
    }
}

Спасибо, Алекс.

Ответ 5

Просто поставьте следующий код в viewDidLoad

self.extendedLayoutIncludesOpaqueBars = YES;

Ответ 6

Как упоминалось в @Rob, topLayoutGuide не является надежным при использовании пользовательских переходов в UINavigationController. Я работал над этим, используя свой собственный макет. Вы можете увидеть код в действии в демонстрационном проекте . Основные характеристики:

Категория для настраиваемых руководств по макетам:

@implementation UIViewController (hp_layoutGuideFix)

- (BOOL)hp_usesTopLayoutGuideInConstraints
{
    return NO;
}

- (id<UILayoutSupport>)hp_topLayoutGuide
{
    id<UILayoutSupport> object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide));
    return object ? : self.topLayoutGuide;
}

- (void)setHp_topLayoutGuide:(id<UILayoutSupport>)hp_topLayoutGuide
{
    HPLayoutSupport *object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide));
    if (object != nil && self.hp_usesTopLayoutGuideInConstraints)
    {
        [object removeFromSuperview];
    }
    HPLayoutSupport *layoutGuide = [[HPLayoutSupport alloc] initWithLength:hp_topLayoutGuide.length];
    if (self.hp_usesTopLayoutGuideInConstraints)
    {
        [self.view addSubview:layoutGuide];
    }
    objc_setAssociatedObject(self, @selector(hp_topLayoutGuide), layoutGuide, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

HPLayoutSupport - это класс, который будет выступать в качестве руководства по компоновке. Он должен быть подклассом UIView, чтобы избежать сбоев (интересно, почему это не является частью интерфейса UILayoutSupport).

@implementation HPLayoutSupport {
    CGFloat _length;
}

- (id)initWithLength:(CGFloat)length
{
    self = [super init];
    if (self)
    {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        self.userInteractionEnabled = NO;
        _length = length;
    }
    return self;
}

- (CGSize)intrinsicContentSize
{
    return CGSizeMake(1, _length);
}

- (CGFloat)length
{
    return _length;
}

@end

UINavigationControllerDelegate является ответственным за "исправление" руководства по компоновке перед переходом:

- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC
{
    toVC.hp_topLayoutGuide = fromVC.hp_topLayoutGuide;
    id <UIViewControllerAnimatedTransitioning> animator;
    // Initialise animator
    return animator;
}

Наконец, UIViewController использует hp_topLayoutGuide вместо topLayoutGuide в ограничениях и указывает это, переопределяя hp_usesTopLayoutGuideInConstraints:

- (void)updateViewConstraints
{
    [super updateViewConstraints];
    id<UILayoutSupport> topLayoutGuide = self.hp_topLayoutGuide;
    // Example constraint
    NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _dateLabel, topLayoutGuide);
    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLayoutGuide][_imageView(240)]-8-[_dateLabel]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views];
    [self.view addConstraints:constraints];
}

- (BOOL)hp_usesTopLayoutGuideInConstraints
{
    return YES;
}

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

Ответ 7

я нашел способ. Сначала снимите флажок "Extend Edges" свойства контроллера. после этого навигационная панель становится темным цветом. Добавьте представление в контроллер и установите верхний и нижний LayoutConstraint -100. Затем создайте свойство viewviewviewview no (для транскулентного эффекта navigaionbar). Мой английский плохой сори для этого.:)

Ответ 8

У меня была та же проблема, в результате я создал собственное руководство по topLayout и сделал для него ограничения, а не topLayoutGuide. Не идеально. Публикуйте его здесь только в том случае, если кто-то застрял и ищет быстрое хакерское решение http://www.github.com/stringcode86/SCTopLayoutGuide

Ответ 9

попробуйте:

self.edgesforextendedlayout=UIRectEdgeNone

Или просто настройте навигационную панель непрозрачной и установите фоновое изображение или backgroundcolor на панель навигации

Ответ 10

Здесь простое решение, которое я использую, отлично работает для меня: во время фазы настройки - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext вручную установите "from" и "to" viewController.view.frame.origin.y = navigationController.navigationBar.frame.size.height. Это сделает ваши представления макета автоматически позиционируются вертикально, как вы ожидаете.

Минус псевдокода (например, у вас, вероятно, есть собственный способ определения, работает ли устройство iOS7), вот как выглядит мой метод:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *container = [transitionContext containerView];

    CGAffineTransform destinationTransform;
    UIViewController *targetVC;
    CGFloat adjustmentForIOS7AutoLayoutBug = 0.0f;

    // We're doing a view controller POP
    if(self.isViewControllerPop)
    {
        targetVC = fromViewController;

        [container insertSubview:toViewController.view belowSubview:fromViewController.view];

        // Only need this auto layout hack in iOS7; it fixed in iOS8
        if(_device_is_running_iOS7_)
        {
            adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
            [toViewController.view setFrameOriginY:adjustmentForIOS7AutoLayoutBug];
        }

        destinationTransform = CGAffineTransformMakeTranslation(fromViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
    }
    // We're doing a view controller PUSH
    else
    {
        targetVC = toViewController;

        [container addSubview:toViewController.view];

        // Only need this auto layout hack in iOS7; it fixed in iOS8
        if(_device_is_running_iOS7_)
        {
            adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
        }

        toViewController.view.transform = CGAffineTransformMakeTranslation(toViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
        destinationTransform = CGAffineTransformMakeTranslation(0.0f,adjustmentForIOS7AutoLayoutBug);
    }

    [UIView animateWithDuration:_animation_duration_
                          delay:_animation_delay_if_you_need_one_
                        options:([transitionContext isInteractive] ? UIViewAnimationOptionCurveLinear : UIViewAnimationOptionCurveEaseOut)
                     animations:^(void)
     {
         targetVC.view.transform = destinationTransform;
     }
                     completion:^(BOOL finished)
     {
         [transitionContext completeTransition:([transitionContext transitionWasCancelled] ? NO : YES)];
     }];
}

Несколько бонусов в этом примере:

  • Для того, чтобы контролировать контроллер, этот настраиваемый переход сместит нажатый toViewController.view поверх неподвижного fromViewController.view. Для pops, fromViewController.view скользит вправо и показывает неподвижный toViewController.view под ним. В целом, это всего лишь тонкий поворот на выходе контроллера iOS7 + контроллера.
  • Блок завершения [UIView animateWithDuration:...] показывает правильный способ обработки завершенных и отмененных пользовательских переходов. Этот крошечный лакомый кусочек был классическим моментом для головы; надеюсь, что это поможет кому-то другому.

Наконец, я хотел бы отметить, что, насколько я могу судить, это проблема только с iOS7, исправленная в iOS8: мой пользовательский переход с контроллером представлений, который нарушен в iOS7, отлично работает в iOS8 без модификация. При этом вы должны убедиться, что это то, что вы тоже видите, и если это так, только запустите исправление на устройствах под управлением iOS7.x. Как вы можете видеть в приведенном выше примере кода, значение y-настройки равно 0.0f, если устройство не работает iOS7.x.

Ответ 11

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

Другим решением является вычисление рамки VC... что-то вроде этого:

float y = toVC.navigationController.navigationBar.frame.origin.y + toVC.navigationController.navigationBar.frame.size.height;
toVC.view.frame = CGRectMake(0, y, toVC.view.frame.size.width, toVC.view.frame.size.height - y);

Сообщите мне, нашли ли вы лучшее решение. Я тоже борюсь с этой проблемой, и я придумал предыдущие идеи.