pageViewController setViewControllers崩溃“无效参数不满意:[views count] == 3”

时间:2014-06-02 18:09:44

标签: xcode

我知道还有其他几个这样的问题,但我无法找到解决问题的方法。 我使用了一个显示不同ViewControllers的pageViewController。每次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'

这是我的代码:

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

        }
    }
}

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

13 个答案:

答案 0 :(得分:44)

我遇到了同样的问题,并且能够使用此答案中的提示解决问题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];
});

希望它有所帮助!

答案 1 :(得分:8)

进入同样的问题。通过在首次加载时在UIPageViewController上显式设置第一个内容控制器来解决它(即:在'viewDidLoad'中)。

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

其中'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;
}

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

答案 2 :(得分:4)

那么2018年,这个错误仍然存​​在。我尝试了上面的所有解决方案,但没有一个适合我。经过多次尝试,将animation参数设置为false,这对我来说在swift 4中是有用的

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

然后只是设置自定义转换的问题。希望它在同样的情况下帮助其他人。

答案 3 :(得分:2)

迅速4.2 版本的正确答案。

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

答案 4 :(得分:1)

我有相同的输出错误,即使我的情况不完全一样。我在emailTextField.becomeFirstResponder()viewDidAppear(:_)内的UIViewController中调用UIPageViewController时看到此错误。问题在于,当键盘出现时,我正在设置UIPageViewControllerUIView动画来移动一些UI元素。

在我挠了几个小时之后,我发现如果你把电话换成DispatchQueue.main.async区块,它就不会再断开了。

我的等级和进一步的解释:

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

如果您使用animated: false调用该方法,则可行。如果您在为键盘更改UIViewControllers设置动画时动画插入UIView,则会中断。

此外,UIViewController内的每个UIPageViewController也会监听NSNotification.Name.UIKeyboardWillChangeFrame以更改包装内容的UIScrollView的大小。

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

答案 5 :(得分:1)

我今天遇到这个问题 我在UIPageController切换viewController时启动动画。

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

如果animation:YES,程序会崩溃,但如果animation:NO则没问题。

我想我们不能同时做两个动画。 但是如果你想要animation:YES,你可以在切换viewControlle之后进行其他动画,例如在之后使用GCD。

答案 6 :(得分:1)

我们最近进行了一些调查,发现禁用动画可以防止崩溃。但是我们仍然希望在切换选项卡时显示动画,因此我们试图找到一种避免动画进入可能导致崩溃的状态的方法。解决方法如下:

let vc = self.orderedViewControllers[index]
self.pageViewController.setViewControllers([vc], direction: direction, animated: !self.pageViewController.children.contains(vc), completion:nil)

以下是调查结果:

  1. 使用childViewControllers而非viewControllers检查屏幕上的当前视图控制器。 childViewControllers反映了UIPageViewController上的实际视图控制器,而viewControllers正是我们设置的/用户刚刚滑动到的视图控制器,其计数是1或2。

  2. UIPageViewController可能会过时-具有两个以上的子视图控制器,并且无法通过用户滑动手势将其删除。 调用setViewControllers会重置正确的状态,但是如果有动画,它将崩溃。

  3. 我们可以通过在不同的选项卡之间快速点击或在同时快速点击不同的选项卡的同时按住UIPageViewController来保持陈旧状态(成功率更高)。看来复杂的操作会破坏UIPageViewController的内部队列,并且会因抛出以下错误而崩溃:

    NSInternalInconsistencyException:无效的参数不令人满意:[观看次数] == 3

  4. setViewControllers回调中调用didFinishAnimating也会导致崩溃。

答案 7 :(得分:0)

pageViewController.dataSource = nil设置为一旦用户滚动到特定页面就停止滚动,我就遇到了这个问题。

结果是,解决方案似乎是不使用任何上述异步解决方法。一般来说,您应该尽一切可能避免这些变通办法-它们通常表明您认为它是错误的。

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

如果以后nil,甚至在pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)内,也会遇到上述异常。

因此,在致电dataSource之前,将setViewControllers设置为您想要的样子。

答案 8 :(得分:0)

除了

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

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

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

因为我要启用/禁用滚动功能,直到用户完成上一页中的值填写,然后才允许他们进入下一页

答案 9 :(得分:0)

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

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

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

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

  1. 纯雨燕

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

import UIKit
extension UIViewController {
    var isInWindow: Bool {
        return self.viewIfLoaded?.window != nil
    }
}
  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({$0})
        .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
    }

}

答案 10 :(得分:0)

我发现在pageViewController.setViewControllers(...)内部调用方法viewDidLoad()两次导致此错误?

答案 11 :(得分:0)

我有同样的错误。除了像其他人建议的那样将其包装在DispatchQueue.main.async中之外,我还在完成块中添加了一个动画,它似乎引起了问题。

所以我想这是需要提防的另一件事,以防人们在阅读完所有解决方案后仍然抓着脑袋。

答案 12 :(得分:0)

重复调用setViewControllers时,我遇到了崩溃问题,与主线程无关

这是我的背景,在滑动页面内容视图控制器时,我需要更新页面中UISegmentControl的所选段索引,然后所选段索引已更改块当然,也要在主线程中调用setViewControllers。但是崩溃了!

轻松修复:

UIViewController *targetVC = ...;

if (![self.pageVC.viewControllers.firstObject isEqual:targetVC]) {
    [self.pageVC setViewControllers:@[targetVC]
                          direction:UIPageViewControllerNavigationDirectionForward
                           animated:NO
                         completion:nil];
}

只需尝试检查最新的视图控制器上下文即可切断额外的重复调用,解决了!希望对您有帮助!