I need a UIPageViewController to display as many UIViewControllers as the data available in an array.
Let's say I have an array of Int and for each of this Int I want a UIViewController to display one of them in a UILabel.
To accomplish this I've created in my Main storyboard both the UIPageViewController and UIViewController with its UILabel.
This is the code I've written for the UIPageViewController:
import UIKit
import RealmSwift
class PageViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var subViewControllers: [UIViewController] = {
return [
self.createNewVC(withName: "CounterViewController"),
self.createNewVC(withName: "CounterViewController"),
self.createNewVC(withName: "CounterViewController")
]
}()
var dataArray: [Int] = [30, 134, 345]
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
setViewControllers([subViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
// Creates a new ViewController
func createNewVC(withName name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
// Returns the previous ViewController
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex: Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex <= 0) {
return nil
}
if let previous = subViewControllers[currentIndex - 1] as? CounterViewController {
previous.daysLeftLabel.text = String(dataArray[currentIndex])
return previous
} else {
return nil
}
}
// Returns the next ViewController
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex: Int = subViewControllers.index(of: viewController) ?? 0
if (currentIndex >= subViewControllers.count - 1) {
return nil
}
if let next = subViewControllers[currentIndex + 1] as? CounterViewController {
next.daysLeftLabel.text = "0"
return next
} else {
return nil
}
}
}
and this is the code for the UIViewController
import UIKit
class CounterViewController: UIViewController {
#IBOutlet weak var daysLeftLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
NB: "CounterViewController" is also the UIViewController StoryBoardID
The problem is that when I scroll for the first time to the right the app crashes at this line next.daysLeftLabel.text = "0" saying that it found a nil value while unwrapping Optional.
What am I doing wrong here?
Also I want to take the opportunity to ask something.
In this case, as you may have guessed, I am using a UIPageViewController which holds the same UIViewControllers but with different data displayed, which is the best way to accomplish this? Am I doing it correctly or there is an easier and cleaner way?
This line crashes
next.daysLeftLabel.text = "0"
because daysLeftLabel is nil until the VC loads , you need
class CounterViewController: UIViewController {
#IBOutlet weak var daysLeftLabel: UILabel!
var sendedText = ""
override func viewDidLoad() {
super.viewDidLoad()
daysLeftLabel.text = sendedText
// Do any additional setup after loading the view.
}
}
Then use
next.sendedText = "0"
Related
I got an issue where my UI button is not as responsive. normally i can tap it like normal but when the view is loaded in the UIpageviewcontrol the button has to be pressed hard to get a response out of. Why is that? Thanks alot
UIpageViewControl:
import UIKit
class mainPageViewController: UIPageViewController ,
UIPageViewControllerDelegate , UIPageViewControllerDataSource{
lazy var orderedViewControllers : [UIViewController] = {
return [self.newVC(viewController: "messages"),
self.newVC(viewController: "discover"),
self.newVC(viewController: "profile")
]
}()
var pageControl = UIPageControl()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self;
if let firstVC = orderedViewControllers.first{
setViewControllers([firstVC],
direction: .forward,
animated: true,
completion: nil);
self.delegate = self;
// ads dots if i want later
//configPageControl()
}
// Do any additional setup after loading the view.
}
func newVC(viewController: String ) -> UIViewController{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController);
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewContoller = pageViewController.viewControllers![0];
self.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewContoller)!
}
func configPageControl(){
pageControl = UIPageControl(frame: CGRect(x: 0, y: UIScreen.main.bounds.maxY - 50, width: UIScreen.main.bounds.width, height: 50))
pageControl.numberOfPages = orderedViewControllers.count;
pageControl.currentPage = 0;
pageControl.tintColor = UIColor.white;
pageControl.pageIndicatorTintColor = UIColor.white;
pageControl.currentPageIndicatorTintColor = UIColor.red;
self.view.addSubview(pageControl);
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of : viewController)else{
return nil;
}
let previousIndex = viewControllerIndex - 1;
guard previousIndex >= 0 else{
// infinatly swipe in a loop
return orderedViewControllers.last;
// no swipe back
//return nil;
}
guard orderedViewControllers.count > previousIndex else{
return nil;
}
return orderedViewControllers[previousIndex];
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of : viewController)else{
return nil;
}
let nextIndex = viewControllerIndex + 1;
guard orderedViewControllers.count != nextIndex else{
// infinatly swipe in a loop
return orderedViewControllers.first;
// no swipe back
//return nil
}
guard orderedViewControllers.count > nextIndex else{
return nil;
}
return orderedViewControllers[nextIndex];
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
ViewController with button that IS NOT ACTING LIKE NORMAL
import UIKit
class ProfileViewController: UIViewController {
#IBOutlet weak var moneyOutlet: UILabel!
#IBOutlet weak var scoreOutlet: UILabel!
#IBOutlet weak var profilePicOutlet: UIImageView!
#IBOutlet weak var usernameOutlet: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func trophiesPressed(_ sender: Any) {
// not triggering as expected
print("hello world")
}
}
Try adding this to your mainPageViewController class:
override func touchesShouldCancel(in view: UIView) -> Bool {
if view is UIButton {
return true
}
return super.touchesShouldCancel(in: view)
}
On a side note, use leading uppercase for class names, leading lowercase for variable and function names:
class MainPageViewController: UIPageViewController ...
I m trying to design one screen which contain swipe part. I have one screen with one imageview and at the bottom there are two buttons. I want to swipe only the imageview not the bottom area. Screen is like this
At the moment the problem it is swiping the whole screen including buttons too.
Here is my code
ViewController.swift
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
var pageImages:NSArray!
var pageViewController:UIPageViewController!
override func viewDidLoad() {
super.viewDidLoad()
pageImages = NSArray(objects: "startscreen1","startscreen2","startscreen3")
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("MyPageViewController") as! UIPageViewController
self.pageViewController.dataSource = self
let initialContenViewController = self.pageTutorialAtIndex(0) as TutorialScreenViewController
let viewControllers = NSArray(object: initialContenViewController)
self.pageViewController.setViewControllers(viewControllers as! [UIViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
/* self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)*/
self.pageViewController.view.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.height-100)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pageTutorialAtIndex(index: Int) -> TutorialScreenViewController
{
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("TutorialScreenViewController") as! TutorialScreenViewController
pageContentViewController.imageFileName = pageImages[index] as! String
pageContentViewController.pageIndex = index
return pageContentViewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! TutorialScreenViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{
return nil
}
index--
return self.pageTutorialAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! TutorialScreenViewController
var index = viewController.pageIndex as Int
if((index == NSNotFound))
{
return nil
}
index++
if(index == pageImages.count)
{
return nil
}
return self.pageTutorialAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return pageImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int // The selected item reflected in the page indicator.
{
return 0
}
}
TutorialScreenViewController.swift
#IBOutlet weak var myImageView: UIImageView!
var imageFileName: String!
var pageIndex:Int!
var width:CGFloat!
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var registerButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
myImageView.image = UIImage(named:imageFileName)
}
You can use view controller containment:
Create a standard view controller that has the the two buttons and "container" view. Just search for "container" in the object library on the right panel in Interface builder and then drag that onto your main view controller's scene:
Obviously, set up your constraints so that this container view is properly laid out with the buttons.
Now control-drag from the container view to your page view controller, and choose the "embed" segue.
You'll end up with a scene that looks like:
Now you can enjoy the page view controller functionality from within a standard view controller with other buttons.
I took one of the most popular online tutorial ( http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/ ) about making UIPageViewController with Storyboard and tried to convert it into Swift.
As one could expect, I encountered some issues.
Here is my code:
PageContentViewController.swift
import UIKit
class PageContentViewController: UIViewController {
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
var pageIndex:Int!
var titleText:String!
var imageFile:String!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.backgroundImageView.image = UIImage(named: self.imageFile)
self.titleLabel.text = self.titleText
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
ViewController.swift
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
#IBAction func startWalkthrough(sender: AnyObject) {
}
var pageViewController:UIPageViewController!
var pageTitles:Array<String>!
var pageImages:Array<String>!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.pageTitles = ["Over 200 Tips and Tricks", "Discover Hidden Features", "Bookmark Favorite Tip", "Free Regular Update"]
self.pageImages = ["page1.png", "page2.png", "page3.png", "page4.png"]
// Create page view controller
self.pageViewController = self.storyboard!.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
self.pageViewController.dataSource = self
var startingViewController:PageContentViewController = self.viewControllerAtIndex(0)!
var viewControllers:Array<PageContentViewController> = [startingViewController]
self.pageViewController.setViewControllers(viewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30)
self.addChildViewController(pageViewController)
self.view.addSubview(pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
//helper method to get the index of current page
func viewControllerAtIndex(index: Int)-> PageContentViewController?{
if((self.pageTitles.count == 0) || (index >= self.pageTitles.count))
{
return nil
}
var pageContentViewController: PageContentViewController = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
pageContentViewController.imageFile = self.pageImages[index]
pageContentViewController.titleText = self.pageTitles[index]
pageContentViewController.pageIndex = index
return pageContentViewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?{
var index: Int? = PageContentViewController().pageIndex!
if ((index! == 0) || index! == NSNotFound)
{
return nil
}
else {
index! = index! - 1
println(index!)
return viewControllerAtIndex(index!)
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?{
var index:Int? = PageContentViewController().pageIndex!
println(index!)
if (index! == NSNotFound)
{
return nil
}
index! = index! + 1
if(index! == self.pageTitles.count)
{
return nil
}
println(index!)
return viewControllerAtIndex(index!)
}
}
Here are the issues:
1) When I run the app, it loads the first picture, but swiping right leads to runtime error which suggests that index of page is equal to nil. Then I tried to initialize index inside PageContentViewController
var pageIndex:Int! = 0
but this does not work. Index is not nil anymore, but whole logic of swiping right and left is down. It's counting only from 0 to 1 then gets back to 0 instead of incrementing to 2.
2) Second thing is that my label and button aren't visible, seems like they are covered by the picture, don't know why
If anybody got any idea about this index issue, I would be very grateful
Thanks in advance!
You are not using a local variable pageViewController.pageIndex! but static PageContentViewController().pageIndex! in delegate methods. This is why the pageIndex is always the same.
Here is how I did my implementation, so you can do something similar.
func viewControllerAtIndex(index: Int) -> FeaturedRecipeViewController {
let featuredRecipeViewController = FeaturedRecipeViewController(nibName: "FeaturedRecipeViewController", bundle: nil)
featuredRecipeViewController.pageIndex = index
return featuredRecipeViewController
}
Note here that I am setting an index to a ViewController object.
And when I'm looking for a next ViewController I do it like this:
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = (viewController as! FeaturedRecipeViewController).pageIndex // This is the line you are looking for
if index == pages.count - 1 {
index = 0
} else {
index++
}
return viewControllerAtIndex(index)
}
I altered the code since it does something really weird in my case, but this should be similar to the thing you are looking for.
Creating a page dynamically using this code, which creates a PageViewController
import UIKit
class PageViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pvc:UIPageViewController?
var testArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pvc = UIPageViewController(
transitionStyle: .Scroll,
navigationOrientation: .Horizontal,
options: nil )
// Make this object the datasource and delegate for the PageViewController
self.pvc?.delegate = self
self.pvc?.dataSource = self
// Populate an array of test content for the pages
testArray = ["Page 1", "Page 2", "Page 3"]
// Create an array of child pages
var swipePage:SwipePageController = SwipePageController()
println("Page view Controller: \(testArray[0])")
swipePage.name = testArray[0]
var pageArray: NSArray = [swipePage]
self.pvc?.setViewControllers(pageArray, direction: .Forward, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index:Int! = (viewController as SwipePageController).index!
if (index >= self.testArray.count){
return nil
}
index + 1
return viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index:Int! = (viewController as SwipePageController).index!
if index == NSNotFound {
return nil
}
index - 1
return viewControllerAtIndex(index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return self.testArray.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
let page = self.pvc?.viewControllers[0] as SwipePageController
let name = page.name
return find(self.testArray, name!)!
}
func viewControllerAtIndex(index: Int) -> SwipePageController?
{
if self.testArray.count == 0 || index >= self.testArray.count
{
return nil
}
// Create a new view controller and pass suitable data.
let swipeView = SwipePageController()
swipeView.name = self.testArray[index]
swipeView.index = index
return swipeView
}
The println() about a 1/3 of the way down the code, does display the value I expect to see. The code for the ViewController I create dynamically is here:
import UIKit
class SwipePageController: UIViewController {
// var to hold the var to display to the interface
var name:String?
var index:Int?
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
println(name!)
if let passedName = name? {
self.testLabel.text = passedName
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
println(name!) does show the correct value as well. However, I cannot populate the label with the value. I get the following error:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
I'm at my wit's end...I cannot figure out what's not working. Also, see the screen shot of my Xcode interface where the error occurs:
Can anyone help?
I think the problem is the way you are instantiating the SwipePageController.
Instead of this:
var swipePage:SwipePageController = SwipePageController()
Try this:
var swipePage: SwipePageController = UIStoryboard(name: "YourStoryboardName", bundle: nil).instantiateViewControllerWithIdentifier("YourViewControllerId")
Replace "YourStoryboardName" with the actual name of your storyboard file, and "YourViewControllerId" with the Storyboard Id of your Swipe Page Controller. If you haven't already given it a Storyboard Id, make sure to set this in the Identity inspector.
I've been having a terribly hard time trying to create a Tutorial flow for my app using swift...Here's what I want to do:
User comes to start screen which has button to allow them to "take the tour" This is Storyboarded and works fine.
User presses button, and tutorial appears from push segue.
After tutorial user can go on to perform other functions.
I first tried to create the UIPageViewController by simply dragging it into the storyboard and attaching a class. That didn't work. So I found a tutorial that I used to create this:
import UIKit
class TutorialViewController: UIViewController, UIPageViewControllerDataSource {
var pageController: UIPageViewController?
let pageAssets:[String] = ["blender.png", "click.png", "share.png"]
var currentIndex: Int = 0
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pageController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
pageController!.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height);
pageController!.dataSource = self
var initialViewController: PageContentViewController = self.viewControllerAtIndex(0)
let viewControllers: NSArray = [initialViewController]
pageController!.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: nil)
addChildViewController(self.pageController!)
self.view.addSubview(pageController!.view)
pageController!.didMoveToParentViewController(self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - DataSource protocol conformance
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var idx: Int = (viewController as PageContentViewController).itemIndex
if (idx == 0) {
return nil
}
idx--
return self.viewControllerAtIndex(idx)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var idx = (viewController as PageContentViewController).itemIndex
if (idx > pageAssets.count){
return nil
}
idx++
return self.viewControllerAtIndex(idx)
}
func viewControllerAtIndex(index:Int) -> PageContentViewController {
let childViewController:PageContentViewController = PageContentViewController()
childViewController.itemIndex = index
childViewController.imageName = pageAssets[index]
currentIndex = index
return childViewController
}
// MARK: - Navigation
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pageAssets.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
/*
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
} */
}
And here is the PageContentViewController:
import UIKit
class PageContentViewController: UIViewController {
#IBOutlet weak var testLabel: UILabel!
#IBOutlet weak var tutorialImageView: UIImageView!
// MARK: - Variables
#IBOutlet weak var contentImageView: UIImageView!
var itemIndex: Int = 0
//computed member which calls didSet that actually creates the UIImage that's displayed
var imageName: String = ""{
didSet {
if let imageView = contentImageView {
tutorialImageView.image = UIImage(named: imageName)
}
}
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
contentImageView?.image = UIImage(named: imageName)
}
}
So first, how do I get to this Class using a Segue, OR from my AppDelegate?
I know...long question, but I'm really outdone by trying to create a PageView intro. Any and all help is appreciated...