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:
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.
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').
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.
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.
I had the same output error, even though my case it's not exactly the same. I saw this error while calling
emailTextField.becomeFirstResponder()
inviewDidAppear(:_)
in aUIViewController
inside aUIPageViewController
. The problem was that I was animating theUIPageViewController
parentUIView
to move some UI elements when the keyboard appeared.After scratching my head for a few hours, I found that if you wrap the call in a
DispatchQueue.main.async
block it no longer breaks.My hierarchy and further explaination:
UIViewController
: it has two buttons at the bottom and aUIContainerView
that holds aUIPageViewController
. ThisUIViewController
listens toNSNotification.Name.UIKeyboardWillChangeFrame
to 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, theUIViewController
calls the childUIPageViewController.setViewControllers(_:direction:animated:completion:)
with the needed view controller.If you call that method with
animated: false
it works. If you animate the insertion ofUIViewControllers
while animating theUIView
for the Keyboard changes, it breaks.Furthermore, every
UIViewController
inside theUIPageViewController
also listens toNSNotification.Name.UIKeyboardWillChangeFrame
to change the size of the UIScrollView that wraps the content.My final code inside every
UIViewController
that's embedded inside theUIPageViewController
is the following:Swift 4.2 version of the correct answer.
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
Hope it helps!
I was experiencing this problem when setting
pageViewController.dataSource = nil
to stop scrolling once the user scrolls to a certain page.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 = nil
is called before you callpageViewController.setViewControllers(...)
.If you
nil
it afterwards, even insidepageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
, you will get the exception mentioned above.So set
dataSource
to what you want it to be before you callsetViewControllers
.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?
and
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:
Use isInWindow to detect if the view is visible, so safe to manipulate UI.