UIPageViewController : move programmatically page doesn't work - swift

I try to turn the page by touching a button on one of the two navigation controllers(each navigationcontroller is related to a viewcontroller) but when i call a function in the pageviewcontroller to change index and change page it doesn't work at all...
I tried setViewController.
I would like to do like in the snapchat app, a navbuttonitem which can slide to the left or to the right.
Thank you in advance
here is the code of the pageviewcontroller :
class PageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
}

First, why don't you stop each instance of your controllers rather than re-instantiating it ? Here is the line :
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as UINavigationController
}
return nil}
You could create a local array containing all your instances..
Then, to move programmatically between your pages, you can use setViewController this way :
func showAViewController(){
let startVC : NSArray = [viewController]
setViewControllers(startVC as! [AnyObject], direction: .Reverse, animated: true, completion: nil)
}
Where direction is .Reverse or .Forward depending on the position of the destination view controller relative to the present view controller.
Here is an example of a test program I wrote, it worked for me :
class RootVC: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var viewController1 : UIViewController!
var viewController2 : UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
viewController1 = storyboard?.instantiateViewControllerWithIdentifier("viewController1") as! UIViewController
viewController1.title = "viewController1"
viewController2 = storyboard?.instantiateViewControllerWithIdentifier("viewController2") as! UIViewController
viewController2.title = "viewController2"
var startingVC : NSArray = [viewController1]
setViewControllers(startingVC as! [AnyObject], direction: .Forward, animated: false, completion: nil)
Notifications.addObserver(self, selector: "showViewController1", name: "showViewController1", object: nil)
Notifications.addObserver(self, selector: "showViewController2", name: "showViewController2", object: nil)
}
deinit{
Notifications.removeObserver(self)
}
func showViewController1(){
let startVC : NSArray = [viewController1]
setViewControllers(startVC as! [AnyObject], direction: .Forward, animated: true, completion: nil)
}
func showViewController2(){
let startVC : NSArray = [viewController1]
setViewControllers(startVC as! [AnyObject], direction: .Forward, animated: true, completion: nil)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
switch viewController.title!{
case "viewController1": return nil
case "viewController2": return viewController1
default: return nil
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
switch viewController.title!{
case "viewController1": return viewController2
case "viewController2": return viewController1
default: return nil
}
}}
NSNotificationsis not a mandatory, but I like to use it to avoid heavy delegation.

Related

How to set the PageViewController to cover the whole screen and not be modal?

I am implementing a UIPageViewController to my app to try to build a UI like Tinder, in which you can swipe left and right to not only like or dislike a person, but to navigate different screens, i.e. chat screen, profile screen, matches screen etc.
In my case, after signing in, a UIPageViewController that contains 4 other UIViewControllers will pop up.
However, the UIPageViewController is modal and doesn't cover the whole screen(as there is a small gap at the top which allows the user to swipe the modal down and away).
I tried using code like this:
self.window = UIWindow(frame: UIScreen.main.bounds)
if let window = self.window {
window.rootViewController = PageViewController()
window.makeKeyAndVisible()
}
at my AppDelegate, or setting Full Screen at the storyboard, but did't work.
I wonder how I should do this?
Or maybe UIPageViewController is not the right choice to achieve this swipe from screen to screen navigation style Tinder has?
Anyway, here is the code of my PageViewController:
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var controllers = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
let vc = TodayPicksViewController()
controllers.append(vc)
let vc1 = TopPicksViewController()
vc1.view.backgroundColor = .yellow
controllers.append(vc1)
let vc2 = ChatViewController()
vc2.view.backgroundColor = .gray
controllers.append(vc2)
let vc3 = (storyboard?.instantiateViewController(withIdentifier: String(describing: ProfileViewController.self)) as? ProfileViewController)!
controllers.append(vc3)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now()+2, execute: {
self.presentPageVC()
})
}
func presentPageVC() {
guard let first = controllers.first else {
return
}
let vc = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: nil)
vc.delegate = self
vc.dataSource = self
vc.setViewControllers([first],
direction: .forward,
animated: true,
completion: nil)
present(vc, animated: true)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index > 0 else {
return nil
}
let before = index - 1
return controllers[before]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index < (controllers.count - 1) else {
return nil
}
let after = index + 1
return controllers[after]
}
}
By default when you present a ViewController in Swift it doesn't cover the fullscreen. To make it cover the fullscreen you need to set the modalPresentationStyle on the ViewController.
So in your presentPageVC method you need to add the following line :
vc.modalPresentationStyle = .fullScreen
So your method will now look like this:
func presentPageVC() {
guard let first = controllers.first else {
return
}
let vc = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: nil)
vc.delegate = self
vc.dataSource = self
vc.setViewControllers([first],
direction: .forward,
animated: true,
completion: nil)
vc.modalPresentationStyle = .fullScreen // <- add this before presenting your ViewController
present(vc, animated: true)
}
To read more about the different presentation styles that you can have check out the documentation here.

Is there a workaround to bring the method "present" in a UIimageView class?

I created a class for my Logo UIimageView which is on every ViewController in my app. Now I want to create a tapGesture to jump back to the Home ViewController when it is tapped like Logos on Homepages back to index. How can I get the present method to work in the class UIimageView?
import Foundation
import UIKit
class LogoImageView: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
let TapGesture = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped))
self.addGestureRecognizer(TapGesture)
}
#objc func imageTapped() {
let mainStoryboad = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let destinationViewController = mainStoryboad.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
return
}
present(destinationViewController, animated: true, completion: nil)
}
}
I´ve got the solution for my problem. I added an extension to check the current ViewController to use the present method. Now I´m able to add every UIimageview this class and it brings me back to the root viewcontroller.
import Foundation
import UIKit
class LogoImageView: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
let TapGesture = UITapGestureRecognizer(target: self, action: #selector(self
.imageTapped))
self.addGestureRecognizer(TapGesture)
}
#objc func imageTapped() {
let mainStoryboad = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let destinationViewController = mainStoryboad.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
return
}
guard let currentViewController = UIApplication.shared.keyWindow?.topMostViewController() else {
return
}
destinationViewController.modalTransitionStyle = .flipHorizontal
currentViewController.present(destinationViewController, animated: true, completion: nil)
}
}
extension UIWindow {
func topMostViewController() -> UIViewController? {
guard let rootViewController = self.rootViewController else {
return nil
}
return topViewController(for: rootViewController)
}
func topViewController(for rootViewController: UIViewController?) -> UIViewController? {
guard let rootViewController = rootViewController else {
return nil
}
guard let presentedViewController = rootViewController.presentedViewController else {
return rootViewController
}
switch presentedViewController {
case is UINavigationController:
let navigationController = presentedViewController as! UINavigationController
return topViewController(for: navigationController.viewControllers.last)
case is UITabBarController:
let tabBarController = presentedViewController as! UITabBarController
return topViewController(for: tabBarController.selectedViewController)
default:
return topViewController(for: presentedViewController)
}
}
}

Swift 3 - UIPageViewController - Presenting view controllers on detached view controllers is discouraged

Iam using UIPageViewController for my app's first open. And third page is my login page. This contains facebook login. When I clicked facebook login button, opening empty page and xcode give me this output "Presenting view controllers on detached view controllers is discouraged < tapusor.LoginPage: 0x101f09a00 >."
When I dont use UIPageViewController this button is working. So there is my code. How can I fix this issue?
import UIKit
class MyPageViewController: UIPageViewController,
UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pageContainer: UIPageViewController!
// The pages it contains
var pages = [UIViewController]()
// Track the current index
var currentIndex: Int?
private var pendingIndex: Int?
override func viewDidLoad() {
super.viewDidLoad()
// Setup the pages
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "firstVC")
let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "secondVC")
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "LoginPage")
pages.append(page1)
pages.append(page2)
pages.append(page3)
pageContainer = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageContainer.delegate = self
pageContainer.dataSource = self
pageContainer.setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
// Add it to the view
view.addSubview(pageContainer.view)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.index(of: viewController)!
if currentIndex == 0 {
return nil
}
let previousIndex = abs((currentIndex - 1) % pages.count)
return pages[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.index(of: viewController)!
if currentIndex == pages.count-1 {
return nil
}
let nextIndex = abs((currentIndex + 1) % pages.count)
return pages[nextIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
pendingIndex = pages.index(of: pendingViewControllers.first!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Also I can try this
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "LoginPage")
to this
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "LoginPage") as! LoginPage
or this
let page3: LoginPage! = storyboard.instantiateViewController(withIdentifier: "LoginPage") as! LoginPage
Not worked.
Create delegate for your LoginPage controller with function what will notify about Facebook login button pressed. Something like this:
protocol LoginPageDelegate: class {
func loginPageDidPressFacebookButton(_ controller: LoginPage)
}
Now add delegate property to your LoginPage class
class LoginPage: UIViewController {
weak var delegate: LoginPageDelegate?
}
Now you need to inform delegate when user press Facebook login button
#IBAction func facebookButtonPressed(button: UIButton) {
self.delegate?.loginPageDidPressFacebookButton(self)
}
And not you can subscribe to this delegate in your MyPageViewController.
First you need to make it class conform to LoginPageDelegate
class MyPageViewController: UIPageViewController,
UIPageViewControllerDataSource, UIPageViewControllerDelegate, LoginPageDelegate {
//MARK: LoginPageDelegate methods
func loginPageDidPressFacebookButton(_ controller: LoginPage) {
//present what you need here
}
}
And now before you show you LoginPage:
let page3: LoginPage! = storyboard.instantiateViewController(withIdentifier: "LoginPage")
page3.delegate = self

How do i load a viewcontroller from a xib file?

I tried to load a viewcontroller from a custom cell using delegates.But i get nil from the set delegate!
Here is a sample if anyone can help!
1. In Cell
protocol hotelFindDelegate{
func modalDidFinished(modalText: String)
}
class hotelFindCell: UITableViewCell {
var delegate:hotelFindDelegate?
#IBAction func findButton(sender: AnyObject) {
self.delegate!.modalDidFinished("HELLO")
print("Damn nothing works")
}
2. In Main View
class MainViewController:hotelFindDelegate {
let modalView = hotelFindCell()
override func viewDidLoad() {
super.viewDidLoad()
self.modalView?.delegate = self
}
func modalDidFinished(modalText: String){
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HotelListVC") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
self.modalView.delegate = self
print(modalText)
}
To load view controller from XIB do the following steps.
let settingVC : SettingsViewController = SettingsViewController(nibName :"SettingsViewController",bundle : nil)
later on you can push the same view controller object like
self.navigationController?.pushViewController(settingsVC, animated: true)
NSBundle.mainBundle().loadNibNamed("viewController", owner: self, options: nil)

Delete Image from UIPageViewController

I have a PageViewController set up so that I can add photos from camera or library and the pageviewcontroller resets itself every time the imagepickercontroller is called so the pageviewcontrol updates and shows. It works perfectly (i think). What i know want to do is have the option to delete one of the photos then call the same reset.
The problem is, i can not get an accurate index value of the current image being displayed in the pageviewcontroller. I try to grab the index from the viewControllerBeforeViewController or AfterViewController or the pagecontrolleratindex... but the index does not follow correctly the way the pagecontrol is designed. I read about willtransitiontoviewcontrollers and didfinsihanimating, but those are not being called, i am thinking because i am getting the images from an array and not new view controllers.
My method to delete works, but the index is not accurate so even though the second image is shown the first image could be the one deleted.
Is there a good way to get the current displayed array index so i can delete the image from array? //this array holds images from imagepicker, i call the restartviewcontrollers evertime there is an imagepicked to update the number of pages and this works.
UPDATE
var pageViewController:UIPageViewController!
var pageImages: [UIImage]? = [] //this array holds images from imagepicker, i call the restartviewcontrollers evertime there is an imagepicked to update the number of pages and this works.
var deletePageIndex: Int = 0
func pageViewControllerAtIndex(index: Int) -> PhotoContentPageViewController {
if (self.pageImages!.count == 0 || index >= self.pageImages!.count) {
return PhotoContentPageViewController()
}
var vc = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageContentStoryBoard") as! PhotoContentPageViewController
vc.imageFileName = pageImages![index] as! UIImage
vc.pageIndex = index
return vc
}
func restartViewController() {
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
}
func startViewControllers() {
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageViewControllerStoryBoard") as! UIPageViewController
self.pageViewController.dataSource = self
pageViewController.delegate = self
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, -10, self.view.frame.size.width, self.view.frame.size.height-30)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! PhotoContentPageViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound) {
return nil
}
index--
return self.pageViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! PhotoContentPageViewController
var index = vc.pageIndex as Int
if (index == NSNotFound) {
return nil
}
index++
if (index == self.pageImages!.count) {
return nil
}
return self.pageViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if (!completed) {
return
}
}
`
If you actually are using a UIPageViewControllerDelegate, you can use willtransitiontoviewcontrollers but that method only works if you set the delegate property of the page view controller to your view controller. That way, you can simply get the index of the current view controller in page view controller and use it for deletion.
myPageViewController.delegate = self
And make sure your view controller implements UIPageViewControllerDelegate
UPDATE
Assuming that you have an array of you view controllers, you could implement your data source like this:
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if let index = pages.indexOf(viewController)?.advancedBy(1) where index < pages.count {
return pages[index]
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if let index = pages.indexOf(viewController)?.advancedBy(-1) where index > -1 {
return pages[index]
}
return nil
}