UIScrollView在contentSize改变时会调整contentOffset

42

我在将一个细节视图控制器推入导航控制器之前,调整它的状态:

[self.detailViewController detailsForObject:someObject];
[self.navigationController pushViewController:self.detailViewController
                                     animated:YES];

DetailViewController 中有一个滚动视图,我根据传递的对象调整内容的大小:

- (void)detailsForObject:(id)someObject {
    // set some textView's content here
    self.contentView.frame = <rect with new calculated size>;

    self.scrollView.contentSize = self.contentView.frame.size;
    self.scrollView.contentOffset = CGPointZero;
}

现在,所有这些都可以正常工作,但是 scrollView 在 navigationController 的滑入动画期间会调整其 contentOffset 。这意味着,contentOffset 将被设置为上一次 contentSize 与新计算 contentSize 之间的差异。这意味着第二次打开 detailsView 时,详细信息将滚动到某个不希望的位置。尽管我明确地将 contentOffset 设置为 CGPointZero

我发现在 - viewWillAppear 中重置 contentOffset 没有效果。我能想到最好的方法是在 viewDidAppear 中重置 contentOffset,在内容上下移动时会出现明显的上下移动:

- (void)viewDidAppear:(BOOL)animated {
    self.scrollView.contentOffset = CGPointZero;
}

UIScrollViewcontentSize 被更改时,是否有一种方法可以防止它调整其 contentOffset


你为什么要在动画块内调整内容偏移量?跳过CATransaction的部分,这样它就不会被动画化。然后它看起来就不会很生硬了。 - Andrew
确实删除CATransaction相关内容并不会改变任何事情,因此它被删除了。 - Berik
你确定viewDidAppear被调用了吗?我建议在其中设置断点或NSLog语句来检查。此外,您尝试过交换调用顺序吗?比如说先推出视图再设置detailsForObject? - Andrew
是的,它被称为,并且它将正确的contentOffset设置到scrollView中。但是,在viewDidAppear被调用之前,contentOffset动画就已经发生了。 - Berik
8个回答

62

当使用UINavigationController推入一个包含UIScrollViewUIViewController时发生。

iOS 11+

解决方案1(Swift代码):

scrollView.contentInsetAdjustmentBehavior = .never

解决方案 2 (故事板)

enter image description here

iOS 7

解决方案 1 (代码)

@property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets设置为NO

解决方案 2 (故事板)

取消选中Adjust Scroll View Insets

Adjust Scroll View Insets

iOS 6

解决方案 (代码)

viewWillLayoutSubviews中设置UIScrollView的属性contentOffsetcontentInset。示例代码:

- (void)viewWillLayoutSubviews{
  [super viewWillLayoutSubviews];
  self.scrollView.contentOffset = CGPointZero;
  self.scrollView.contentInset = UIEdgeInsetsZero;
}

1
这是解决问题的新方法。它比重置contentSize更整洁。 - Berik
3
优秀的回答。感谢您发布关于iOS 7差异的内容,甚至包括图像以作澄清。太棒了! - Marchy
1
很高兴能帮助到你们 :) - KarenAnne

15

尽管本问题的根本原因不明,但我找到了一个解决方案。在调整内容大小和偏移量之前先将它们重置,UIScrollView 就不会进行动画处理:

- (void)detailsForObject:(id)someObject {
    // These 2 lines solve the issue:
    self.scrollView.contentSize = CGSizeZero;
    self.scrollView.contentOffset = CGPointZero;

    // set some textView's content here
    self.contentView.frame = <rect with new calculated size>;

    self.scrollView.contentSize = self.contentView.frame.size;
    self.scrollView.contentOffset = CGPointZero;
}

3

我曾遇到一个UIScrollview的问题,原因是没有设置contentSize。将contentSize设置为项目数量后就解决了这个问题。

self.headerScrollView.mainScrollview.contentSize = CGSizeMake(320 * self.sortedMaterial.count, 0);

3
这是我的解决方案:
在Storyboard中的ScrollView中,将Size Inspector中的Content Insets Adjustment Behavior设置为“Never”。
图示参考:
enter image description here

0

补充KarenAnne的答案:

iOS 11+

automaticallyAdjustsScrollViewInsets已被弃用

请使用以下方法代替:

Storyboards:

enter image description here

代码(Swift):

scrollView.contentInsetAdjustmentBehavior = .never

我认为如果你将这个作为对KarenAnne答案的修改提出会更好。 - Yeheshuah
你是对的,@Yeheshuah。抱歉我不知道我可以提出修改建议。 - Tilman

0

我遇到了类似的问题,即在推送动画期间UIKit设置了我的scrollView的contentOffset。

这些解决方案都对我不起作用,可能是因为我支持iOS 10和iOS 11。

我通过子类化我的滚动视图来解决我的问题,以防止UIKit在滚动视图从窗口中移除后更改我的偏移量:

/// A Scrollview that only allows the contentOffset to change while it is in the window hierarchy. This can keep UIKit from resetting the `contentOffset` during transitions, etc.
class LockingScrollView: UIScrollView {
    override var contentOffset: CGPoint {
        get {
            return super.contentOffset
        }
        set {
            if window != nil {
                super.contentOffset = newValue
            }
        }
    }
}

这是一个相当粗糙的解决方案。你可能会在iOS更新等方面遇到问题。你能否进行更多调试,看看是什么导致了UIKit在你的情况下表现不良?(setter何时被调用?UI上是否有可用的设置?是否有其他动画正在运行?是否有自定义转换等?) - Berik
1
毫无疑问,这是一种笨拙的方法,我更喜欢其他方式。我进行了更深入的挖掘,并发现setter是由视图的_didMoveFromWindow:toWindow调用的。看起来这是一个相关的问题:https://dev59.com/UmvXa4cB1Zd3GeqPL7ic - SlimeBaron
1
我已经更新了我的回答,采用了稍微更清晰(虽然仍然很武断)的方法。 - SlimeBaron

0

你的scrollView是DetailViewController的根视图吗?如果是,尝试将scrollView包装在一个普通的UIView中,并将后者作为DetailViewController的根视图。由于UIView没有contentOffset属性,它们不会受到导航控制器(由于导航栏等原因)所做的内容偏移调整的影响。


1
scrollView不是DetailViewController的根视图。self.scrollView绑定到一个UIScrollView,该UIScrollView位于DetailViewController的根视图中。 - Berik

0

我遇到了这个问题,对于一个特定的情况 - 我不调整大小 - 我使用了以下方法:

float position = 100.0;//for example

SmallScroll.center = CGPointMake(position + SmallScroll.frame.size.width / 2.0, SmallScroll.center.y);

对于 y 坐标同样适用:anotherPosition + SmallScroll.frame.size.height / 2.0

因此,如果您不需要调整大小,这是一个快速且无痛的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接