如何检测UIPageViewController正在滚动(用于图像的视差滚动)

31

我试图实现类似于新的Yahoo天气应用程序中所发现的效果。基本上,UIPageViewController中的每个页面都具有背景图像,并且在滚动页面视图时,图像的位置仅滚动约一半的速度。我该怎么做呢? 我想我可以在UIPageViewController中使用某种委托方法来获取当前偏移量,然后像那样更新图像。唯一的问题是,我找不到任何方法来判断UIPageViewController是否正在滚动!有这方面的方法吗?谢谢!

8个回答

51
for (UIView *view in self.pageViewController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]]) {
         [(UIScrollView *)view setDelegate:self];
    }
} 

这将为您提供所有标准滚动视图API方法的访问权限。 并且不使用私有的Apple API。

我添加了遍历子视图的功能,以完全找到UIPageViewController内部滚动视图 警告: 要小心使用scrollview.contentOffset。当控制器滚动到新页面时,它会重置。

如果您需要精确的scrollview偏移跟踪等功能,最好使用UICollectionViewController,其中单元格大小与集合视图本身相同,并启用分页。


10
如果苹果更改了其页面视图控制器的架构,你就完蛋了。 - Van Du Tran
1
@VanDuTran同意,但在那之前,一切都很好,似乎这是最好的方法。我看不到其他选择。 - Pavel Gurov
3
对我来说,这不是第一个子视图,而是第二个。这段代码完成了这个操作:if([self.pageVC.view.subviews[1] respondsToSelector:@selector(setDelegate:)]){ [self.pageVC.view.subviews[1] setDelegate:self]; } - Whoa
8
问题在于它不能为scrollview.contentOffset提供标准结果。它在每个新页面时都会重置。 - Nick ONeill
4
这不仅涉及到苹果公司所做的更改,还有你指派了新的代理人,因此如果UIPageViewController的内部实现使用它,你将会破坏它。 - Jakub Truhlář
显示剩余4条评论

15

我会这样做:

Objective-C

for (UIView *v in self.pageViewController.view.subviews) {
    if ([v isKindOfClass:[UIScrollView class]]) {
        ((UIScrollView *)v).delegate = self;
    }
}

并实现该协议

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

Swift

for view in self.pageViewController.view.subviews {
  if let scrollView = view as? UIScrollView {
    scrollView.delegate = self
  }
}

并且实现这个协议

func scrollViewDidScroll(scrollView: UIScrollView)

感谢您提供的 Swift 代码片段。 - Axe

4
extension UIPageViewController {

    var scrollView: UIScrollView? {

        return view.subviews.filter { $0 is UIScrollView }.first as? UIScrollView
    }
}

使用:

pageController.scrollView?.delegate = self

3
请问需要翻译的内容是什么?
for (UIView *v in self.pageViewController.view.subviews) {
if ([v isKindOfClass:[UIScrollView class]]) {
    ((UIScrollView *)v).delegate = self;
}
}

为了实现这个协议:-(void)scrollViewDidScroll:(UIScrollView *)scrollView
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint point = scrollView.contentOffset;

float percentComplete;
percentComplete = fabs(point.x - self.view.frame.size.width)/self.view.frame.size.width;
NSLog(@"percentComplete: %f", percentComplete);
}

这将为您提供滚动完成百分比。编码愉快!

3
在Swift 3中,你甚至可以写得更短:
if let scrollView = self.pageViewController.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
    scrollView.delegate = self
}

3
我猜它不是UIPageViewController,而是分页的UIScrollView。UIScrollView会不断重复地调用代理方法来跟踪滚动时发生的情况。或者你也许可以访问UIPageViewController秘密使用的分页UIScrollView,但可能会破坏某些东西,而且我不确定苹果公司会有何感想。

1
有一种方法可以使用UIPageViewController来实现这个功能。如果我使用它,一切都会变得简单。Yahoo应用程序似乎也使用了UIPageViewController。是否有办法从UIViewController内部获取屏幕上的位置? - Keiran Paster
1
UIPageViewController基本上不就是一个花哨的UIScrollView吗? - Keiran Paster
这对你来说可能更简单,但我向你保证,仅使用UIScrollView,我们就能在滚动式UIPageViewController出现之前实现类似的滚动式UI。有整个WWDC视频专门讲解如何实现它。 - matt
它看起来很花哨但不透明。就像我说的那样,你可以沿着视图层次结构往下查找滚动视图。但是你可能不应该这么做。 - matt
有没有办法让滚动视图置于顶部,只从中读取数据? - Keiran Paster

1
你要寻找的是称为视差滚动的功能,你可以找到几个库来帮助你实现它。
编辑:Matt是对的,这不是一个答案,只是一个提示。无论如何,让我们完成它:
要为 UIPageViewController 后面的背景图片添加动画效果,你应该使用它提供的委托方法。
-[id<UIPageViewControllerDelegate> pageViewController:willTransitionToViewControllers:]
-[id<UIPageViewControllerDelegate> pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:]

使用这两种方法,您可以计算滚动的百分比(您应该将您的控制器存储在数组中,以便知道您滚动到哪个控制器并获取百分比)。


2
你要如何从那个数值中获取百分比? - Keiran Paster
1
你应该使用 -[NSArray indexOfObject:] 获取搜索到的视图控制器的位置,然后进行一些基本的数学计算,以获取该索引相对于 -[NSArray count] 的百分比。 - Antonio E.
1
那并不是我想要的。每个视图控制器都有自己的UIImage。我需要逐像素地控制图像的位置,而使用百分比就行不通了。 - Keiran Paster
也许你可以查看 -[UIPageViewController gestureRecognizers] 获取一个 UIPanGestureRecognizer 并使用它的方法来准确(且合法)地知道当前的移动情况。 - Antonio E.

0

你不应该更改页面视图控制器滚动视图的委托:这可能会破坏其正常行为和/或在以后不受支持。

相反,你可以:

  1. 在页面视图控制器的视图中添加一个平移手势:

    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panRecognized(gesture:)))
    view.addGestureRecognizer(panGesture)
    panGesture.delegate = self
    
  2. 添加新函数以了解视图如何滚动。

    @objc func panRecognized(gesture: UIPanGestureRecognizer) {
        // Do whatever you need with the gesture.translation(in: view)
    }
    
  3. 将您的ViewController声明为UIGestureRecognizerDelegate

  4. 实现此函数:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    

如果页面视图控制器被推到导航栈上,这会导致向后滑动出现问题(向后滑动将与页面视图控制器中的水平滑动同时被识别)。 - Samuël

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