解除模态视图控制器后,框架不反映自动布局约束

我使用iOS 6,分页UIScrollView和纯自动布局。

简介:我创build了一个滚动页面内容的视图控制器。 一些视图是在故事板中创build和configuration的,其他则是以编程方式。 这是视图层次结构:

- Main view (storyboard) - UIScrollView (storyboard) - content view (programmatically) - subviews representing pages of content (programmatically) 

在IB中configuration滚动视图的约束。 以下是我如何在代码中configuration内容视图的约束:

 - (void)viewDidLoad { // ABPageScrollerContentView is a subclass of UIView; it overrides intrinsicContentSize; the size is calculated without referencing the scroll view's dimensions self.contentView = [[ABPageScrollerContentView alloc] init]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.pageScrollerView addSubview:self.contentView]; // configure constraints between scroll view and content view... UIView *contentView = self.contentView; NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView); [self.pageScrollerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewsDictionary]]; [self.pageScrollerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewsDictionary]]; // the content view's subviews are added/removed in the tilePages method (not shown); tilePages is called later in the view controller lifecycle... } 

如果用户点击一个编辑button,另一个视图控制器将在故事板中以模式显示。 在视图控制器被解散之后,即使约束没有改变,系统似乎莫名其妙地修改了内容视图的框架。

我在下面的委托方法中closures了呈现的视图控制器:

 - (void)didExitEditPageViewVC:(id)controller { // update currently displayed page view from data model... // logged content view frame = (0, 0; 1020, 460) [self dismissViewControllerAnimated:YES completion:^{ // logged content view frame = (-170, 0; 1020, 460) }]; } 

我不明白框架原点的x分量是如何从0改变到-170的。 解除视图控制器之前和之后的约束是相同的。

在调用dismissViewControllerAnimated之前,这里是框架和约束:completion:方法:

 (lldb) po self.contentView $0 = 0x1ede2b40 <AEBPageScrollerContentView: 0x1ede2b40; frame = (0 0; 1020 460); layer = <CALayer: 0x1edd6f00>> (lldb) po self.pageScrollerView.constraints $1 = 0x1ed076c0 <__NSArrayM 0x1ed076c0>( <NSLayoutConstraint:0x1ede2980 H:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410 )>, <NSLayoutConstraint:0x1eded480 H:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410 )>, <NSLayoutConstraint:0x1edecbc0 V:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410 )>, <NSLayoutConstraint:0x1ede1040 V:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410 )> ) 

这里是框架和呈现视图控制器重新出现后的约束:

 contentView = <AEBPageScrollerContentView: 0x1ede2b40; frame = (-170 0; 1020 460); layer = <CALayer: 0x1edd6f00>> self.pageScrollerView.constraints = ( "<NSLayoutConstraint:0x1ede2980 H:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410 )>", "<NSLayoutConstraint:0x1eded480 H:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410 )>", "<NSLayoutConstraint:0x1edecbc0 V:|-(0)-[AEBPageScrollerContentView:0x1ede2b40] (Names: '|':UIScrollView:0x1edd3410 )>", "<NSLayoutConstraint:0x1ede1040 V:[AEBPageScrollerContentView:0x1ede2b40]-(0)-| (Names: '|':UIScrollView:0x1edd3410 )>" ) 

为什么内容视图的框架意外改变? 为什么它不符合约束条件呢?

延迟调用hasAmbiguousLayout会令人惊讶地返回false。 没有例外被抛出。 滚动视图甚至滚动,尽pipe内容视图部分在屏幕外。

没有我在哪里显式设置滚动视图的内容大小; 我把它留给系统。 内容视图有一个固有的大小(内容视图的大小看起来很好,这是内容视图的起源是问题)。

滚动视图的内容偏移在closures视图控制器之前和之后是相同的。 然而,内容视图的原点的x分量的位移与内容偏移成比例。 内容偏移量越大,模式视图控制器closures后,内容视图的原点的x分量就越负。 而且,在内容偏移量为“零”时,x分量为零。 因此,如果在查看第一页内容时(当内容偏移量为“0”时)呈现模态视图控制器,则在解除视图控制器的情况下内容视图的帧是正确的。 content-offset-of-zero情况是内容视图的框架正确反映其约束的唯一情况。

我曾尝试插入调用layoutIfNeeded在各个地方没有结果。

有什么build议么?

我创build了一个解决这个问题的UIScrollView子类(在iOS7中,它是固定的):

 @interface ConstraintsSafeScrollView : UIScrollView @end @implementation ConstraintsSafeScrollView { CGPoint _savedContentOffset; UIEdgeInsets _savedContentInset; } - (void)willMoveToWindow:(UIWindow *)newWindow { if (newWindow) { // Reset the scrollview to the top. [super setContentOffset:CGPointMake(-_savedContentInset.left, -_savedContentInset.top)]; } [super willMoveToWindow:newWindow]; } // Overridden to store the latest value. - (void)setContentOffset:(CGPoint)contentOffset { _savedContentOffset = contentOffset; [super setContentOffset:contentOffset]; } // Overridden to store the latest value. - (void)setContentInset:(UIEdgeInsets)contentInset { _savedContentInset = contentInset; [super setContentInset:contentInset]; } - (void)didMoveToWindow { if (self.window) { // Restore offset and insets to their previous values. self.contentOffset = _savedContentOffset; self.contentInset = _savedContentInset; } [super didMoveToWindow]; } @end 

由于解除模态视图控制器后内容视图的框架是正确的唯一情况是当滚动视图的内容偏移量为“零”时,我解决了这个问题,如下所示:

 // presenting view controller's implementation self.contentOffsetBeforeModalViewControllerDismissal = self.pageScrollerView.contentOffset; self.pageScrollerView.contentOffset = CGPointMake(0, 0); [self dismissViewControllerAnimated:YES completion:nil]; 

然后,我在呈现视图控制器的viewDidLayoutSubviews方法中恢复了实际的内容偏移量:

 - (void)viewDidLayoutSubviews { if (!CGPointEqualToPoint(self.contentOffsetBeforeModalViewControllerDismissal, CGPointZero)) { self.pageScrollerView.contentOffset = self.contentOffsetBeforeModalViewControllerDismissal; self.contentOffsetBeforeModalViewControllerDismissal = CGPointZero; } } 

事实certificate,我无法恢复呈现视图控制器的viewWillAppear:方法中的内容偏移,因为它太早了。 恢复viewDidAppear:中的内容偏移太迟了,因为它产生了一个令人震惊的UI更新。 在使用自动布局时,我发现通常viewDidLayoutSubviews在时间上是正确的。

这感觉就像一个黑客; 但它是一个熟悉的黑客:保存一些状态,让系统做它的事情,恢复这一点的状态。

不幸的是,另一个bug迅速浮出水面。 如果我提交和解散模态视图控制器,滚动到另一个页面,然后再次呈现模态视图控制器,应用程序将崩溃时,模式视图控制器被解散。 控制台中没有提供关于应用程序崩溃原因的信息。 在调用dismissViewControllerAnimated:completion:时抛出exception。

后面的问题已经通过closures我的自定义页面平铺方法(在滚动视图的内容偏移改变时触发)解决。 页面平铺方法确保显示正确的内容页面并回收子视图。 我不需要在我的内容偏移舞蹈中发生页面平铺,因为正确的子视图已经加载。 所以这里是解除模式视图控制器的最终修复,而不会取代内容视图的框架或崩溃的应用程序:

 // presenting view controller's implementation self.disablePageTiling = YES // flag causes tilePages to do nothing self.contentOffsetBeforeModalViewControllerDismissal = self.pageScrollerView.contentOffset; self.pageScrollerView.contentOffset = CGPointMake(0, 0); // triggers tilePages, but nothing will happen because of the flag [self dismissViewControllerAnimated:YES completion:^{ self.disablePageTiling = NO; }]; 

viewDidLayoutSubviews的实现和上面一样。

我不认为这些问题可以通过任何方式彻底解决。 当使用滚动视图的自动布局,我一直留下这个唠叨的感觉,我正在写错误的代码。 所以我欢迎进一步的见解。 我也很好奇iOS 7是否会影响这些问题。