I've got a working UIPageViewController that holds multiple UIViewControllers embedded in an UINavigationController and each UIViewController has a preview of an array of images wich, when opened, instantiate a new UIPageViewController to display those images
when i swipe through the images and then swipe back to the first one my app crashes with "EXC_BAD_ACCESS(code=EXC_I386_GPFLT)" same thing when i use the back button of the UINavigationController
why is that and how can i fix this ?
My PageViewController (the marked line is the last one i got in the debugger before it crashes):
class DetailPageMasterViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource
{
var presentationPageIndex: Int = 0
var itemsArray = [Aktion]()
var pageViewController: UIPageViewController!
#IBOutlet weak var btnEditOutlet: UIBarButtonItem!
#IBAction func btnEditAction(sender: AnyObject)
{
}
override func viewDidLoad()
{
super.viewDidLoad()
self.pageViewController = UIPageViewController.init(transitionStyle: .Scroll,
navigationOrientation: .Horizontal,
options: nil)
self.pageViewController.delegate = self
self.pageViewController.dataSource = self
self.presentationPageIndex = 0
let firstVC = self.viewControllerAtIndex(presentationPageIndex)
let viewControllers = [firstVC]
self.pageViewController.setViewControllers(viewControllers,
direction: .Forward,
animated: false,
completion: nil)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
self.setupPageControl()
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
{
if completed
{
let minionVC = self.pageViewController.viewControllers?.last as! DetailMinionViewController
presentationPageIndex = minionVC.pageIndex
}
}
func viewControllerAtIndex(index: Int) -> DetailMinionViewController
{
let contentVC = self.storyboard?.instantiateViewControllerWithIdentifier("MinionPageViewController") as! DetailMinionViewController
contentVC.aktion = itemsArray[index]
contentVC.pageIndex = index
return contentVC
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
if let viewController = viewController as? DetailMinionViewController
{
var index = viewController.pageIndex
if index == 0 || index == NSNotFound
{
return nil //MARKED LINE
}
index -= 1
return self.viewControllerAtIndex(index)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
if let viewController = viewController as? DetailMinionViewController
{
var index = viewController.pageIndex
if index == NSNotFound
{
return nil
}
index += 1
if index == NSNotFound || index >= itemsArray.count
{
return nil
}
return self.viewControllerAtIndex(index)
}
return nil
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return itemsArray.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return presentationPageIndex
}
func setupPageControl()
{
UIPageControl.appearance().backgroundColor = UIColor.clearColor()
UIPageControl.appearance().pageIndicatorTintColor = UIColor.whiteColor()
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.redColor()
}
}
So the structure looks like
UINavigationController -> DetailPageMasterViewController -> DetailMinionViewController -> PicturesPageMasterViewController -> PicturesMinionViewController
So i finally figured out what caused my app to crash.
Long story short:
a gestureRecognizer in the PicturesMinionViewController tried to access an already deinitialized ImageView, my pageViewController worked fine
Please check the number of viewController added into the PageController. If possible you can share your code.
Related
When PageController trying to present it's content I'm encountering following message in console:
"[Assert] Window should be nonnil here unless a subclass is mistakenly sending this to a child when no window can be found".
App is not crashing it's just not showing content it's supposed to show in PageController. Code seems fine to me:
class RulesPageViewController: UIPageViewController {
var rulesDelegate: RulesPageVeiwControllerDelegate?
var pageHeadings = ["1st page","2nd","3rd"]
var currentIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let startingViewController = contentViewController(at: 0)
{
self.setViewControllers([startingViewController], direction: .forward, animated: false, completion: nil)
}
}
func contentViewController(at index: Int)-> PageContentViewController?
{
if(index < 0 || index >= pageHeadings.count)
{
return nil
}
if let pageContentViewController = UIStoryboard(name: "GameRules", bundle: nil).instantiateViewController(withIdentifier: "PageContentViewController") as? PageContentViewController{
pageContentViewController.index = index
return pageContentViewController
}
return nil
}
}
extension RulesPageViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
if let contentViewController = pageViewController.viewControllers?.first as? PageContentViewController
{
currentIndex = contentViewController.index
rulesDelegate?.didUpdatePageIndex(currentIndex: currentIndex)
}
}
}
}
extension RulesPageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = (viewController as! PageContentViewController).index
index -= 1
return contentViewController(at: index)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = (viewController as! PageContentViewController).index
index += 1
return contentViewController(at: index)
}
}
Problem was solved: PageContentViewController was conforming to wrong class
My problem here it's the swipe movement when I use the pageViewController implementation. I want it to go from right to left but it's going from left to right who's not naturally for the UI experience !
It was working perfectly when I was using Xcode 7 and Swift 2.2 ...
So since I copied/pasted my code and modifiying with the Xcode 8 suggestion, it has this strange behavior.
import UIKit
class FirstEventViewController: UIViewController {
// FIXME: - The swipe gesture has to be from right to left when the view appears (PageControl not showing)
var pageViewController: UIPageViewController!
var titleEvents = [String]()
var pageImages: NSArray!
override func viewDidLoad() {
super.viewDidLoad()
pageImages = NSArray(objects: "supersmashbros","spiritodjsession","thomasdelortrio")
titleEvents = ["supersmashbros","spiritodjsession","thomasdelortrio"]
initiatePgVC()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//TODO: CODE HERE
}
// MARK: - Private functions
/**
Function initializing the logic of the pageviewController
*/
func initiatePgVC() {
pageViewController = UIStoryboard(name:"Main", bundle:nil).instantiateViewController(withIdentifier: "PageViewController") as! UIPageViewController
pageViewController.dataSource = self
let startVC = viewControllerAtIndex(index: 0)
let viewControllers = NSArray(object: startVC) as! [UIViewController]
pageViewController.setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil)
//pageViewController.view.frame = CGRect(x:0, y:65, width:self.view.frame.width, height:self.view.frame.size.height - 140)
pageViewController.view.frame = self.view.bounds
addChildViewController(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParentViewController: self)
}
/**
Function associating the viewControllers
*/
func viewControllerAtIndex(index: Int)->SecondEventViewController{
if ((titleEvents.count == 0) || (index >= titleEvents.count)){
return SecondEventViewController()
}
let vc:SecondEventViewController = storyboard?.instantiateViewController(withIdentifier: "SecondEventViewController") as! SecondEventViewController
vc.imageFile = pageImages[index] as! String
vc.labelTitle = titleEvents[index]
vc.pageIndex = index
return vc
}
}
extension FirstEventViewController:UINavigationBarDelegate{
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
extension FirstEventViewController:UIPageViewControllerDataSource{
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let vc = viewController as! SecondEventViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound){
return nil
}
index = index-1
return viewControllerAtIndex(index: index)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let vc = viewController as! SecondEventViewController
var index = vc.pageIndex as Int
if (index == NSNotFound){
return nil
}
index = index+1
if (index == titleEvents.count){
return nil
}
return viewControllerAtIndex(index: index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return titleEvents.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
I once suspected the pageViewController.setViewControllers(viewControllers, direction: .forward, animated: true, completion: nil) because it implements the direction but even if I change with reverse, nothing happens.
Have you got the changing of the indexes the wrong way around?
for after controller i'd expect the index to increase but you are decreasing it, and vice versa.
Try swapping these, so after should be + 1 and before -1
index = index+1
I am Using UIPageController having 2 images in it. While running the app, The UIPageController shows the first page perfectly, but then scrolls infinitely from the second page. It maintains the same image and continues to show the similar image even after it has reached the limit. It only changes the image for the first swipe, and then neither changes the image nor revert back to the initial image on the subsequent scroll.
Given is the source. Please help.
import UIKit
class SignUpViewController: UIViewController, UIPageViewControllerDataSource {
var pageViewController : UIPageViewController!
var InfoImages : NSArray!
override func viewDidLoad() {
super.viewDidLoad()
self.InfoImages = NSArray(objects: "blue","red")
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("IntroPageController") as! UIPageViewController
self.pageViewController.dataSource = self
let startVC = self.viewControllerAtIndex(0) as ContentViewController
let viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewControllerAtIndex(index: Int) -> ContentViewController
{
if ((self.InfoImages.count == 0) || (index >= self.InfoImages.count)){
return ContentViewController()
}
let info = storyBoard.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController
info.imageFile = self.InfoImages[index] as! String
return info
}
//MARK : -DataSource
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if( index == 0 || index == NSNotFound ){
return nil
}
index--
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if( index == NSNotFound ){
return nil
}
index++
if(index == self.InfoImages.count){
return nil
}
return self.viewControllerAtIndex(index)
}
#available(iOS 6.0, *)
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.InfoImages.count
}
#available(iOS 6.0, *)
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
#IBAction func SkipInfo(sender: AnyObject) {
let skipinfo = storyBoard.instantiateViewControllerWithIdentifier("afterLoginview") as! LoginViewController
self.presentViewController(skipinfo , animated: true, completion: nil)
}
Cheers and Thanks in advance!
P.S : I am using Xcode 7.0 beta (swift 2.0)
Okay, I've created a pageview slider app. Everything works great, but now I'd like to be able to hide/unhide my navbar with a single tap using UITapGestureRecognizer.
I have been able to hide the navbar on viewDidLoad, but I am not sure where to call my singleTapped function, or how to implement UITapGestureRecognizer.
How can I hide/unhide the UIPageViewController navbar with UITapGestureRecognizer?
New to swift/ios
my code
class ViewController: UIViewController, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController!
private var allPages = [Page]()
var pages = NSMutableOrderedSet()
override func viewDidLoad() {
super.viewDidLoad()
###Hide NavBar
self.navigationController?.navigationBarHidden = true
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
self.pageViewController.dataSource = self
var startVC = self.viewControllerAtIndex(0) as ContentViewController
var viewControllers = NSArray(object: startVC)
self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 30, self.view.frame.width, self.view.frame.size.height)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
###Function to Hide/Unhide Navbar
func singleTapped(recognizer: UITapGestureRecognizer) {
if (self.navigationController?.navigationBarHidden == false) {
###hide the Navigation Bar
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
###if Navigation Bar is already hidden
else if (self.navigationController?.navigationBarHidden == true)
{
###Show the Navigation Bar
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound) {
return nil
}
index--
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if (index == NSNotFound) {
return nil
}
index++
if (index == self.pages.count) {
return nil
}
return self.viewControllerAtIndex(index)
}
func viewControllerAtIndex(index: Int) -> ContentViewController
{
if ((self.pages.count == 0) || (index >= self.pages.count)) {
return ContentViewController()
}
var vc: ContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController
###Store image in imageFile
var image = (pages.objectAtIndex(index) as! Page).image
vc.imageFile = image
vc.pageIndex = index
return vc
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.pages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
}
Use UITapGestureRecognizer:
In viewDidLoad function add
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action:"singleTapped:"))
As you can see, there are two UIViewControllers (one of them is called ProView and acts as a container for the other UIViewController and one UIPageViewController.
import UIKit
class ProView: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pageViewController: UIPageViewController?
let characterImages = ["character1", "character2"]
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController ProView: UIViewController) -> UIViewController? {
let itemController = ProView as PageItemController
if itemController.itemIndex+1 < characterImages.count {
return getItemController(itemController.itemIndex+1)
}
return nil
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController ProView: UIViewController) -> UIViewController? {
let itemController = ProView as PageItemController
if itemController.itemIndex > 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
private func getItemController(itemIndex: Int) -> PageItemController? {
if itemIndex < characterImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as PageItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = characterImages[itemIndex]
return pageItemController
}
return nil
}
func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
pageController.dataSource = self
if characterImages.count > 0 {
let firstController = getItemController(0)!
let startingViewControllers: NSArray = [firstController]
pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController?.didMoveToParentViewController(self)
}
func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return characterImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
class PageItemController: UIViewController {
#IBOutlet weak var imageCharacterChoose: UIImageView!
var itemIndex: Int = 0
var imageName: String = "" {
didSet {
if let imageView = imageCharacterChoose {imageCharacterChoose.image = UIImage(named: imageName)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
imageCharacterChoose!.image = UIImage(named: imageName)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I would like to know if the user correctly swipes from one view to another. For that matter, I need the method transitionCompleted to work:
func pageViewController(ProView: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers pageViewController: [AnyObject],
transitionCompleted completed: Bool)
{
// If the page did not turn
if (!completed)
{
// You do nothing because whatever page you thought
// the book was on before the gesture started is still the correct page
println("The page number did not change.")
return;
}
// This is where you would know the page number changed and handle it appropriately
println("The page number has changed.")
}
The problem is that doesn't work (the console output doesn't print anything) because the parameters aren't correct probably. According to Apple Reference,
pageViewController The page view controller.
previousViewControllers The view controllers prior to the transition.
I therefore changed the parameters using "ProView" and "pageViewController" but that doesn't seem to work. What are the correct parameters according to the code for the transitionCompleted method? Do I need to declare something?
The problem is that pageViewController:didFinishAnimating:... is a delegate method, but this class is not the page view controller's delegate.
You have this line:
pageController.dataSource = self
So you are setting the dataSource. But you never set the delegate. Set it:
pageController.delegate = self