ios 从 UIPageViewController 中删除视图控制器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14220289/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:46:20  来源:igfitidea点击:

Removing a view controller from UIPageViewController

iphoneobjective-cioscocoa-touch

提问by Snowman

It's odd that there's no straightforward way to do this. Consider the following scenario:

奇怪的是,没有直接的方法可以做到这一点。考虑以下场景:

  1. You have a page view controller with 1 page.
  2. Add another page (total 2) and scroll to it.
  3. What I want is,when the user scrolls back to the first page, the 2nd page is now removed and deallocated, and the user can no longer swipe back to that page.
  1. 您有一个包含 1 页的页面视图控制器。
  2. 添加另一个页面(共 2 个)并滚动到它。
  3. 我想要的是,当用户滚动回第一页时,第二页现在被删除并解除分配,用户无法再滑动回该页面。

I've tried removing the view controller as a child view controller after the transition is completed, but it still lets me scroll back to the empty page (it doesn't "resize" the page view)

我尝试在转换完成后将视图控制器作为子视图控制器删除,但它仍然让我滚动回空页面(它不会“调整”页面视图的大小)

Is what I want to do possible?

我想做的可能吗?

回答by Matt Mc

While the answers here are all informative, there is an alternate way of handling the problem, given here:

虽然这里的答案都提供了信息,但还有一种处理问题的替代方法,如下所示:

UIPageViewController navigates to wrong page with Scroll transition style

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

When I first searched for an answer to this problem, the way I was wording my search wound me up at this question, and not the one I've just linked to, so I felt obligated to post an answer linking to this other question, now that I've found it, and also elaborating a little bit.

当我第一次搜索这个问题的答案时,我搜索的措辞方式让我对这个问题产生了兴趣,而不是我刚刚链接到的那个,所以我觉得有必要发布一个链接到另一个问题的答案,现在我已经找到了,并且还详细说明了一点。

The problem is described pretty well by matt here:

matt在这里很好地描述了这个问题:

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.

这实际上是 UIPageViewController 中的一个错误。它只发生在滚动样式 (UIPageViewControllerTransitionStyleScroll) 和调用 setViewControllers:direction:animated:completion: withanimated:YES 之后。因此有两种解决方法:

不要使用 UIPageViewControllerTransitionStyleScroll。

或者,如果您调用 setViewControllers:direction:animated:completion:,请仅使用 animation:NO。

要清楚地看到错误,请调用 setViewControllers:direction:animated:completion: 然后,在界面中(作为用户),手动向左(返回)导航到上一页。您将导航回错误的页面:根本不是前一个页面,而是您在调用 setViewControllers:direction:animated:completion: 时所在的页面。

该错误的原因似乎是,在使用滚动样式时, UIPageViewController 会进行某种内部缓存。因此,在调用 setViewControllers:direction:animated:completion: 之后,它无法清除其内部缓存。它认为它知道前一页是什么。因此,当用户向左导航到上一页时,UIPageViewController 无法调用 dataSource 方法 pageViewController:viewControllerBeforeViewController:,或者使用错误的当前视图控制器调用它。

This is a good description, not quite the problem noted in this question but very close. Note the line about if you do setViewControllerswith animated:NOyou 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.

这是一个很好的描述,不是这个问题中提到的问题,但非常接近。请注意,如果您这样做setViewControllersanimated:NO您将强制 UIPageViewController 下次用户使用手势平移时重新查询其数据源,因为它不再“知道它在哪里”或其当前视图控制器旁边的视图控制器.

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

然而,这并没有为我工作,因为有些时候我需要以编程方式四处移动的浏览量动画。

So, my first thought was to call setViewControllerswith 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.

因此,我的第一个想法是setViewControllers使用动画进行调用,然后在完成块中使用现在显示的任何视图控制器再次调用该方法,但没有动画。所以用户可以平移,很好,但随后我们再次调用该方法以重置页面视图。

Unfortunately when I tried that I started getting strange "assertion errors" from the page view controller. They look something like this:

不幸的是,当我尝试这样做时,我开始从页面视图控制器中收到奇怪的“断言错误”。它们看起来像这样:

*** Assertion failure in -[UIPageViewController queuingScrollView: ...

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

Not knowing exactly why this was happening, I backtracked and eventually started using Jai's answeras a solution, creating an entirely new UIPageViewController, pushing it onto a UINavigationController, then popping out the old one. Gross, but it works--mostly. I have been finding I'm still getting occasional Assertion Failures from the UIPageViewController, like this one:

不知道为什么会发生这种情况,我回溯并最终开始使用Jai 的答案作为解决方案,创建一个全新的 UIPageViewController,将其推送到 UINavigationController,然后弹出旧的。很糟糕,但它有效——主要是。我发现我仍然偶尔会从 UIPageViewController 收到断言失败,就像这样:

*** 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 >

*** 断言失败 -[UIPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit_Sim/UIKit-2380.17/UIPageViewController.m:1820 $1 = 15450782 无视图控制器管理 > 视图可见

And the app crashes. Why? Well, searching, I found this other questionthat I mentioned up top, and particularly the accepted answerwhich advocates my original idea, of simply calling setViewControllers: animated:YESand then as soon as it completes calling setViewControllers: animated:NOwith 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:

并且应用程序崩溃。为什么?好吧,搜索,我发现了我在上面提到的另一个问题,特别是支持我最初想法的公认答案,即简单地调用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
                });
            }
        }];

Wow! The only reason this actually made sense to me is because I have watched the the WWDC 2012 Session 211, Building Concurrent User Interfaces on iOS (available herewith a dev account). I recall now that attempting to modify data source objects that UIKit objects (like UIPageViewController) depend on, and doing it on a secondary queue, can cause some nasty crashes.

哇!这对我来说真正有意义的唯一原因是因为我观看了 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:NOin the completion block of setViewControllers animated:YESand 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.

我从未见过特别记录但现在必须假设情况并继续阅读的是,动画的完成块是在辅助队列上执行的,而不是主要队列。那么,为什么UIPageViewController是嘎嘎叫,并给予断言失败,都当我最初试图调用的原因setViewControllers animated:NO在完成块setViewControllers animated:YES,现在也该我只是用一个UINavigationController推上一个新的UIPageViewController(做起来,再一次,在完成) 的块setViewControllers animated:YES是因为这一切都发生在该辅助队列上。

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.

这就是上面那段代码完美运行的原因,因为您来自动画完成块并将其发送回主队列,这样您就不会与 UIKit 交叉流。杰出的。

Anyway, wanted to share this journey, in case anyone runs across this problem.

无论如何,想分享这段旅程,以防有人遇到这个问题。

EDIT: Swift version here, if anyone's interested.

编辑:这里的Swift 版本,如果有人感兴趣的话。

回答by Lukas Kalinski

Concluding Matt Mc's great answer, the following method could be added to a subclass of UIPageViewController, that way allowing the usage of setViewControllers:direction:animated:completion: as it was intended to be used if the bug would not be present.

总结 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);
            }
        }
    }];
}

Now, simply call setViewControllers:direction:animated:completion: on the class/subclasses implementing this method, and it should work as expected.

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

回答by bilobatum

maq is right. If you are using the scrolling transition, removing a child view controller from the UIPageViewController does not prevent the deleted "page" from returning on-screen if the user navigates to it. If you're interested, here's how I removed the child view controller from the UIPageViewController.

马克是对的。如果您正在使用滚动过渡,从 UIPageViewController 中删除子视图控制器不会阻止已删除的“页面”在用户导航到它时返回到屏幕上。如果您有兴趣,这里是我从 UIPageViewController 中删除子视图控制器的方法。

// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController]; 

View controller deleteVC is removed from the childViewControllers property of the UIPageViewController, but still appears on-screen if the user navigates to it.

视图控制器 deleteVC 从 UIPageViewController 的 childViewControllers 属性中删除,但如果用户导航到它,它仍会出现在屏幕上。

Until someone smarter than me finds an elegant solution, here's a work around (it's a hack--so you have to ask yourself if you really need to remove pages from a UIPageViewController).

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

These instructions assume that only one page is displayed at a time.

这些说明假定一次只显示一页。

After the user taps a button indicating that she would like to delete the page, navigate to the next or previous page using the setViewControllers:direction:animated:completion: method. Of course, you then need to delete the page's content from your data model.

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

Next (and here's the hack), create and configure a brand new UIPageViewController and load it in the foreground (i.e., in front of the other UIPageViewController). Make sure that the new UIPageViewController starts off displaying the exact same page that was previously displayed. Your new UIPageViewController will fetch fresh view controllers from the data source.

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

Finally, unload and destroy the UIPageViewController that's in the background.

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

Anyway, maq asked a really good question. Unfortunately, I don't have enough reputation points to up vote the question. Ah, dare to dream... someday I will have 15 reputation points.

无论如何,maq 问了一个非常好的问题。不幸的是,我没有足够的声望点来支持这个问题。啊,敢做梦……总有一天我会有15点声望。

回答by Jai Govindani

I'll put this answer here just for my own future reference and if it helps anyone - what I ended up doing was:

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

  1. Delete the page and advance to the next page
  2. In the completion block of setViewControllers, I created/init'ed a new UIPageViewController with the modified data (item removed), and pushed it without animating, so nothing changes on-screen (my UIPageViewController is contained within a UINavigationController)
  3. After pushing the new UIPageViewController, get a copy of the viewControllersarray of the UINavigationController, remove the second-to-last view controller (which is the old UIPageViewController)
  4. There is no step 4 - done!
  1. 删除页面并前进到下一页
  2. 在 的完成块中setViewControllers,我使用修改后的数据(删除了项目)创建/初始化了一个新的 UIPageViewController,并在没有动画的情况下推送它,因此屏幕上没有任何变化(我的 UIPageViewController 包含在 UINavigationController 中)
  3. 推送新的 UIPageViewController 后,获取viewControllersUINavigationController 数组的副本,移除倒数第二个视图控制器(也就是旧的 UIPageViewController)
  4. 没有第 4 步 - 完成!

回答by user1459524

I am just learning this myself, so take with a grain of salt, but from what I understand, you need to change the datasource of the pageviewcontroller, not remove the viewcontroller. How many pages are shown in a pageviewcontroller is determined by its datasource, not the viewcontrollers.

我只是自己学习这个,所以请持保留态度,但据我所知,您需要更改 pageviewcontroller 的数据源,而不是删除 viewcontroller。pageviewcontroller 中显示的页面数量由其数据源决定,而不是由视图控制器决定。

回答by TonnyTao

For improve this. You should detect whether pageView is scrolling or not before setViewControllers.

为了改善这一点。您应该在 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)
            })
        }
    })
}

回答by Michal Zaborowski

This problem exists when you are trying to change viewControllers during swipe gesture animation between viewControllers. To solve this problem, i made a simple flag to discover when page view controller is during transition.

当您尝试在视图控制器之间的滑动手势动画期间更改视图控制器时,会出现此问题。为了解决这个问题,我做了一个简单的标志来发现页面视图控制器何时处于转换状态。

- (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;
}

And when i am trying to chenge view controller i am checking if there is transition in progress.

当我尝试使用视图控制器时,我正在检查是否正在进行转换。

- (void)setCurrentPage:(NSString *)newCurrentPageId animated:(BOOL)animated {

    if (self.pageViewControllerTransitionInProgress) {
        return;
    }

    [self.pageContentViewController setViewControllers:@[pageDetails] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
    }

}

回答by Ibdakine

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];

I didn't quite have time to read through the above comments but this worked for me. Basically I remove the data (in my case a user) and then move to the page before it. Works like a charm. Hope this helps those looking for a quick fix.

我没有时间阅读上述评论,但这对我有用。基本上我删除数据(在我的情况下是用户),然后移动到它之前的页面。奇迹般有效。希望这可以帮助那些寻求快速修复的人。

回答by Eike

It did it the following in Swift 3.0

它在 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
    }
}

And here the delay 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)
}

回答by TheCodeTalker

we are getting crash on tab change .So one solution i got is didChangeSelect of tabs disable the userintraction.

我们在选项卡更改时崩溃了。所以我得到的一个解决方案是选项卡的 didChangeSelect 禁用 userintraction。