Autolayout Constraints"消失"在UIPageViewController中滚动

时间:2015-02-24 14:45:35

标签: ios objective-c autolayout nslayoutconstraint

我正在尝试使用"浮动"来构建滚动视图。标头使用自动布局。更确切地说,我正在尝试使用多个列构建日历视图。这些列中的每一列都应该有自己的标题,它应该浮在顶部,而列可以在它下面垂直滚动。

如果一切正常,它看起来像这样: Calendar display working correctly

正如您所看到的,有几列和一个页面控件可以指示有多少列可用。

但是,当滑动/平移(或者甚至只是尝试滑动)切换到下一页时,将标题标签保持在顶部的约束将被删除,它们将消失到滚动视图的顶部(它们位于故事板中) )。

Broken calendar view part 1 由于在屏幕外滚动,列标题不可见

Broken calendar view part 2 列标题位于滚动视图的顶部(高度错误)。

设置

用户可以在日期之间切换("今天"顶部的按钮,左右箭头),并在显示的人之间切换。 (在视图上滑动/平移) Boh交互是通过UIPageViewController s彼此实现的。外部页面视图控制器在日期之间切换,列之间的内部日期(与人员)。 时间视图包含在外部页面视图控制器中,但不包含在内部视图控制器中。

因此视图和控制器的层次结构如下所示:

Calendar PageViewController (the one controlled via buttons in the navigation bar)
-- Scroll View of Page View Controller
---- Team View Controller (with view)
------ Header View reference (see screenshot 2)
------ Scroll View for vertical scrolling
-------- Time View (the one on the left)
-------- People PageViewController (the one controlled by swiping left/right)
---------- ScrollView of Page View Controller
------------ ViewController for a Single Page (with view)
--------------(1-n) Container View Controller (several of those, in the example 4 per page)
---------------- Column View Controller
------------------ Header View (A UIVisualEffectsView with a label)
------------------ calendar column view (the one doing the horizontal stripes)

将各个列视图控制器的标题视图放在顶部的两个引脚我使用内部页面视图控制器外部的参考视图和垂直滚动视图之外的参考视图。我在上面的概述中称它为Header View reference,你可以在破碎的例子中看到它很好:

Header View Reference

这是一个简单的UIVisualEffectsView,我约束在左上方,其高度和宽度与时间视图相同。所以这个视图具有正确的位置和高度我希望我的所有标题视图都有,并且我使用它来限制代码中的各个列标题视图(在故事板中设置所有其他约束)updateViewConstraints方法每个ColumViewController如此:

- (void)updateViewConstraints
{
    DDLogDebug(@"updating view constraints in colum view controller for %@", self.employee.displayName);
    UIView *baseView = self.headerViewReferenceContainer.viewForHeaderContainerViewConstraints;
    UIView *containerReference = self.headerViewReferenceContainer.headerContainerViewReference;
    NSParameterAssert([containerReference isDescendantOfView:baseView] && [self.headerContainerView isDescendantOfView:baseView]);
    [baseView addConstraint:[NSLayoutConstraint constraintWithItem:self.headerContainerView
                                                     attribute:NSLayoutAttributeTop
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:containerReference
                                                     attribute:NSLayoutAttributeTop
                                                        multiplier:1.0
                                                          constant:0]];

    [baseView addConstraint:[NSLayoutConstraint constraintWithItem:self.headerContainerView
                                                         attribute:NSLayoutAttributeHeight
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:containerReference
                                                     attribute:NSLayoutAttributeHeight
                                                        multiplier:1.0
                                                          constant:0]];

    [super updateViewConstraints];
}

baseView是应该添加约束的视图。它必须是标题引用视图和列视图控制器的标题视图的超级视图。目前,这将是上面Team View Controller的视图(包含垂直滚动视图的视图)。

containerReference是Header View Reference视图,如上面的屏幕截图所示。

因此,我将列标题视图限制为与参考视图具有相同的顶部位置和相同的高度。 (宽度和x位置取决于列)

正如您在破损的屏幕截图中看到的那样,标题视图引用始终正确定位。此外,在加载视图控制器和视图时,会正确设置约束。

然后当启动一个平移手势切换PeoplePageView控制器的页面并加载下一个ViewController for a Single Page时,约束会以某种方式消失,并且标题视图移动到滚动视图的顶部,因为不再固定到最佳。

下一个View Controller for a Single Page设置了正确的约束,但只有在它卡入到位之前。

While Panning

因此,在平移视图控制器的约束以进行显示时,可以正确设置。但是一旦它卡入到位(感觉它发生在-viewDidAppear),约束也会被删除。

我不明白为什么以及如何删除约束。

什么是错的/我尝试了什么

可能是因为其中一个相关的观点消失了,但是

  • baseView在向右滑动时根本没有变化,因为它是分页滚动视图的超级视图
  • 标题引用视图未更改,因为它未包含在分页页面视图控制器中。
  • 标题列视图在视图控制器完全脱屏之前不会消失,因此在分页期间布局不应中断

我曾经遇到布局问题,只有在分页完成后才会出现问题。这是由于我对iOS 8 UIPageViewController Applying Constraints After Transitions中描述的顶部布局约束的限制。 因此,我使用超级视图顶部的固定距离来固定我的Header View Reference,而不是将其固定到顶部布局约束。

因此UIPageViewController在分页上没有正确的顶部布局约束的错误也不应该是问题。

我没有以编程方式添加任何视图。我的故事板全部使用自动布局,视图控制器通过故事板中的容器视图控制器相互添加,或实现UIPageViewControllerDatasource并从xib实例化视图控制器。所以不应该有与translatesAutoResizingMasks相关的任何问题。此外,它适用于加载,它只会在分页时中断,这不符合通常的translatesAutoResizingMasks = YES问题。

我也没有遇到任何有关冲突约束的错误或没有为约束做好准备的视图层次结构,只是删除了工作约束。

我应该有一个我认为无关的问题,但我会在此列出完整性问题'清酒:由于标签内部的布局限制,我发现错误,列标题视图被添加到早期:

The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7af4df20 UILabel:0x7af4e0d0'Susanne'.width == UIVisualEffectView:0x7af4de70.width>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.

然而,该消息抱怨标签与其包含视图(列的标题视图)之间的关系,而不是关于&#34;外部&#34;标题视图的约束。此外,它警告说,如果需要过早解决约束,应用程序将崩溃。该应用程序不会崩溃,所以时间似乎是正确的。此外,应用程序抱怨的限制是在故事板中设置的,所以我不知道如何将时间错误。

tl; dr:从UIPageViewController内部到视图层次结构的多个级别到达视图层次结构的约束被删除(没有警告,注释,原因或任何内容)。 我不明白为什么以及如何防止这种情况发生或在什么时候再次添加它们。

更新

我将以下调用添加到viewWillAppear:

[self.view setNeedsUpdateConstraints]

这具有以下效果:

  • 当将页面拖到一边(不切换分页)并再次释放它(因此它快速向后)时,再次修复损坏的约束(并且布局是固定的)。这是因为页面视图控制器在原始视图控制器上调用viewWillAppear:,如果它&#34;快速恢复&#34; (以抵消先前发出的viewWillDisappear电话)。
  • 但在滚动期间,布局仍然存在。
  • 同样,当刷到新页面时,新页面的约束也会被破坏。
  • 但是每当调用viewWillAppear时,约束会被修复一段时间,直到它们被再次删除(通常在滚动结束时)。

如果我将相同的来电添加到viewDidAppear 而不是 viewWillAppear,则会发生以下情况:

  • 这导致布局在大多数时间都是正确的(它们在滚动结束后固定)。
  • 但在滚动期间它仍然被打破。

现在,如果我向两种方法添加[self.view setNeedsUpdateConstraints],则会发生以下情况:

  • 布局几乎在所有时间都表现正确,没有奇怪的跳跃视图
  • 第一页更改除外。在那个期间,原始页面的约束似乎被删除,并且布局以上面显示的方式被破坏。在此之后,约束似乎永久固定,来回滑动工作没有问题。
  • 当分页到最后一页(并试图超越它)并返回时,三页(我也尝试了四页)都是如此,尽管我非常确定页面视图控制器没有保留所有页面内存中有三页。

4 个答案:

答案 0 :(得分:3)

是否有可能不止一次调用updateViewConstraints导致多次添加相同的约束,从而导致问题。我相信这很容易发生。

我在很多讨论中都注意到许多人似乎建议不要创建并添加此函数,(viewDidLoad似乎更受欢迎)并且只在此函数中执行约束值设置以最小化多个调用的问题但是要确保尺寸是正确的。另一种选择似乎是为控制器添加一个布尔保护,以限制加法,以确保它只在控制器的生命周期内发生一次。

可能不是你的问题,但值得一提。

答案 1 :(得分:0)

尝试在viewDidLayoutSubviews中设置约束。我不太确定,但我记得以这种方式解决约束相关的问题,它似乎有效。

答案 2 :(得分:0)

为什么不将标题View的顶部设置为基本视图的顶部。即包含scrollview的视图。这肯定会解决您的问题。

答案 3 :(得分:0)

好的,我仍然认为这是一个错误,但我找到了一个解决方法:

我基本上有两个我想要保留的约束,有时会消失。我为这些创建属性,但使它们

@property (weak) NSLayoutConstraint *headerViewHeightConstraint;
@property (weak) NSLayoutConstraint *headerViewTopConstraint;

然后我将updateViewConstraints中创建的约束分配给这些属性,如下所示:

NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.headerContainerView
                                                                 attribute:NSLayoutAttributeTop
                                                                 relatedBy:NSLayoutRelationEqual
                                                                    toItem:containerReference
                                                                 attribute:NSLayoutAttributeTop
                                                                multiplier:1.0
                                                                  constant:0];
[baseView addConstraint:topConstraint];
self.headerViewTopConstraint = topConstraint;

通过使用weak引用,我可以检查约束是否存在而不保留它,如果没有其他引用它。这样,当从视图中删除约束时(无论出于何种原因),它将被释放,我的属性将为零。我可以使用它来检查是否需要重新建立这样的约束:

- (void)reestablishHeaderViewConstraintsIfNecessary
{
    if (!self.headerViewTopConstraint || !self.headerViewHeightConstraint) // if any of the constraints is missing
    {
        [self.view setNeedsUpdateConstraints];
    }
}

现在我只需要一个地方来调用该方法,以确保在删除约束时调用它。正如问题viewWillAppearviewDidAppear中描述的那样不够好。相反,我使用viewWillLayoutSubviews。每当边界发生变化时(例如在滚动期间多次)就会调用它,这很常见,但是由于我将实际约束失效包裹起来,如果不需要这样做,这应该足够便宜:

- (void)viewWillLayoutSubviews
{
    [self reestablishHeaderViewConstraintsIfNecessary];
    [super viewWillLayoutSubviews];
}

我认为使用viewWillLayoutSubviews而非viewDidLayoutSubviews广告使viewDidLayoutSubviews中的布局失效会导致异常,但我不确定。这也是我将呼叫置于[super viewWillLayoutSubviews]呼叫前面的原因,但我没有尝试它是否适用于呼叫后出现的呼叫。