UIScrollView在返回到UIViewController后其起始位置发生了变化

58

我在故事板中有一个UIViewController子类作为场景,其中包含一个UIScrollView,其中包含各种子视图。 其中一个子视图是一个UIButton,它连接到另一个场景的UIViewController子类。 当我从视图返回时(弹出导航控制器堆栈中的UIViewController),我发现滚动视图的原点已经改变,尽管contentsizecontentoffset看起来正确。

有趣的是,应用程序具有选项卡栏,当我切换到该视图并返回时,滚动视图会正确设置,并且偏移为(0,0)。

这个过程基本上没有涉及任何代码,因为它基本上都在故事板中。由于我对使用Storyboard相当新,所以我认为我做错了什么,但我不知道是什么。 任何想法可能是什么?也许是尺寸问题或约束问题?


我在使用UICollectionView以模态方式呈现VC时遇到了类似的问题,但仍未解决。 - Andrea
你尝试在viewWillAppear中设置原点了吗?那是控制器从弹出返回的地方。第二次(或第三次...)加载时,它不会再看到viewDidLoad。 - timquinn
好的,我在viewWillAppear中尝试了以下代码:self.scrollView.frame = CGRectMake(self.scrollView.frame.origin.x, self.scrollView.frame.origin.y - y, self.scrollView.frame.size.width, self.scrollView.frame.size.height);其中,y是新的“origin”高度。但是它似乎没有任何效果。有没有其他方法可以设置原点? - Peter Jacobs
你的故事板中使用了自动布局吗?如果是,那么设置scrollview的frame将不起作用。你需要设置相应的约束条件。在你的情况下,也许尝试将scrollview的顶部与其包含视图固定在一起。 - Steph Sharp
Steph,感谢您的回复。我按照您说的将scrollview固定了,但是已经有太多的“顶部空间”约束条件被自动生成了(超过100个!)。我想我必须删除它们或者至少降低它们的优先级,这似乎相当痛苦。最终,我使用了https://dev59.com/h2cs5IYBdhLWcg3wym4h中答案的变体。 - Peter Jacobs
17个回答

92
在iOS 7/8/9中,简单的self.automaticallyAdjustsScrollViewInsets = NO;在我的情况下解决了问题。

简单的解决方案!我只是通过搜索找到了这个并解决了我的问题。 - yong ho
非常好。我在切换选项卡栏的标签时使用它。 - Chandni - Systematix
7
一则注释:假设您有一个包含UIViewController和ContainerController的TabBarController,您需要将以下代码添加到TabBarController中。 - superarts.org
对我来说,在iOS8中将其设置为self.automaticallyAdjustsScrollViewInsets = YES;解决了我的问题。 - iOSdev
我认为由于自动布局约束,它尝试调整一些插图。但最终会破坏布局。谢谢,它起作用了 :) - Anjum Shrimali
显示剩余3条评论

31

尝试在你弹回的视图控制器的viewWillAppear方法中加入以下代码:

 self.scrollView.contentOffset = CGPointMake(0, 0);

编辑:同时添加Peter的代码可以获得最佳效果:

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

- (void)viewWillDisappear:(BOOL)animated {  
    self.recentContentOffset = self.scrollView.contentOffset;
    [super viewWillDisappear:animated];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.scrollView.contentOffset = CGPointMake(0, self.recentContentOffset.y);
}

回到原始滚动位置,没有视觉副作用,并且滚动视图本身在返回后正确地定位在其父视图中(这是一个问题)。


我已经将viewDidLayoutSubviews的代码放在viewWillAppear中,因为在弹出后不会调用viewDidLayoutSubviews。但还是感谢你的回答! - Rudolf J
非常感谢。解决了我的问题。 - androniennn
Ian Wilkinson通过子类化UIScrollView来解决这个问题,我认为这非常棒。https://dev59.com/V3fZa4cB1Zd3GeqPPDyJ#19370881 - Jeow Li Huan

12

实际上,我将那一行代码放在viewDidDisappear方法里。为了让它在视图重新出现时记住偏移量,我在它之前添加了这一行代码。

 self.contentOffset = self.scrollView.contentOffset;

以及

 - (void)viewDidLayoutSubviews { 
       self.scrollView.contentOffset = self.contentOffset; 
 }

6
我遇到了类似的问题,当我关闭一个视图控制器后,我的表视图的contentOffset被改变为(0,-64)。
我的解决方案有点奇怪,我尝试了所有其他答案,但都没有成功,唯一修复我的问题的方法是在.xib文件的控件树中切换tableView的位置。
它是父视图中的第一个控件,就像这样: before 我将tableView移到ImageView右侧,然后它就好了: after 看来把表视图放在第一个位置会导致问题,而将其移动到另一个位置则可以解决问题。
注:我既不使用autoLayout也不使用storyboards。
希望这能帮助到某个人!

这对我有用,使用storyboard和自动布局...我只是在其他所有内容下面放了一些透明的虚拟视图,框架为{0,0,0,0},它就起作用了! - mllm

5
我正在使用一个collectionView,遇到了类似的问题。对于iOS 11:在尺寸检查器中,有一个“内容插入”选项。将其设置为“从不”。这解决了我的问题。希望能帮到其他人。
Objective C:
if (@available(iOS 11, *)) {
   [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Swift:

if #available(iOS 11, *) {
UIScrollView.appearance().contentInsetAdjustmentBehavior = .never
}

唯一有帮助的。谢谢! - Baran Emre
2
没问题。很高兴能帮上忙! - Joseph Francis
尝试了几个答案后,这个有效。 Xcode 9,iOS 11。谢谢! - Matt Mc
@JERC 不,我没有遇到任何崩溃。你有吗? - Joseph Francis
@JERC 我也不确定;尝试在故事板中完成。看看是否有效。 - Joseph Francis
显示剩余3条评论

4
在iOS 11中,我遇到了一个类似的问题,在我弹出一个视图控制器后返回时,之前视图控制器中的表格视图会自动调整其内容插页,导致表格视图的内容突然从顶部跳转。以下解决方法适用于iOS 11。
在Storyboard中,选择表格视图并进入属性检查器,取消选中"自动"行高和估算字段。同时将内容插入从"自动"更改为"从不"。
[Table view]

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

    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
        _isRecoredforios6 = YES;
        _recordedOffsetforios6 = _scrollView.contentOffset;
        _recordedSizeforios6 = _scrollView.contentSize;
        _scrollView.contentOffset = CGPointZero;
    }

}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    if (_isRecoredforios6) {
        _isRecoredforios6 = NO;
        _scrollView.contentSize = _recordedSizeforios6;
        _scrollView.contentOffset = _recordedOffsetforios6;
    }
}

我已经修复了这个 iOS6 的 bug,可以使用以下代码解决。 我解决了在 Scrollview 中出现的 bug。 感谢所有我的朋友!


0

遵循当前答案时仍存在问题:

第二次尝试打开呈现的视图控制器,而没有离开呈现的视图控制器,问题仍然存在。这就是为什么我发布了导致我的解决方案的确切步骤

  1. 所以,我在Storyboard中重置了collectionView的约束条件,并确保它们固定在presentingViewController的主视图上。

  2. 添加self.view.translatesAutoresizingMaskIntoConstraints = YES; 在呈现视图控制器的viewDidLoad方法中。

  3. 并且,在模态呈现的视图控制器出现之前,私下存储了集合视图的contentOffset:

    (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
    
        self.contentOffset = self.collectionView.contentOffset;
        self.collectionView.contentOffset = CGPointZero;
    }
    
    - (void)viewDidLayoutSubviews {
         [super viewDidLayoutSubviews];
         self.collectionView.contentOffset = self.contentOffset;
    }
    

嗨,当我使用overcurrentcontext呈现视图控制器时,viewDidLayoutSubviews没有被调用。 - kemdo
@kemdo 如果你在这个页面 https://dev59.com/pWgu5IYBdhLWcg3wkHyP 向下滚动到更新的回答,可能会有所帮助。 - ChrisHaze

0

以上方法都对我没用,我设法自己制作了一个定制的推/拉动画,现在它运行得非常好。

首先,添加这个实现推动场景的类:

class PushAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.origin.x = container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        var frame = fromView.frame
        frame.origin.x = -container.frame.width
        fromView.frame = frame

        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

然后添加实现弹出场景的这个类

import Foundation
import UIKit

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // set up from 2D transforms that we'll use in the animation
    let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.origin.x = -container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        fromView.transform = offScreenRight
        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

然后将下面这行代码添加到你的viewDidLoad方法中,以更改默认的NavigationController代理

self.navigationController?.delegate = self

看看这个神奇的东西 :)


0

尝试了很多方法:

  • 将 automaticallyAdjustsScrollViewInsets 设置为 false,以查看是否与导航栏有关。
  • 调整固定单元格高度...
  • 缓存单元格高度...
  • 保留具有表视图偏移量的属性,将其缓存并在 viewWillAppear 上设置...
  • 等等。

我意识到问题是关于 estimatedRowHeight 值,它被设置为 50px,而应该是 ~150px。更新值解决了问题,现在表视图保持相同的偏移量。


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