从UIPageViewController中移除一个视图控制器
很奇怪,没有简单的方法来做到这一点。 考虑以下情况:
- 你有一页页面视图控制器。
- 添加另一个页面(共2)并滚动到它。
- 我想要的是,当用户滚动回到第一页时,第二页现在被移除和释放,并且用户不能再滑回到该页面。
我已经尝试过渡完成后删除视图控制器作为子视图控制器,但它仍然让我滚动回空页面(它不“调整”页面视图)
我想做什么?
虽然这里的答案都是信息性的,但还有一个处理这个问题的替代方法,在这里给出:
UIPageViewController导航到滚动转换样式的错误页面
当我第一次search这个问题的答案的时候,我的search方式让我对这个问题感到困惑,而不是我刚刚链接的那个,所以我觉得有义务发表一个与这个问题相关的答案,现在我已经find了,并且稍微详细一点。
这个问题在这里用亚光描述得非常好:
这实际上是UIPageViewController中的一个错误。 它只发生在滚动样式(UIPageViewControllerTransitionStyleScroll)中,并且只有在调用setViewControllers之后才会出现:direction:animated:completion:with animated:YES。 因此有两个解决方法:
不要使用UIPageViewControllerTransitionStyleScroll。
或者,如果您调用setViewControllers:direction:animated:completion:,则只使用animation:NO。
为了清楚地看到bug,调用setViewControllers:direction:animated:completion:然后,在界面(以用户身份)中,手动向左(后退)导航到前一页。 您将导航回错误的页面:根本不是前面的页面,而是在setViewControllers:direction:animated:completion:被调用时所在的页面。
这个错误的原因似乎是,当使用滚动样式时,UIPageViewController会执行某种内部caching。 因此,在调用setViewControllers:direction:animated:completion:之后,它无法清除其内部caching。 它认为它知道前一页是什么。 因此,当用户向左导航到前一页时,UIPageViewController无法调用dataSource方法pageViewController:viewControllerBeforeViewController:,或者使用错误的当前视图控制器调用它。
这是一个很好的描述,不是这个问题中提到的问题,而是非常接近的问题。 注意,如果你使用animated:NO
setViewControllers
animated:NO
你会强制UIPageViewController重新查询其数据源,下一次用户平息手势,因为它不再“知道它在哪里”或什么视图控制器旁边它当前的视图控制器。
然而,这不适合我,因为有时候我需要以animation的方式编程地移动PageView。
所以,我的第一个想法是用animation调用setViewControllers
,然后在完成块中再次调用该方法与任何视图控制器现在显示,但没有animation。 所以用户可以平移,很好,但是我们再次调用方法来获取页面视图来重置。
不幸的是,当我尝试,我开始从页面视图控制器奇怪的“断言错误”。 他们看起来像这样:
***声明失败 – [UIPageViewController queuingScrollView:…
我不知道到底为什么会发生这种情况,于是我回过头来,最终开始使用Jai的答案作为解决scheme,创build一个全新的UIPageViewController,将其推送到UINavigationController上,然后popup旧的。 毛,但它的作品 – 主要是。 我一直在发现我仍然偶尔从UIPageViewController断言失败,就像这样:
***断言失败 – [UIPageViewController queuingScrollView:didEndManualScroll:toRevealView:方向:animation:didFinish:didComplete:],/SourceCache/UIKit_Sim/UIKit-2380.17/UIPageViewController.m:1820 $ 1 = 154507824没有视图控制器pipe理可见视图>
而应用程序崩溃。 为什么? 那么search,我发现了我提到的另一个问题 ,特别是提出我最初想法的接受的答案 ,简单地调用setViewControllers: animated:YES
,然后一旦完成调用setViewControllers: animated:NO
查看控制器重置UIPageViewController,但它有缺less的元素:调用该代码回主队列! 代码如下:
__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会议211,在iOS上构build并发用户界面( 这里提供了一个开发账户)。 我记得现在试图修改UIKit对象(如UIPageViewController)所依赖的数据源对象,并在辅助队列上进行操作可能会导致一些令人讨厌的崩溃。
我从来没有见过特别logging,但现在必须假设是这样的情况,并阅读,是一个animation完成块是在一个二级队列,而不是主要的。 所以,UIPageViewController之所以吵闹,是因为当我最初试图调用setViewControllers animated:NO
在setViewControllers animated:YES
的完成块中,现在我只是简单地使用UINavigationController来推新的UIPageViewController(但是再次,在setViewControllers animated:YES
)的完成块中, setViewControllers animated:YES
是因为它都发生在该辅助队列上。
这就是为什么那段代码完美地工作,因为你来自animation完成块,并将其发送回主队列,所以你不会通过UIKit跨越stream。 辉煌。
无论如何,要分享这个旅程,以防万一遇到这个问题。
编辑: 这里的 Swift版本,如果任何人有兴趣。
总结马特Mc的伟大的答案,下面的方法可以被添加到UIPageViewController的子类,这样的方式允许使用setViewControllers:方向:animation:完成:因为它打算如果错误不会出现使用。
- (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:就可以按预期工作。
maq是对的。 如果您正在使用滚动过渡,从UIPageViewController中删除子视图控制器不会阻止删除的“页面”返回到屏幕上,如果用户导航到它。 如果你有兴趣,这是我如何从UIPageViewController中删除子视图控制器。
// deleteVC is a child view controller of the UIPageViewController [deleteVC willMoveToParentViewController:nil]; [deleteVC.view removeFromSuperview]; [deleteVC removeFromParentViewController];
视图控制器deleteVC从UIPageViewController的childViewControllers属性中移除,但如果用户导航到它,仍然显示在屏幕上。
直到比我聪明的人find一个优雅的解决scheme,这是一个解决方法(这是一个黑客 – 所以你必须问自己,如果你真的需要从UIPageViewController删除页面)。
这些说明假定一次只显示一个页面。
用户点击表示要删除页面的button后,使用setViewControllers:direction:animated:completion:方法导航到下一个或上一个页面。 当然,您需要从数据模型中删除页面的内容。
接下来(这里是黑客攻击),创build并configuration一个全新的UIPageViewController并将其加载到前台(即在另一个UIPageViewController的前面)。 确保新的UIPageViewController开始显示与之前显示的完全相同的页面。 新的UIPageViewController将从数据源中获取新的视图控制器。
最后,卸载并销毁在后台的UIPageViewController。
无论如何,马克问一个非常好的问题。 不幸的是,我没有足够的信誉点来投票。 啊,敢于梦想…总有一天我会有15个声望点。
我将这个答案放在这里仅仅是为了我自己的未来,如果它帮助任何人 – 我最终做的是:
- 删除页面并进入下一页
- 在
setViewControllers
的完成块中,我创build/初始化一个新的UIPageViewController与修改的数据(项目删除),并推动它没有animation,所以没有任何改变屏幕上(我的UIPageViewController是包含在一个UINavigationController) - 推入新的UIPageViewController后,获取UINavigationController的
viewControllers
数组副本,删除倒数第二个视图控制器(这是旧的UIPageViewController) - 没有第四步 – 完成!
为了改善这一点。 你应该在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) }) } }) }
当您尝试在viewControllers之间的滑动手势animation期间更改viewControllers时存在此问题。 为了解决这个问题,我做了一个简单的标志来发现页面视图控制器在转换过程中。
- (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) { } }
我只是自己学习这个,所以拿一点盐,但从我的理解,你需要改变pageviewcontroller的数据源,而不是删除viewcontroller。 在pageviewcontroller中显示多less页面是由其数据源而不是视图控制器决定的。
其实我想我解决了这个问题。 这是我的应用程序中发生的事情:
- 通过从UIPageViewController中移除UIViewController子类实例,将其从模型中移除,并将UIPageViewController的viewControllers属性设置为不同的ViewController。 这足以完成这项工作。 不需要在该控制器上执行任何控制器控制代码
- ViewController已经不在了,但是我仍然可以通过向右滑动来查看它。
- 我的问题是这个。 我正在向UIPageViewController显示的ViewController添加一个自定义手势识别器。 这个识别器在拥有UIPageViewController的控制器上是一个强大的属性。
- 修复: 放弃访问ViewController被解雇之前,我确保我正确地清理了它使用的所有内存(dealloc),并删除了手势识别器
- 你的里程可能会有所不同,但我认为这个解决scheme是错误的,当有些东西不能工作时,我首先怀疑我的代码:)
我有类似的情况,我希望用户能够从UIPageViewController“点击和删除”任何页面。 玩了一下之后,我find了比上面描述的更简单的解决scheme:
- 捕获“垂死的页面”的页面索引。
- 检查“垂死的页面”是否是最后一页。
- 这很重要,因为如果它是最后一页,我们需要向左滚动(如果我们有页面“ABC”和删除C,我们将滚动到B)。
- 如果不是最后一页,我们将向右滚动(如果我们有页面“ABC”和删除B,我们将滚动到C)。
-
暂时跳到一个“安全”的地方(理想的是最后一个)。 使用animation:NO来让这个瞬间发生。
UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1]; [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
-
从模型/数据源中删除选定的页面。
-
现在,如果这是最后一页:
- 调整您的模型以select左侧的页面。
- 获取左侧的视图控制器,注意这与步骤3中检索到的相同。
- 在从数据源中删除页面之后,执行此操作非常重要,因为它会刷新它。
-
跳转到这个animation:YES。
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1]; [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
-
在不是最后一页的情况下:
- 调整模型以select右侧的页面。
- 得到右侧的viewcontroller,请注意,这不是在第3步中检索到的。在我的情况中,您将看到它是dyingPageIndex中的一个,因为垂死的页面已经从模型中删除。
- 同样,在从数据源删除页面之后执行此操作也很重要,因为它会刷新页面。
-
跳转到这个animation:YES。
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex; [self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
-
而已! 这在XCode 6.1.1中运行良好。
下面的完整代码。 这段代码位于我的UIPageViewController中,并通过页面中的委托来调用。 在我的情况下,删除第一页是不允许的,因为它包含与其余页面不同的东西。 当然,你需要replace:
- YourUIViewController:与您的个人网页的类。
- YourTotalNumberOfPagesFromModel:包含模型中的页面总数
-
[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 } }
而这里的延迟function
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) }