UIPageViewController阻止了我的表格视图在点击状态栏时滚动到顶部

24

在发布这个问题之前,我进行了搜索,但没有找到任何相关的内容。

我遇到了一个很大的问题。我的应用程序以滚动类型的UIPageViewController为基础,其中包含3个视图控制器。

其中一个视图控制器(listTableView)具有表格视图和搜索显示控制器。

问题是,当像普通表格视图一样点击状态栏时,我无法将表格视图滚动到顶部。我认为UIPageViewController正在干扰此操作,但我不知道如何解决它,但我知道我需要解决它,使我的应用程序不会感觉有问题。

非常感谢提供任何帮助。

我知道即使在这种情况下它与问题无关,但我知道有人会要求代码,因此在此提供创建UIPageViewController的代码:

#import "MainViewController.h"

@interface MainViewController ()

@property (nonatomic, strong) UIPageViewController *pageViewController;
@property (nonatomic, strong) NSArray *contentViewControllers;

@end

@implementation MainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageView"];
    self.pageViewController.dataSource = self;

    UIViewController *settings = [self.storyboard instantiateViewControllerWithIdentifier:@"Settings"];

    UIViewController *listTableView = [self.storyboard instantiateViewControllerWithIdentifier:@"List"];

    UIViewController *first = [self.storyboard instantiateViewControllerWithIdentifier:@"First"];

    self.contentViewControllers = [NSArray arrayWithObjects:settings,listTableView,first,nil];

    [self.pageViewController setViewControllers:@[first] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];

    [self addChildViewController:self.pageViewController];
    [self.view addSubview:self.pageViewController.view];
    [self.pageViewController didMoveToParentViewController:self];

    self.view.backgroundColor = [UIColor colorWithRed:(248.0/255.0) green:(248.0/255.0) blue:(248.0/255.0) alpha:1.0];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
  viewControllerBeforeViewController:(UIViewController *)viewController {

    NSUInteger index = [self.contentViewControllers indexOfObject:viewController];

    if (index == 0) {
        return nil;
    }

    return self.contentViewControllers[index - 1];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
   viewControllerAfterViewController:(UIViewController *)viewController {

    NSUInteger index = [self.contentViewControllers indexOfObject:viewController];

    if (index >= self.contentViewControllers.count - 1) {
        return nil;
    }

    return self.contentViewControllers[index + 1];
}

你尝试过将<UIScrollViewDelegate>添加到你的MainViewController接口中吗? - Nitin Alabur
是的,我有尝试过,但那并不起作用。问题在于你无法访问_UIQueuingScrollView,而UIPageViewController中恰好包含了它,这破坏了一切。 - klcjr89
我已按照您的应用程序描述并使用您的代码重新实现它。它的行为是正确的,我无法复制您的问题。问题必须在应用程序的其他地方 - 您能提供更多信息吗?也许是来自您的“列表”viewController的某些代码。 - foundry
请问您能展示一下MainViewController接口的声明吗?(MainViewController.h) - dmirkitanov
5个回答

31

解决方案

经过聊天后,我们发现了一些有趣的事情。页面视图控制器将其他视图控制器的视图保留在视图层次结构中,因此它们也会捕获scrollsToTop行为并产生干扰。这意味着您需要在消失的视图控制器的viewWillDisappear:中为每个可滚动视图禁用scrollsToTop(并在viewWillAppear:上重新启用)。


原始调查

快速简单的方法:滚动视图是UIPageViewController的视图的唯一子视图:

self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];

在调试器中检查:

(lldb) po [self.pageViewController.view recursiveDescription]
<_UIPageViewControllerContentView: 0x8d7c390; frame = (0 0; 320 480); clipsToBounds = YES; opaque = NO; autoresize = W+H; layer = <CALayer: 0x8d7c4a0>>
   | <_UIQueuingScrollView: 0xa912800; frame = (0 0; 320 480); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x8d7cc90>; layer = <CALayer: 0x8d7c7e0>; contentOffset: {320, 0}>
   |    | <UIView: 0x8d7da00; frame = (0 0; 320 480); layer = <CALayer: 0x8d7da60>>
   |    | <UIView: 0x8d7dab0; frame = (320 0; 320 480); layer = <CALayer: 0x8d7db10>>
   |    | <UIView: 0x8d7db40; frame = (640 0; 320 480); layer = <CALayer: 0x8d7dba0>>

您可以使用多种方法来达到该滚动视图。最简单的方法是迭代self.pageViewController.view.subviews并找到其中一个是UIScrollView的子类。由于它是唯一的子视图,在一次迭代后您的循环将会结束。

这个解决方案是否最优?不是。容易出错吗?理论上是。它可能会改变吗?不太可能,因为视图层次结构相当合乎逻辑。它至少为您提供了一个快速修复,而不必因 Apple 的小疏忽(提供用户访问滚动视图)而处理整个应用程序结构的更改。

您应该在https://bugreport.apple.com上向Apple提出功能请求。


嗨Leo,我尝试循环遍历UIPageViewController的滚动视图,我确实可以禁用滚动,但将滚动设置为NO仍然会使我的子控制器无法滚动到顶部。 - klcjr89
我发现了一些有趣的事情,我发现一个UICollectionView作为子视图出现了?通过禁用它,我的表格视图现在可以滚动到顶部了!太棒了! - klcjr89
如果它引起了更多的问题,那么你应该这样做。但我并不认为问题在于页面视图控制器。 - Léo Natan
1
@Roma-MT 不行,因为苹果不会公开他们的scrollview。你应该提交一个功能增强请求,以便在未来的UIKit版本中添加它。 - Léo Natan
1
@Mischa 编辑过了。看看是否更好。随意进一步编辑以使其更好。 - Léo Natan
显示剩余10条评论

3
iOS 7文档中的-scrollToTop说明如下:
在iPhone上,如果有一个以上的滚动视图设置了滚动到顶部为YES,则返回顶部手势无效。
我正在类似的方式使用UIPageViewController;我的一个页面控制器是UITableViewController。因此,我受到two-scrollviews-onscreen-no-scrolltotop策略的影响。
Kyle的建议是通过一个不可见按钮来实现自己的scrollToTop行为,这可能是在此情况下最可靠的选择。

我实际上放弃了页面视图控制器,改用UIScrollView,它比页面视图控制器更好用,并提供了更多的选项。 - klcjr89

1
我使用了这个:

for v in view.subviews {
    if v.isKindOfClass(UIScrollView) {
        (v as! UIScrollView).scrollsToTop = false
    }
}

但是如果仍有其他可以滚动到顶部的滚动视图,则您的滚动视图将无法正常工作。使用此脚本查找其他滚动视图:

https://dev59.com/QGoy5IYBdhLWcg3wdt8L#33443757,然后禁用它们的滚动到顶部。

然后在您想要使滚动到顶部正常工作的视图控制器中添加以下内容:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    tableView.scrollsToTop = true
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    tableView.scrollsToTop = false
}

1
在我的情况下,我在UIPageViewController的子视图控制器中有一个UITableView。根据Leo Natan的建议,将以下内容添加到我的子视图控制器中即可解决问题:
- (void)viewWillAppear:(BOOL)animated 
{
    [super viewWillAppear:animated];
    self.tableView.scrollsToTop = YES; 
}

- (void)viewWillDisappear:(BOOL)animated 
{
    [super viewWillDisappear:animated];
    self.tableView.scrollsToTop = NO; 
}

正如提到的那样,在子视图控制器中竞争的UIScrollView会相互干扰它们的scrollsToTop委托方法。该代码确保一次只有一个滚动视图具有启用了scrollsToTop。

0

我认为最好的解决方案是使用UIPageViewControllerDelegate协议的以下两种方法(快速且简单的版本,考虑到屏幕上只有一个视图控制器):

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
    ((UITableViewController *)pendingViewControllers[0].tableView).scrollsToTop = YES;
}

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    ((UITableViewController *)previousViewControllers[0].tableView).scrollsToTop = NO;
}

编辑:最好使用已接受答案中最后一段概述的方法


1
有点是,但要声明“这个解决方案是否最优?不是。是否容易出错?理论上是的。”所以我想分享另一种方法。别管它了。 - ernesto
这应该是被接受的答案。这是最干净的解决方案,一点也不脆弱或者像黑客一样。请注意,在访问其tableView之前,您需要将这些行拆分为两个部分 - 将其分配给表视图控制器,因为否则它仍然是id,没有tableView属性。此外,您可能希望在分配之前进行一些内省。还要注意,这对于UICollectionViewController同样适用 - 将tableView更改为collectionView。太棒了,谢谢! - Jordan H
如果你看一下另一个答案的聊天记录,你会发现我们最终找到了问题并解决了它。这种方式很危险,而且不能涵盖从一个视图控制器到另一个视图控制器的所有可能转换。最好在视图控制器本身的viewWillAppearviewWillDisappear方法中实现。 - Léo Natan
1
@LeoNatan是正确的,他所接受的方法(在viewWillAppear中将其设置为YES,在viewWillDisappear中将其设置为NO)更好。 - ernesto

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