pageViewController setViewControllers 因“无效参数不满足:[views count] == 3”而崩溃
Posted
技术标签:
【中文标题】pageViewController setViewControllers 因“无效参数不满足:[views count] == 3”而崩溃【英文标题】:pageViewController setViewControllers crashes with "Invalid parameter not satisfying: [views count] == 3" 【发布时间】:2014-06-02 18:09:43 【问题描述】:我知道还有其他几个类似的问题,但我找不到我的问题的解决方案。 我使用显示不同 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
正如我已经说过的。我进行了很多搜索并实施了一些解决方案,但没有任何帮助。 谢谢。
【问题讨论】:
【参考方案1】:我遇到了同样的问题,并且能够使用此答案 https://***.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];
);
希望对你有帮助!
【讨论】:
谢谢!我已经看到过这种方式,但我再次尝试了一些属性 - 现在它可以工作了。 这也为我解决了这个崩溃问题,即使代码已经在主队列上执行了 这对我也有用,但很高兴知道当代码已经在主队列上时为什么它实际上有帮助。 警惕这种解决方案。它修复了Invalid parameter not satisfying
,但引入了NSInternalInconsistencyException Duplicate states in queue
,它更难重现,但仍然会导致足够多的崩溃让用户担心。
你们中的任何更新,这不起作用?【参考方案2】:
遇到了同样的问题。通过在 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 调用。恕我直言,这里的框架设计很差。
【讨论】:
我已经在我的 viewDidLoad 方法中设置了第一个 ViewController。所以这不是问题。当我稍后尝试使用 setViewContollers 方法时,我的问题首先出现。在 if 语句之后,我尝试跳回之前的页面。但是这里的方法不起作用(不再)。 我通过使用您的代码 sn-p 设置我的第一个视图控制器来修复我的代码。第一个孩子根本没有出现,但我可以滑动到导致这个异常的第二个孩子。我在没有故事板的情况下在代码中做所有事情,因为我的 UIPageViewController 视图是更大 UI 的子视图。【参考方案3】:嗯,现在是 2018 年,错误仍然存在。我尝试了上述所有解决方案,但没有一个对我有用。在多次尝试之后,将动画参数设置为 false,这对我来说是 swift 4
let myViewController : UIViewController = orderedViewControllers[vcIndex]
setViewControllers([myViewController], direction: .forward, animated: false, completion: nil);
然后只需设置自定义过渡即可。希望它可以帮助其他处于相同情况的人。
【讨论】:
dispatch_async 对于 webkit 视图交换控制器来说是不够的。我还不得不关闭动画。【参考方案4】:Swift 4.2 版本的正确答案。
DispatchQueue.main.async
self.pageViewController?.setViewControllers([self.initialViewControllerAtIndex(index: 0)!], direction: .forward, animated: false, completion: nil)
【讨论】:
【参考方案5】:我有同样的输出错误,即使我的情况并不完全相同。我在UIViewController
内的viewDidAppear(:_)
中调用emailTextField.becomeFirstResponder()
时看到了这个错误UIPageViewController
。问题是我正在为UIPageViewController
父UIView
设置动画以在键盘出现时移动一些UI 元素。
在摸索了几个小时后,我发现如果您将调用封装在 DispatchQueue.main.async
块中,它就不再中断。
我的层次结构和进一步解释:
UIViewController
:它的底部有两个按钮和一个UIContainerView
,其中包含一个UIPageViewController
。这个UIViewController
监听NSNotification.Name.UIKeyboardWillChangeFrame
在键盘出现时移动底部的导航按钮(不要试图改变容器的大小,我试过了,结果更糟)。当点击其中一个按钮时,UIViewController
使用所需的视图控制器调用子 UIPageViewController.setViewControllers(_:direction:animated:completion:)
。
如果您使用animated: false
调用该方法,它会起作用。如果在为键盘更改设置UIView
的动画时为UIViewControllers
的插入设置动画,则会中断。
此外,UIPageViewController
中的每个UIViewController
还监听NSNotification.Name.UIKeyboardWillChangeFrame
以更改包装内容的 UIScrollView 的大小。
嵌入UIPageViewController
的每个UIViewController
中的最终代码如下:
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
DispatchQueue.main.async
self.emailTextField.becomeFirstResponder()
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
emailTextField.resignFirstResponder()
【讨论】:
【参考方案6】:我们最近进行了一些调查,发现禁用动画可以防止崩溃。但是我们还是想在切换标签页的时候显示动画,所以我们想办法避免在可能导致崩溃的状态上出现动画。这是解决方案:
let vc = self.orderedViewControllers[index]
self.pageViewController.setViewControllers([vc], direction: direction, animated: !self.pageViewController.children.contains(vc), completion:nil)
以下是调查结果:
使用childViewControllers
而不是viewControllers
来检查屏幕上的当前视图控制器。 childViewControllers
反映了 UIPageViewController
上的实际视图控制器,而 viewControllers
正是我们设置的/用户刚刚滑动到的视图控制器,它的计数是 1 或 2。
UIPageViewController
可能会变得陈旧 - 拥有两个以上的子视图控制器,并且无法通过用户滑动手势来移除它们。
调用setViewControllers
会重置正确的状态,但如果有动画就会崩溃。
我们可以通过在不同标签之间快速点击或按住UIPageViewController
同时快速点击不同标签(成功率更高)来重现陈旧状态。看起来复杂的操作会破坏UIPageViewController
的内部队列,并且会因为抛出这个错误而崩溃:
NSInternalInconsistencyException:无效参数不满足:[views count] == 3
在didFinishAnimating
回调中调用setViewControllers
也会导致崩溃。
【讨论】:
【参考方案7】:我今天遇到了这个问题。 我在 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。
【讨论】:
【参考方案8】:我在将pageViewController.dataSource = nil
设置为在用户滚动到某个页面后停止滚动时遇到了这个问题。
事实证明,解决方案似乎是不使用上述任何异步解决方法。一般来说,您应该尽一切可能避免这些变通办法——它们通常表明您的做法是错误的。
至少对我来说,解决方案是确保在您调用pageViewController.setViewControllers(...)
之前调用pageViewController.dataSource = nil
。
如果你之后nil
它,即使在pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
内部,你也会得到上面提到的异常。
因此,在调用 setViewControllers
之前,请将 dataSource
设置为您想要的值。
【讨论】:
【参考方案9】:除了
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
因为我启用/禁用滚动,直到用户完成填写前一页上的值,然后才允许他们转到下一页
【讨论】:
【参考方案10】:我遇到了同样的问题,但是在主队列上执行还不够。
在其中实例化了多个视图控制器 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
和
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
这些控制器可能会在它们不可见时尝试操纵 UI!在这些控制器中使用 viewDidAppear 将是过于简单的解决方案,无法让它们仅在可见时修改 UI。所以我想出了一个解决方案:
-
纯斯威夫特
使用 isInWindow 来检测视图是否可见,因此可以安全地操作 UI。
import UIKit
extension UIViewController
var isInWindow: Bool
return self.viewIfLoaded?.window != nil
-
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
【讨论】:
【参考方案11】:我发现在viewDidLoad()
中调用两次方法pageViewController.setViewControllers(...)
会导致这个错误?
【讨论】:
【参考方案12】:我有同样的错误。除了像其他人建议的那样将它包装在DispatchQueue.main.async
中之外,我还在完成块中添加了一个动画,这似乎导致了问题。
所以我想这是另一件需要注意的事情,以防人们在阅读所有解决方案后仍然摸不着头脑。
【讨论】:
【参考方案13】:我在重复调用setViewControllers
时遇到了这个崩溃问题,与主线程无关。
这是我的背景,在滑动页面内容视图控制器时,我需要更新页面中UISegmentControl
的选定段索引,然后选定段索引更改块尝试调用setViewControllers
当然也在主线程中。但是崩溃了!
简单修复:
UIViewController *targetVC = ...;
if (![self.pageVC.viewControllers.firstObject isEqual:targetVC])
[self.pageVC setViewControllers:@[targetVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
只需尝试检查最新的视图控制器上下文以切断额外的重复调用,解决!希望对您有所帮助!
【讨论】:
以上是关于pageViewController setViewControllers 因“无效参数不满足:[views count] == 3”而崩溃的主要内容,如果未能解决你的问题,请参考以下文章
Swift 中的 PageViewController 当前页面索引