xcode pageViewController setViewControllers 因“无效参数不满足:[views count] == 3”而崩溃

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24000712/
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-09-15 04:58:27  来源:igfitidea点击:

pageViewController setViewControllers crashes with "Invalid parameter not satisfying: [views count] == 3"

xcode

提问by Bj?rn

I know that there are several other questions like this, but i couldn't find a solution for my problem. I use a pageViewController which displays different ViewControllers. Every time the pageViewController moves forward I check the input of the lastPage. If it's wrong the pageViewController should go back to that page by using the setViewController method. Without this method everything works fine but if I try to use it the app crashes with the following exception:

我知道还有其他几个类似的问题,但我找不到解决我的问题的方法。我使用一个 pageViewController 来显示不同的 ViewControllers。每次 pageViewController 向前移动时,我都会检查 lastPage 的输入。如果它是错误的 pageViewController 应该使用 setViewController 方法返回到该页面。如果没有此方法,一切正常,但如果我尝试使用它,应用程序将崩溃,但出现以下异常:

19:48:25.596 Phook[23579:60b] *** Assertion failure in -[_UIQueuingScrollView _replaceViews:updatingContents:adjustContentInsets:animated:], /SourceCache/UIKit_Sim/UIKit-2935.137/_UIQueuingScrollView.m:383  
2014-06-02 19:48:25.600 Phook[23579:60b] *** Terminating app due to uncaught   exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying:   [views count] == 3'

And here's my code:

这是我的代码:

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished
   previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    if (completed && finished)
    {

        RegisterUserPageContent* lastPage = (RegisterUserPageContent*)previousViewControllers[ previousViewControllers.count-1];
        int lastIndex   = lastPage.pageIndex;
        int newIndex    = ((RegisterUserPageContent*)[self.pageViewController.viewControllers objectAtIndex:0]).pageIndex;

        if (newIndex > lastIndex)
        {   // Moved Forward
            if(!lastPage.testInput)
            {
                [self.pageViewController setViewControllers:@[ [self.storyboard instantiateViewControllerWithIdentifier:_pageStoryBoardIDs[0]] ]
                                         direction:UIPageViewControllerNavigationDirectionReverse
                                         animated:YES completion:nil];


            }
        }
        else
        {   // Moved Reverse

        }
    }
}

As I already said. I searched a lot and implemented some solutions but nothing helped. Thanks.

正如我已经说过的。我搜索了很多并实施了一些解决方案,但没有任何帮助。谢谢。

回答by jpecoraro342

I ran into the same problem, and was able to solve it using a tip from this answer https://stackoverflow.com/a/20973822/3757370. Simply placing the setViewControllers:direction:animated:completion: code inside of a dispatch_async block on the main queue fixed it for me. For you this would look like

我遇到了同样的问题,并且能够使用这个答案https://stackoverflow.com/a/20973822/3757370 中的提示来解决它。只需将 setViewControllers:direction:animated:completion: 代码放置在主队列上的 dispatch_async 块中即可为我修复它。对你来说,这看起来像

dispatch_async(dispatch_get_main_queue(), ^{
    [self.pageViewController setViewControllers:@[ [self.storyboard instantiateViewControllerWithIdentifier:_pageStoryBoardIDs[0]] ]
                                     direction:UIPageViewControllerNavigationDirectionReverse
                                     animated:YES completion:nil];
});

Hope it helps!

希望能帮助到你!

回答by Marchy

Ran into this exact same issue. Solved it by setting the first content controller explicitly on the UIPageViewController when it is first loaded (ie: inside 'viewDidLoad').

遇到了这个完全相同的问题。通过在第一次加载时在 UIPageViewController 上显式设置第一个内容控制器来解决它(即:在“viewDidLoad”中)。

// create first page
UIViewController* firstContentPageController = [self contentControllerAtIndex: 0];
[_paginationController
    setViewControllers: @[firstContentPageController]
    direction: UIPageViewControllerNavigationDirectionForward
    animated: NO
    completion: nil];

where 'contentControllerAtIndex:' is a simple helper method that creates a content controller. I also use it within the two delegate methods in order to return the appropriate controller for a given page.

其中 'contentControllerAtIndex:' 是一个创建内容控制器的简单辅助方法。我还在两个委托方法中使用它,以便为给定页面返回适当的控制器。

- (UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    NSInteger index = (NSInteger)((MyContentController*)viewController).pageIndex;

    return [self contentControllerAtIndex: index - 1];
}


- (UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
    NSInteger index = (NSInteger)((MyContentController*)viewController).pageIndex;

    return [self contentControllerAtIndex: index + 1];
}


- (MyContentController*)contentControllerAtIndex: (NSInteger)index {
    if (index < 0 || _pagesContent.count <= index)
        return nil;

    // create view controller
    MyContentController* contentController = [self.storyboard instantiateViewControllerWithIdentifier: @"MyContentController"];
    contentController.pageIndex = index;
    contentController.content = [_pagesContent objectAtIndex: index];

    return contentController;
}

The reason for this is that the UIPageViewControllerDataSource protocol was designed to only have methods for pulling the previous/next content controller, as opposed to pulling a single controller at a particular index. It's an odd design decision, but the caveat is that instead of being called by Cocoa when the component is first loaded you have to manually set its starting state. Poor framework design here IMHO.

这样做的原因是 UIPageViewControllerDataSource 协议被设计为仅具有拉取前一个/下一个内容控制器的方法,而不是在特定索引处拉取单个控制器。这是一个奇怪的设计决定,但需要注意的是,当组件首次加载时,您必须手动设置其启动状态,而不是由 Cocoa 调用。恕我直言,这里的框架设计不佳。

回答by kax

Well it's 2018 and the bug is still around. I tried all the solutions above, but none worked for me. And after many tries, setting the animation parameter to false, was what worked for me in swift 4

好吧,现在是 2018 年,错误仍然存​​在。我尝试了上述所有解决方案,但没有一个对我有用。经过多次尝试,将动画参数设置为 false,这在 swift 4 中对我有用

let myViewController : UIViewController = orderedViewControllers[vcIndex]
setViewControllers([myViewController], direction: .forward, animated: false, completion: nil);

Then it was just a matter of setting a custom transition. Hope it helps others in the same situation.

然后只是设置自定义过渡的问题。希望它可以帮助其他处于相同情况的人。

回答by Enea Dume

Swift 4.2version of the correct answer.

Swift 4.2版本的正确答案。

    DispatchQueue.main.async {
        self.pageViewController?.setViewControllers([self.initialViewControllerAtIndex(index: 0)!], direction: .forward, animated: false, completion: nil)
    }

回答by Xavi Moll

I had the same output error, even though my case it's not exactly the same. I saw this error while calling emailTextField.becomeFirstResponder()in viewDidAppear(:_)in a UIViewControllerinside a UIPageViewController. The problem was that I was animating the UIPageViewControllerparent UIViewto move some UI elements when the keyboard appeared.

我有相同的输出错误,即使我的情况并不完全相同。同时呼吁我看到这个错误emailTextField.becomeFirstResponder()viewDidAppear(:_)UIViewController里面UIPageViewController。问题是当键盘出现时,我正在UIPageViewController为父级UIView设置动画以移动一些 UI 元素。

After scratching my head for a few hours, I found that if you wrap the call in a DispatchQueue.main.asyncblock it no longer breaks.

在摸索了几个小时之后,我发现如果将调用封装在一个DispatchQueue.main.async块中,它就不再中断了。

My hierarchy and further explaination:

我的层次结构和进一步解释:

UIViewController: it has two buttons at the bottom and a UIContainerViewthat holds a UIPageViewController. This UIViewControllerlistens to NSNotification.Name.UIKeyboardWillChangeFrameto move the bottom navigation buttons when the keyboard appears (don't try to change the size of the container, I tried that and it broke even more). When one of the buttons is tapped, the UIViewControllercalls the child UIPageViewController.setViewControllers(_:direction:animated:completion:)with the needed view controller.

UIViewController:它的底部有两个按钮,一个UIContainerViewUIPageViewController. 当键盘出现时,这会UIViewController监听NSNotification.Name.UIKeyboardWillChangeFrame底部导航按钮的移动(不要尝试更改容器的大小,我尝试过,但它打破了更多)。当点击其中一个按钮时,使用所需的视图控制器UIViewController调用子级UIPageViewController.setViewControllers(_:direction:animated:completion:)

If you call that method with animated: falseit works. If you animate the insertion of UIViewControllerswhile animating the UIViewfor the Keyboard changes, it breaks.

如果你用animated: false它调用那个方法就行了。如果UIViewControllersUIView为键盘更改设置动画时为插入设置动画,它会中断。

Furthermore, every UIViewControllerinside the UIPageViewControlleralso listens to NSNotification.Name.UIKeyboardWillChangeFrameto change the size of the UIScrollView that wraps the content.

此外,每个UIViewController内部UIPageViewController还侦听NSNotification.Name.UIKeyboardWillChangeFrame更改包装内容的 UIScrollView 的大小。

My final code inside every UIViewControllerthat's embedded inside the UIPageViewControlleris the following:

我在每个UIViewController嵌入其中的最终代码UIPageViewController如下:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    DispatchQueue.main.async {
        self.emailTextField.becomeFirstResponder()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    emailTextField.resignFirstResponder()
}

回答by Heisenbean

I get this problem today.
I start an animation when UIPageController switch viewController.

我今天遇到这个问题。
当 UIPageController 切换 viewController 时,我开始动画。

- (void)setViewControllers:(nullable NSArray<UIViewController *> *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion;

Program will crash if animation:YES,but if animation:NOit will be fine.

程序会崩溃animation:YES,但如果animation:NO它会很好。

I guess we cant do two animations as the same time. But if you want animation:YES,you can do your other animation after switch viewControlle's,such as use GCD after.

我想我们不能同时做两个动画。但是如果你愿意animation:YES,你可以在切换视图控件后做你的其他动画,比如使用 GCD 之后。

回答by Marc Palmer

I was experiencing this problem when setting pageViewController.dataSource = nilto stop scrolling once the user scrolls to a certain page.

我在设置pageViewController.dataSource = nil用户滚动到某个页面后停止滚动时遇到了这个问题。

Turns out, the solution appears to be to not use any of the above async workarounds. In general you should do everything you can to avoid these kinds of workarounds?— they typically show you are holding it wrong.

事实证明,解决方案似乎是不使用上述任何异步解决方法。一般来说,您应该尽一切努力避免这些变通方法? - 它们通常表明您的做法是错误的。

The solution for me at least is to make sure pageViewController.dataSource = nilis called beforeyou call pageViewController.setViewControllers(...).

至少对我来说,解决方案是确保pageViewController.dataSource = nil在调用之前调用pageViewController.setViewControllers(...).

If you nilit afterwards, even inside pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool), you will get the exception mentioned above.

如果你nil在之后,即使在里面pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool),你也会得到上面提到的异常。

So set dataSourceto what you want it to be before you call setViewControllers.

因此dataSource,在调用之前设置为您想要的setViewControllers

回答by Jase Whatson

In addition to

此外

DispatchQueue.main.async {
        self.pageViewController?.setViewControllers([self.initialViewControllerAtIndex(index: 0)!], direction: .forward, animated: false, completion: nil)
    }

I had to also place this following code within a DispatchQueue.main.async

我还必须将以下代码放在DispatchQueue.main.async 中

DispatchQueue.main.async {
           for v in self.pageViewController!.view.subviews {
                if let sub = v as? UIScrollView {
                    sub.isScrollEnabled = enable
                }
            }
}

Because I was enable/disabling scrolling until users completed filling out values on previous pages, before allowing them to go to the next page

因为我启用/禁用滚动,直到用户完成填写前一页的值,然后才允许他们转到下一页

回答by Géza Mikló

I had the very same problem, but executing on main queue was not enough.

我遇到了同样的问题,但在主队列上执行还不够。

There are multiple viewcontrollers instantiated in func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?

在 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? 中实例化了多个视图控制器。

and

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?

These controllers might try to manipulate UI when they are not visible! Using viewDidAppear would be too easy solution in those controllers, to make them modify UI only when visible. So I came up with a solution:

这些控制器可能会在不可见时尝试操作 UI!在这些控制器中使用 viewDidAppear 将是太简单的解决方案,使它们仅在可见时修改 UI。所以我想出了一个解决方案:

  1. Pure Swift
  1. 纯斯威夫特

Use isInWindow to detect if the view is visible, so safe to manipulate UI.

使用 isInWindow 来检测视图是否可见,因此操作 UI 是安全的。

import UIKit
extension UIViewController {
    var isInWindow: Bool {
        return self.viewIfLoaded?.window != nil
    }
}
  1. RxSwift + RxCocoa
  1. RxSwift + RxCocoa
class MyViewControllerThatMightBePaged {

    var hasAppeared = BehaviorRelay<Bool>(value: false)
    var windowObservation: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        // If not paged, the View will have a window immediatelly
        hasAppeared.accept(self.parent != nil)
        windowObservation = observe(\.parent, options: [.new], changeHandler: { [weak self] (_, change) in
            self?.hasAppeared.accept(change.newValue != nil)
        })


        hasAppeared.asObservable()
        .distinctUntilChanged()
        .filter({##代码##})
        .take(1)
            .subscribe(onNext: { [weak self] (_) in
                // DO the initial UI manipulating magic
            }, onError: nil, onCompleted: nil, onDisposed: nil)
        .disposed(by: disposeBag)
    }

    deinit {
        windowObservation?.invalidate()
        windowObservation = nil
    }

}

回答by Ernest Jordan Chechelski

I found that calling twice the method pageViewController.setViewControllers(...)inside viewDidLoad()leads to this error

我发现调用两次pageViewController.setViewControllers(...)里面的方法viewDidLoad()会导致这个错误