从UIPageViewController中删除视图控制器

Sno*_*man 45 iphone cocoa-touch objective-c ios

奇怪的是,没有直接的方法来做到这一点.请考虑以下情形:

  1. 您有一个页面视图控制器有1页.
  2. 添加另一页(共2页)并滚动到它.
  3. 我想要的是,当用户滚动回第一页时,第二页现在被删除并取消分配,用户再也无法刷回该页面.

我已经尝试在转换完成后将视图控制器删除为子视图控制器,但它仍然允许我滚动回空页面(它不会"调整大小"页面视图)

我想做什么?

Mat*_* Mc 123

虽然这里的答案都是提供信息的,但还有另一种处理问题的方法,这里给出:

UIPageViewController使用Scroll过渡样式导航到错误的页面

当我第一次寻找这个问题的答案时,我在搜索中的措辞让我对这个问题感到伤心,而不是我刚刚联系到的问题,所以我觉得有义务发一个链接到另一个问题的答案,现在我已经找到了它,并且还详细说明了一下.

The problem is described pretty well by matt here:

This is actually a bug in UIPageViewController. It occurs only with the scroll style (UIPageViewControllerTransitionStyleScroll) and only after calling setViewControllers:direction:animated:completion: with animated:YES. Thus there are two workarounds:

Don't use UIPageViewControllerTransitionStyleScroll.

Or, if you call setViewControllers:direction:animated:completion:, use only animated:NO.

To see the bug clearly, call setViewControllers:direction:animated:completion: and then, in the interface (as user), navigate left (back) to the preceding page manually. You will navigate back to the wrong page: not the preceding page at all, but the page you were on when setViewControllers:direction:animated:completion: was called.

The reason for the bug appears to be that, when using the scroll style, UIPageViewController does some sort of internal caching. Thus, after the call to setViewControllers:direction:animated:completion:, it fails to clear its internal cache. It thinks it knows what the preceding page is. Thus, when the user navigates leftward to the preceding page, UIPageViewController fails to call the dataSource method pageViewController:viewControllerBeforeViewController:, or calls it with the wrong current view controller.

This is a good description, not quite the problem noted in this question but very close. Note the line about if you do setViewControllers with animated:NO you will force the UIPageViewController to re-query its data source next time the user pans with a gesture, as it no longer "knows where it is" or what view controllers are next to its current view controller.

However, this didn't work for me because there were times when I need to programmatically move the PageView around with an animation.

So, my first thought was to call setViewControllers with an animation, and then in the completion block call the method again with whatever view controller was now showing, but with no animation. So the user can pan, fine, but then we call the method again to get the page view to reset.

不幸的是,当我尝试时,我开始从页面视图控制器中获得奇怪的"断言错误".他们看起来像这样:

***断言失败 - [UIPageViewController queuingScrollView:...

不知道为什么会发生这种情况,我回过头来并最终开始使用Jai的答案作为解决方案,创建一个全新的UIPageViewController,将其推送到UINavigationController,然后弹出旧的.毛,但它的确有效 - 主要是.我一直在发现我仍然会从UIPageViewController中偶尔听到Assertion Failures,就像这样:

***Assertion failure in -[UIPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit_Sim/UIKit-2380.17/UIPageViewController.m:1820 $1 = 154507824 No view controller managing visible view >

And the app crashes. Why? Well, searching, I found this other question that I mentioned up top, and particularly the accepted answer which advocates my original idea, of simply calling setViewControllers: animated:YES and then as soon as it completes calling setViewControllers: animated:NO with the same view controllers to reset the UIPageViewController, but it had the missing element: calling that code back on the main queue! Here's the code:

__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
                });
            }
        }];
Run Code Online (Sandbox Code Playgroud)

哇!这对我来说真正有意义的唯一原因是因为我看过WWDC 2012 Session 211,在iOS上构建并发用户界面(这里有一个开发帐户).我现在回想一下,尝试修改UIKit对象(如UIPageViewController)所依赖的数据源对象,并在辅助队列上执行它可能会导致一些令人讨厌的崩溃.

What I have never seen particularly documented, but must now assume to be the case and read up on, is that the completion block for an animation is performed on a secondary queue, not the main one. So the reason why UIPageViewController was squawking and giving assertion failures, both when I originally attempted to call setViewControllers animated:NO in the completion block of setViewControllers animated:YES and also now that I am simply using a UINavigationController to push on a new UIPageViewController (but doing it, again, in the completion block of setViewControllers animated:YES) is because it's all happening on that secondary queue.

That's why that piece of code up there works perfectly, because you come from the animation completion block and send it back over to the main queue so you don't cross the streams with UIKit. Brilliant.

无论如何,想要分享这个旅程,万一有人遇到这个问题.

编辑:Swift版本在这里,如果有人有兴趣.


Luk*_*ski 9

总结Matt Mc的答案很好,下面的方法可以添加到UIPageViewController的子类中,这样就可以使用setViewControllers:direction:animated:completion:因为如果bug不存在就打算使用它.

- (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);
            }
        }
    }];
}
Run Code Online (Sandbox Code Playgroud)

现在,只需调用setViewControllers:direction:animated:completion:在实现此方法的类/子类上,它应该按预期工作.


bil*_*tum 6

maq是对的.如果您正在使用滚动转换,则从UIPageViewController中删除子视图控制器不会阻止已删除的"页面"在用户导航到屏幕时返回.如果您有兴趣,这是我从UIPageViewController中删除子视图控制器的方法.

// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController]; 
Run Code Online (Sandbox Code Playgroud)

视图控制器deleteVC将从UIPageViewController的childViewControllers属性中删除,但如果用户导航到该屏幕,它仍会显示在屏幕上.

直到比我聪明的人找到一个优雅的解决方案,这是一个解决方法(这是一个黑客 - 所以你必须问自己,你真的需要从UIPageViewController中删除页面).

这些说明假定一次只显示一个页面.

用户点击指示要删除页面的按钮后,使用setViewControllers:direction:animated:completion:方法导航到下一页或上一页.当然,您需要从数据模型中删除页面内容.

接下来(这里是hack),创建并配置一个全新的UIPageViewController并将其加载到前台(即,在另一个UIPageViewController前面).确保新的UIPageViewController开始显示先前显示的完全相同的页面.您的新UIPageViewController将从数据源中获取新的视图控制器.

最后,卸载并销毁后台的UIPageViewController.

无论如何,maq问了一个非常好的问题.不幸的是,我没有足够的声望点来投票.啊,敢于梦想...总有一天我会有15点声望.


Jai*_*ani 5

我将这个答案放在这里仅仅是为了我自己的未来参考,如果它对任何人有帮助 - 我最终做的是:

  1. 删除页面并前进到下一页
  2. 在完成块中setViewControllers,我创建/初始化了一个带有修改数据的新UIPageViewController(删除了项目),并在没有动画的情况下推送它,因此屏幕上没有任何变化(我的UIPageViewController包含在UINavigationController中)
  3. 在推送新的UIPageViewController之后,获取viewControllersUINavigationController数组的副本,删除倒数第二个视图控制器(这是旧的UIPageViewController)
  4. 没有第4步 - 完成!