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

时间:2013-09-25 05:57:58

标签: ios uiscrollview autolayout

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

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

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

滚动视图的约束在IB中配置。以下是我在代码中为内容视图配置约束的方法:

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

如果用户点击编辑按钮,则使用故事板中的segue以模态方式呈现另一个视图控制器。取消视图控制器后,即使约束不变,系统也会莫名其妙地修改内容视图的帧。

我在以下委托方法中忽略了呈现的视图控制器:

- (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。没有异常被抛出。滚动视图甚至滚动,尽管内容视图部分是在屏幕外。

否我在哪里明确设置滚动视图的内容大小;我把它留给系统。内容视图具有内在大小(内容视图的大小看起来很好;它是内容视图的起源,这是问题)。

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

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

有什么建议吗?

2 个答案:

答案 0 :(得分:7)

我创建了一个UIScrollView子类,可以解决这个问题(在iOS7中修复了BTW):

@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

答案 1 :(得分:0)

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

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

事实证明,我无法在呈现视图控制器的viewWillAppear:方法中恢复内容偏移,因为它太早了。在viewDidAppear中恢复内容偏移:为时已晚,因为它产生了一个不和谐的UI更新。使用自动布局时,我发现viewDidLayoutSubviews在时间方面恰好是正确的。

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

不幸的是,另一个错误很快浮出水面。如果我呈现并解除模态视图控制器,滚动到另一个页面,然后再次呈现模态视图控制器,当模态视图控制器被解除时,应用程序将崩溃。控制台中没有提供有关应用程序崩溃原因的信息。在调用dismissViewControllerAnimated时抛出了异常:completion:。

通过关闭我的自定义页面平铺方法(在滚动视图的内容偏移更改时随时触发)解决了后一个问题。页面切片方法可确保显示正确的内容页面并回收子视图。在我的内容偏移舞蹈期间,我不需要进行页面平铺,因为已经加载了正确的子视图。所以这是解决模态视图控制器而不替换内容视图的框架或使应用程序崩溃的最终修复:

// 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是否会影响这些问题。