奇怪的是,没有直接的方法可以做到这一点。考虑以下场景:
- 您有一个带有 1 页的页面视图控制器。
- 添加另一个页面(共 2 个)并滚动到它。
- 我想要的是,当用户滚动回第一页时,第二页现在被删除并释放,用户不能再滑回该页面。
转换完成后,我尝试将视图控制器作为子视图控制器删除,但它仍然让我滚动回空白页面(它不会“调整”页面视图的大小)
我想做的事可能吗?
奇怪的是,没有直接的方法可以做到这一点。考虑以下场景:
转换完成后,我尝试将视图控制器作为子视图控制器删除,但它仍然让我滚动回空白页面(它不会“调整”页面视图的大小)
我想做的事可能吗?
虽然这里的答案都提供了丰富的信息,但还有另一种处理问题的方法,如下所示:
UIPageViewController 使用滚动过渡样式导航到错误页面
当我第一次搜索这个问题的答案时,我的搜索方式让我陷入了这个问题,而不是我刚刚链接到的那个问题,所以我觉得有义务发布一个链接到另一个问题的答案,既然我已经找到了,也详细说明了一点。
马特在这里很好地描述了这个问题:
这实际上是 UIPageViewController 中的一个错误。它仅在滚动样式 (UIPageViewControllerTransitionStyleScroll) 中发生并且仅在使用动画:YES 调用 setViewControllers:direction:animated:completion: 之后发生。因此有两种解决方法:
不要使用 UIPageViewControllerTransitionStyleScroll。
或者,如果您调用 setViewControllers:direction:animated:completion:,则仅使用动画:NO。
要清楚地查看错误,请调用 setViewControllers:direction:animated:completion:,然后在界面中(作为用户)手动向左(返回)导航到前一页。您将导航回错误的页面:根本不是前一页,而是调用 setViewControllers:direction:animated:completion: 时所在的页面。
该错误的原因似乎是,当使用滚动样式时, UIPageViewController 会进行某种内部缓存。因此,在调用 setViewControllers:direction:animated:completion: 之后,它无法清除其内部缓存。它认为它知道前一页是什么。因此,当用户向左导航到上一页时,UIPageViewController 调用 dataSource 方法 pageViewController:viewControllerBeforeViewController: 失败,或者调用了错误的当前视图控制器。
这是一个很好的描述,不是这个问题中提到的问题,但非常接近。请注意,如果您这样做setViewControllers
,animated:NO
将强制 UIPageViewController 在下次用户使用手势进行平移时重新查询其数据源,因为它不再“知道它在哪里”或哪些视图控制器在其当前视图控制器旁边.
但是,这对我不起作用,因为有时我需要使用动画以编程方式移动 PageView。
所以,我的第一个想法是调用setViewControllers
动画,然后在完成块中再次调用该方法,使用现在显示的任何视图控制器,但没有动画。所以用户可以平移,很好,但是我们再次调用该方法来重置页面视图。
不幸的是,当我尝试时,我开始从页面视图控制器中收到奇怪的“断言错误”。它们看起来像这样:
*** 断言失败 -[UIPageViewControllerqueuingScrollView: ...
不知道为什么会发生这种情况,我回溯并最终开始使用Jai 的答案作为解决方案,创建一个全新的 UIPageViewController,将其推送到 UINavigationController,然后弹出旧的。恶心,但它有效 - 主要是。我一直发现我仍然偶尔会从 UIPageViewController 收到断言失败,比如这个:
*** 断言失败 -[UIPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit_Sim/UIKit-2380.17/UIPageViewController.m:1820 $1 = 154507824 没有视图控制器管理可见视图 >
应用程序崩溃。为什么?好吧,搜索,我发现了我在上面提到的另一个问题,特别是提倡我最初想法的公认答案setViewControllers: animated:YES
,即简单地调用,然后在完成调用后立即setViewControllers: animated:NO
使用相同的视图控制器重置 UIPageViewController,但它有缺少的元素:在主队列中调用该代码!这是代码:
__weak YourSelfClass *blocksafeSelf = self;
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished){
if(finished)
{
dispatch_async(dispatch_get_main_queue(), ^{
[blocksafeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];// bug fix for uipageview controller
});
}
}];
哇!这对我来说真正有意义的唯一原因是因为我观看了 WWDC 2012 Session 211,在 iOS 上构建并发用户界面(可在此处使用开发帐户获得)。我现在回想一下,尝试修改 UIKit 对象(如 UIPageViewController)所依赖的数据源对象,并在辅助队列上进行,可能会导致一些令人讨厌的崩溃。
我从未见过特别记录的,但现在必须假设是这种情况并继续阅读的是,动画的完成块是在辅助队列上执行的,而不是在主队列上执行的。因此,当我最初尝试调用setViewControllers animated:NO
完成块时,setViewControllers animated:YES
以及现在我只是使用 UINavigationController 推送新的 UIPageViewController 时,UIPageViewController 发出尖叫并给出断言失败的原因(但再次在完成中执行它) ) 的块setViewControllers animated:YES
是因为这一切都发生在那个辅助队列上。
这就是为什么上面的那段代码可以完美运行的原因,因为您来自动画完成块并将其发送回主队列,这样您就不会与 UIKit 交叉流。杰出的。
无论如何,想分享这个旅程,以防有人遇到这个问题。
编辑:这里的Swift 版本,如果有人感兴趣的话。
总结 Matt Mc 的出色回答,可以将以下方法添加到 UIPageViewController 的子类中,这样就可以使用 setViewControllers:direction:animated:completion:,因为它的目的是在不存在错误的情况下使用。
- (void) setViewControllers:(NSArray*)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL))completion {
if (!animated) {
[super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
return;
}
[super setViewControllers:viewControllers direction:direction animated:YES completion:^(BOOL finished){
if (finished) {
dispatch_async(dispatch_get_main_queue(), ^{
[super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
});
} else {
if (completion != NULL) {
completion(finished);
}
}
}];
}
现在,只需在实现此方法的类/子类上调用 setViewControllers:direction:animated:completion:,它就会按预期工作。
马克是对的。如果您正在使用滚动转换,则从 UIPageViewController 中删除子视图控制器不会阻止已删除的“页面”在用户导航到它时返回到屏幕上。如果您有兴趣,以下是我从 UIPageViewController 中删除子视图控制器的方法。
// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController];
视图控制器 deleteVC 已从 UIPageViewController 的 childViewControllers 属性中删除,但如果用户导航到它,它仍会显示在屏幕上。
在比我聪明的人找到一个优雅的解决方案之前,这里有一个解决方法(这是一个 hack——所以你必须问自己是否真的需要从 UIPageViewController 中删除页面)。
这些说明假定一次只显示一页。
在用户点击一个表示她想要删除页面的按钮后,使用 setViewControllers:direction:animated:completion: 方法导航到下一页或上一页。当然,您随后需要从数据模型中删除页面的内容。
接下来(这里是 hack),创建和配置一个全新的 UIPageViewController 并将其加载到前台(即,在另一个 UIPageViewController 的前面)。确保新的 UIPageViewController 开始显示与之前显示的完全相同的页面。您的新 UIPageViewController 将从数据源中获取新的视图控制器。
最后,卸载并销毁后台的 UIPageViewController。
无论如何,maq 提出了一个非常好的问题。不幸的是,我没有足够的声望点来支持这个问题。啊,敢做梦……总有一天我会有15点声望。
我将把这个答案放在这里只是为了我自己将来的参考,如果它对任何人有帮助 - 我最终做的是:
setViewControllers
,我使用修改后的数据(已删除项目)创建/初始化了一个新的 UIPageViewController,并在没有动画的情况下推送它,因此屏幕上没有任何变化(我的 UIPageViewController 包含在 UINavigationController 中)viewControllers
,移除倒数第二个视图控制器(也就是旧的 UIPageViewController)我自己只是在学习这一点,所以持保留态度,但据我了解,您需要更改 pageviewcontroller 的数据源,而不是删除 viewcontroller。pageviewcontroller 中显示的页面数量取决于其数据源,而不是 viewcontrollers。
为了改善这一点。您应该在 setViewControllers 之前检测 pageView 是否正在滚动。
var isScrolling = false
func viewDidLoad() {
...
for v in view.subviews{
if v.isKindOfClass(UIScrollView) {
(v as! UIScrollView).delegate = self
}
}
}
func scrollViewWillBeginDragging(scrollView: UIScrollView){
isScrolling = true
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView){
isScrolling = false
}
func jumpToVC{
if isScrolling { //you should not jump out when scrolling
return
}
setViewControllers([vc], direction:direction, animated:true, completion:{[unowned self] (succ) -> Void in
if succ {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.setViewControllers([vc], direction:direction, animated:false, completion:nil)
})
}
})
}
当您在视图控制器之间滑动手势动画期间尝试更改视图控制器时,会出现此问题。为了解决这个问题,我做了一个简单的标志来发现页面视图控制器何时在转换。
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
self.pageViewControllerTransitionInProgress = YES;
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
self.pageViewControllerTransitionInProgress = NO;
}
当我试图改变视图控制器时,我正在检查是否正在进行转换。
- (void)setCurrentPage:(NSString *)newCurrentPageId animated:(BOOL)animated {
if (self.pageViewControllerTransitionInProgress) {
return;
}
[self.pageContentViewController setViewControllers:@[pageDetails] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
}
}
其实我想我解决了这个问题。这是我的应用程序中发生的事情:
我有类似的情况,我希望用户能够从 UIPageViewController 中“点击并删除”任何页面。在玩了一会儿之后,我找到了一个比上面描述的更简单的解决方案:
临时跳到一个“安全”的地方(最好是最后一个)。使用动画:NO 让这种情况立即发生。
UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
从模型/数据源中删除选定的页面。
现在,如果它是最后一页:
跳到它,这次是动画:是的。
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
在它不是最后一页的情况下:
跳到它,这次是动画:是的。
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex;
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
就是这样!这在 XCode 6.1.1 中运行良好。
完整代码如下。此代码在我的 UIPageViewController 中,并通过要删除的页面中的委托调用。就我而言,不允许删除第一页,因为它包含与其余页面不同的内容。当然,你需要替换:
[YourModel deletePage:] 使用代码从模型中删除垂死页面
- (void)deleteAViewController:(id)sender {
YourUIViewController *dyingGroup = (YourUIViewController *)sender;
NSUInteger dyingPageIndex = dyingGroup.pageIndex;
// Check to see if we are in the last page as it's a special case.
BOOL isTheLastPage = (dyingPageIndex >= YourTotalNumberOfPagesFromModel.count);
// Make a temporary jump back - make sure to use animated:NO to have it jump instantly
UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
// Now delete the selected group from the model, setting the target
[YourModel deletePage:dyingPageIndex];
if (isTheLastPage) {
// Now jump to the definitive controller. In this case, it's the same one, we're just reloading it to refresh the data source.
// This time we're using animated:YES
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
} else {
// Now jump to the definitive controller. This reloads the data source. This time we're using animated:YES
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}
NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:self.userArray];
[mArray removeObject:userToBlock];
self.userArray = mArray;
UIViewController *startingViewController = [self viewControllerAtIndex:atIndex-1];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
我没有时间阅读上述评论,但这对我有用。基本上我删除数据(在我的情况下是用户),然后移动到它之前的页面。奇迹般有效。希望这可以帮助那些寻求快速修复的人。
它在 Swift 3.0 中完成了以下操作
fileprivate var isAnimated:Bool = false
override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
if self.isAnimated {
delay(0.5, closure: {
self.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
})
}else {
super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
}
}
extension SliderViewController:UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
self.isAnimated = true
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
self.isAnimated = finished
}
}
这里是延迟函数
func delay(_ delay:Double, closure:@escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
我们在标签更改时崩溃了。所以我得到的一个解决方案是标签的 didChangeSelect 禁用了用户吸引力。