UIScrollView使用自动布局错误偏移

时间:2023-01-23 15:04:52

I have a fairly simple view configuration:

我有一个相当简单的视图配置:

A UIViewController, with a child UIScrollView and a UIImageView in this UIScrollView. I set the UIImageView with a height sufficient to break out of the visible area (ie. higher to 1024pt), and set the Bottom space to superview constraint of my UIImageView to a fixed positive value (20 for example).

一个UIViewController,在这个UIScrollView中有一个子UIScrollView和一个UIImageView。我设置UIImageView的高度足以突破可见区域(即高达1024pt),并将Bottom空间设置为我的UIImageView的superview约束到固定的正值(例如20)。

UIScrollView使用自动布局错误偏移

The whole setup works as expected, the image scrolls nicely in its parent. Except when the view is scrolled (the effect is more visible if you scrolled to the bottom of the view), then disappear, and appear again (you switched to another view and came back) the scrolling value is restored, but the content of the scroll view is moved to the outside top part of its parent view.

整个设置按预期工作,图像在其父级中滚动很好。除非滚动视图(如果滚动到视图底部,效果更明显),然后消失,再次出现(您切换到另一个视图并返回)滚动值将恢复,但内容为滚动视图移动到其父视图的外部顶部。

This is not simple to explain, I'll try to draw it: UIScrollView使用自动布局错误偏移

这不是一个简单的解释,我会试着画出来:

If you want to test/view the source (or the storyboard, I did not edit a single line of code). I put a little demo on my github: https://github.com/guillaume-algis/iOSAutoLayoutScrollView

如果您想测试/查看源(或故事板,我没有编辑一行代码)。我在我的github上放了一个小小的演示:https://github.com/guillaume-algis/iOSAutoLayoutScrollView

I did read the iOS 6 changelog and the explanation on this particular topic, and think this is the correct implementation of the second option (pure auto layout), but in this case why is the UIScrollView behaving so erratically ? Am I missing something ?

我确实阅读了iOS 6更改日志和关于这个特定主题的解释,并认为这是第二个选项(纯自动布局)的正确实现,但在这种情况下,为什么UIScrollView表现得如此不规律?我错过了什么吗?

EDIT: This is the exact same issue as #12580434 uiscrollview-autolayout-issue. The answers are just workarounds, as anyone found a proper way to fix this or is this a iOS bug ?

编辑:这是与#12580434 uiscrollview-autolayout-issue完全相同的问题。答案只是解决方法,因为任何人都找到了解决这个问题的正确方法,或者这是一个iOS错误?

EDIT 2: I found another workaround, which keep the scroll position in the same state the user left it (this is an improvement over 12580434's accepted answer):

编辑2:我找到了另一种解决方法,它将滚动位置保持在用户离开的相同状态(这是对12580434接受的答案的改进):

@interface GAViewController ()

@property CGPoint tempContentOffset;

@end


@implementation GAViewController

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.tempContentOffset = self.mainScrollView.contentOffset;
    self.scrollView.contentOffset = CGPointZero;
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    self.scrollView.contentOffset = self.tempContentOffset;
}

This basically save the offset in viewWillAppear, reset it to the origin, and then restore the value in viewDidAppear. The problem seems to occur between these two calls, but I can't find its origin.

这基本上保存了viewWillAppear中的偏移量,将其重置为原点,然后在viewDidAppear中恢复该值。问题似乎发生在这两个调用之间,但我无法找到它的起源。

7 个解决方案

#1


22  

Yeah, something strange happened with UIScrollView in pure autolayout environment. Re-reading the iOS SDK 6.0 release notes for the twentieth time I found that:

是的,UIScrollView在纯自动布局环境中发生了一些奇怪的事情。第二十次重新阅读iOS SDK 6.0发行说明我发现:

Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.

请注意,通过在视图和滚动视图子树外部的视图(例如滚动视图的超级视图)之间创建约束,可以使滚动视图的子视图显示为浮动(不滚动)在其他滚动内容上。

Solution

Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.

将子视图连接到外部视图。换句话说,对于嵌入了scrollview的视图。

As IB does not allow us set up constraints between the imageView and a view outside the scroll view’s subtree, such as the scroll view’s superview then I've done it in code.

由于IB不允许我们在imageView和滚动视图的子树外部的视图之间设置约束,例如滚动视图的超级视图,然后我在代码中完成了它。

- (void)viewDidLoad {
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    [self.view removeConstraints:[self.view constraints]];
    [self.scrollView removeConstraints:[self.scrollView constraints]];
    [self.imageView removeConstraints:[self.imageView constraints]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_imageView(700)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_imageView(1500)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
}

And vau! It works!

和vau!有用!

#2


10  

The edit didn't work for me. But this worked:

编辑对我不起作用。但这有效:

-(void)viewWillDisappear:(BOOL)animated
{
     [super viewWillDisappear:animated];

     self.tempContentOffset = self.scrollView.contentOffset;
     self.scrollView.contentOffset = CGPointZero;
}

- (void)viewDidLayoutSubviews {
     [super viewDidLayoutSubviews];
     self.scrollView.contentOffset = self.tempContentOffset;
}

#3


5  

For me I went to the IB clicked my view controller that contains the scroll view. Then I went to Attribute Inspector -> View Controller -> Extend Edges -> Uncheck "Under Top Bars" and "Under Bottom Bars".

对我来说,我去了IB点击了包含滚动视图的视图控制器。然后我去了属性检查器 - >查看控制器 - >扩展边缘 - >取消选中“在顶部条形图下”和“在底部条形图下”。

#4


2  

Simple solution found, Just put

找到简单的解决方案,就这么说

[self setAutomaticallyAdjustsScrollViewInsets:NO];

in your ViewControllers viewDidLoad method

在ViewControllers viewDidLoad方法中

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

    [self setAutomaticallyAdjustsScrollViewInsets:NO];
}

#5


0  

I had a similar problem using a UIScrollView in a UIViewController with a top extra space that was not visible in Storyboard. The solution for me was to uncheck the "Adjust Scroll View Insets" on the ViewController storyboard properties : see answer Extra Space (inset) in Scroll View at run time

我在UIViewController中使用UIScrollView时遇到了类似的问题,其中有一个在Storyboard中不可见的顶部额外空间。我的解决方案是取消选中ViewController故事板属性上的“调整滚动视图插入”:请参阅在运行时滚动视图中的回答额外空间(插图)

#6


-1  

Add a global property contentOffset and save the current contentOffset in viewDidDisappear. Once you return the method viewDidLayoutSubviews will be called and you can set your original contentOffset.

添加全局属性contentOffset并将当前contentOffset保存在viewDidDisappear中。返回方法后,将调用viewDidLayoutSubviews,您可以设置原始contentOffset。

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self.scrollView setContentOffset:self.contentOffset animated:FALSE];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    self.contentOffset = self.scrollView.contentOffset;
    [self.scrollView setContentOffset:CGPointMake(0, 0) animated:FALSE];
}

#7


-4  

Looks like the problem solved with the dispatch_async during the viewWillAppear:

看起来在viewWillAppear期间使用dispatch_async解决了问题:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    CGPoint originalContentOffset = self.scrollView.contentOffset;
    self.scrollView.contentOffset = CGPointZero;

    dispatch_async(dispatch_get_main_queue(), ^{
        self.scrollView.contentOffset = originalContentOffset;
    });
}

#1


22  

Yeah, something strange happened with UIScrollView in pure autolayout environment. Re-reading the iOS SDK 6.0 release notes for the twentieth time I found that:

是的,UIScrollView在纯自动布局环境中发生了一些奇怪的事情。第二十次重新阅读iOS SDK 6.0发行说明我发现:

Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.

请注意,通过在视图和滚动视图子树外部的视图(例如滚动视图的超级视图)之间创建约束,可以使滚动视图的子视图显示为浮动(不滚动)在其他滚动内容上。

Solution

Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.

将子视图连接到外部视图。换句话说,对于嵌入了scrollview的视图。

As IB does not allow us set up constraints between the imageView and a view outside the scroll view’s subtree, such as the scroll view’s superview then I've done it in code.

由于IB不允许我们在imageView和滚动视图的子树外部的视图之间设置约束,例如滚动视图的超级视图,然后我在代码中完成了它。

- (void)viewDidLoad {
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    [self.view removeConstraints:[self.view constraints]];
    [self.scrollView removeConstraints:[self.scrollView constraints]];
    [self.imageView removeConstraints:[self.imageView constraints]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_imageView(700)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_imageView(1500)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
}

And vau! It works!

和vau!有用!

#2


10  

The edit didn't work for me. But this worked:

编辑对我不起作用。但这有效:

-(void)viewWillDisappear:(BOOL)animated
{
     [super viewWillDisappear:animated];

     self.tempContentOffset = self.scrollView.contentOffset;
     self.scrollView.contentOffset = CGPointZero;
}

- (void)viewDidLayoutSubviews {
     [super viewDidLayoutSubviews];
     self.scrollView.contentOffset = self.tempContentOffset;
}

#3


5  

For me I went to the IB clicked my view controller that contains the scroll view. Then I went to Attribute Inspector -> View Controller -> Extend Edges -> Uncheck "Under Top Bars" and "Under Bottom Bars".

对我来说,我去了IB点击了包含滚动视图的视图控制器。然后我去了属性检查器 - >查看控制器 - >扩展边缘 - >取消选中“在顶部条形图下”和“在底部条形图下”。

#4


2  

Simple solution found, Just put

找到简单的解决方案,就这么说

[self setAutomaticallyAdjustsScrollViewInsets:NO];

in your ViewControllers viewDidLoad method

在ViewControllers viewDidLoad方法中

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

    [self setAutomaticallyAdjustsScrollViewInsets:NO];
}

#5


0  

I had a similar problem using a UIScrollView in a UIViewController with a top extra space that was not visible in Storyboard. The solution for me was to uncheck the "Adjust Scroll View Insets" on the ViewController storyboard properties : see answer Extra Space (inset) in Scroll View at run time

我在UIViewController中使用UIScrollView时遇到了类似的问题,其中有一个在Storyboard中不可见的顶部额外空间。我的解决方案是取消选中ViewController故事板属性上的“调整滚动视图插入”:请参阅在运行时滚动视图中的回答额外空间(插图)

#6


-1  

Add a global property contentOffset and save the current contentOffset in viewDidDisappear. Once you return the method viewDidLayoutSubviews will be called and you can set your original contentOffset.

添加全局属性contentOffset并将当前contentOffset保存在viewDidDisappear中。返回方法后,将调用viewDidLayoutSubviews,您可以设置原始contentOffset。

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self.scrollView setContentOffset:self.contentOffset animated:FALSE];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    self.contentOffset = self.scrollView.contentOffset;
    [self.scrollView setContentOffset:CGPointMake(0, 0) animated:FALSE];
}

#7


-4  

Looks like the problem solved with the dispatch_async during the viewWillAppear:

看起来在viewWillAppear期间使用dispatch_async解决了问题:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    CGPoint originalContentOffset = self.scrollView.contentOffset;
    self.scrollView.contentOffset = CGPointZero;

    dispatch_async(dispatch_get_main_queue(), ^{
        self.scrollView.contentOffset = originalContentOffset;
    });
}