I just created a UIPageViewController with page content view controller that consists of single ImageView and a Button. It works almost flawlessly, but it has a major issue when I try to slide through the views in vertical view only, with Aspect Fill mode set for my ImageView (which is pretty much basically setup for this).
You can see this behavior right here: https://youtu.be/2yl6UXbUGQg
I am using autolayout, Button constraints are aligned to ImageView (full size), ImageView constraints are set to Superview...
Code:
1) UIPageViewController:
class ViewController: UIPageViewController, UIPageViewControllerDataSource {
var arrPageTitle: NSArray = NSArray()
var arrPagePhoto: NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
arrPageTitle = ["page 1", "page 2", "page 3"]
arrPagePhoto = ["slider-1.jpg", "slider-2.jpg", "slider-3.jpeg"]
self.dataSource = self
self.setViewControllers([getViewControllerAtIndex(0)] as [UIViewController],
direction: .Forward,
animated: false,
completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if ((index==0) || (index == NSNotFound)) {
return nil
}
index -= 1
return getViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if index == NSNotFound {
return nil
}
index += 1
if index == arrPageTitle.count {
return nil
}
return getViewControllerAtIndex(index)
}
func getViewControllerAtIndex(index: NSInteger) -> PageContentViewController {
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.strPhotoName = "\(arrPagePhoto[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
}
2) Content view controller, UIViewController:
class PageContentViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var labelTitle: UIButton!
var pageIndex: Int = 0
var strTitle: String!
var strPhotoName: String!
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = UIImage(named: self.strPhotoName)
self.labelTitle.setTitle(self.strTitle, forState: .Normal)
}
}
Your image views from one view controller are spilling over to other view controllers because you need to set clipsToBounds to true on your image views. This will prevent the contents of the image view from going beyond the image view's bounds.
Check Clip Subviews to prevent image view size stretched.
Related
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"
Hi am following a tutorial on UIPageViewController to generate a gallery of pictures in my project http://www.theappguruz.com/blog/easy-steps-to-implement-uipageviewcontroller-in-swift even download the demo and it works but in my if I get an error and the application falls.
fatal error: unexpectedly found nil while unwrapping an Optional value
at next my code.
PageContentViewController.swift:
import UIKit
class PageContentViewController: UIPageViewController {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var imageView: UIImageView!
var pageIndex: Int = 0
var strTitle: String!
var strPhotoName: String!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: strPhotoName)
lblTitle.text = strTitle
}
}
ViewController2.swift
import UIKit
class ViewController2: UIPageViewController, UIPageViewControllerDataSource
{
var arrPageTitle: NSArray = NSArray()
var arrPagePhoto: NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
arrPageTitle = ["This is The App Guruz", "This is Table Tennis 3D", "This is Hide Secrets"];
arrPagePhoto = ["1.jpg", "2.jpg", "3.jpg"];
self.dataSource = self
self.setViewControllers([getViewControllerAtIndex(0)] as [UIViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
// MARK:- UIPageViewControllerDataSource Methods
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if ((index == 0) || (index == NSNotFound))
{
return nil
}
index -= 1;
return getViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if (index == NSNotFound)
{
return nil;
}
index += 1;
if (index == arrPageTitle.count)
{
return nil;
}
return getViewControllerAtIndex(index)
}
// MARK:- Other Methods
func getViewControllerAtIndex(index: NSInteger) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.strPhotoName = "\(arrPagePhoto[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
}
I believe Joakin is right, you are forcing the unwrap of an optional value, you need to either initialize your values when you declare them or treat them as optionals and unwrap them as you need them.
Try this:
var strTitle: String?
var strPhotoName: String?
override func viewDidLoad() {
super.viewDidLoad()
if let photoName = strPhotoName {
imageView.image = UIImage(named: photoName)
}
if let title = strTitle {
lblTitle.text = title
}
}
Then, somewhere in your code you need to specify the name of the image (make sure the image exists in your project) and the name of the title. If you dont specify them, the updated lines of code will not get executed.
Read more about optional values here
var strPhotoName: String!
It is literally nil. You will need to assign it a image name.
Let's say your image is called cloudsImage.png
You would set your var strPhotoName like this in viewDidLoad if you want.
override func viewDidLoad() {
super.viewDidLoad()
strPhotoName = "cloudsImage.png"
imageView.image = UIImage(named: strPhotoName)
lblTitle.text = strTitle
}
import UIKit
class PageContentViewController: UIViewController {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var imageView: UIImageView!
var pageIndex: Int = 0
var strTitle: String!
var strPhotoName: String!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: strPhotoName)
lblTitle.text = strTitle
}
}
the main error was that the class of the page was wrong now the problem is solved.
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.
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...