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

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

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.

UIPageViewController with only 1 UIViewController and infinite loop

First at all, I am not using UICollectionView because I need put my Slider inside of a ViewContainer and it's not allowed put repeatable elements in it.
As my question title says, I want to get work a UIPageViewController with only 1 UIViewController and infinite loop.
This is my current code:
class ListPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages: [UIViewController] = []
let viewModel = ListPageViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.delegate = self
delegate = self
dataSource = self
if let storyboard = storyboard {
pages = [
storyboard.instantiateViewController(withIdentifier: "listViewController"),
storyboard.instantiateViewController(withIdentifier: "listViewController")
]
setViewControllers([pages.first!], direction: .forward, animated: true)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return brother(controller: viewController)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return brother(controller: viewController)
}
func brother(controller: UIViewController) -> UIViewController? {
if controller == pages.first { return pages.last }
return pages.first
}
}
The problem is that I am not able to reference an unique UIViewController because I'm create two instead of one, and I need manipulate that child UIViewController from my UIPageViewController.
Some help?

Push UIViewController with Alert without UINavigationController

I have a button in UITableViewController. when I press this button, alert controller appears asks if the user wants to go to the cart page or stay in the same page, as follows,
if user?.uid != nil{
let title = "Item is added to your cart "
let alert = AlertController.instance(title: title)
present(alert, animated: true, completion: nil)
} else{
// Alert asks user to register..
}
AlertController is in different class and I created it with custom animation.
I want when user press (go to cart page) button, the cart page is displayed as if it was as (show,"push") segue.
This is how I pushed the view controller programmatically in alert controller class.
#IBAction func showCartButton(_ sender: Any) {
//---> go to cart page ...
print("cart button pressed")
//-----> this code is working fine with other UIviewcontrollers (however it is 'not working in UItableviewcontroller)
let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
navigationController?.pushViewController(CartViewController, animated: true)
//-----> this code shows the cart page without the navigation and tabs..
// let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
// let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
// self.present(CartViewController, animated:true, completion:nil)
}
The push view controller is not working with Alert controller, I am not sure if it is because I used the navigation with the alert, and if so, is there a way I can push the view controller??
This is the whole AlertController Class I created in seperate storyboard and put it as initial view controller..
import UIKit
class AlertController: UIViewController{
#IBOutlet weak fileprivate var AddProductToCartLabel: UILabel!
#IBOutlet weak fileprivate var cartButton: UIButton!
#IBOutlet weak fileprivate var shoppingButton: UIButton!
#IBOutlet weak fileprivate var container: UIView!
var text = String()
override func viewDidLoad() {
setContainer()
setCartButton()
setShoppingButton()
}
override func viewDidLayoutSubviews() {
layoutContainer()
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func showCartButton(_ sender: Any) {
//---> go to cart page ...
print("cart button pressed")
let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
navigationController?.pushViewController(CartViewController, animated: true)
// let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
// let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
// self.present(CartViewController, animated:true, completion:nil)
}
#IBAction func completeShopButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
fileprivate extension AlertController{
// to set the view above
func setContainer() {
let shape = CAShapeLayer()
shape.fillColor = UIColor.white.cgColor
container.backgroundColor = UIColor.clear
container.layer.insertSublayer(shape, at: 0)
}
func setCartButton(){
cartButton.clipsToBounds = true
//cartButton.layer.cornerRadius = 10
}
func setShoppingButton(){
shoppingButton.clipsToBounds = true
shoppingButton.layer.borderColor = UIColor.darkGray.cgColor
shoppingButton.layer.borderWidth = 1
//shoppingButton.layer.cornerRadius = 10
}
}
fileprivate extension AlertController{
// design the size of the container
func layoutContainer() {
let path = UIBezierPath(roundedRect: container.bounds, cornerRadius: 10)
let layer = container.layer.sublayers?.first as! CAShapeLayer
layer.path = path.cgPath
}
}
extension AlertController{
static func instance(title: String?) -> AlertController{
let storyboard = UIStoryboard(name: String(describing: self), bundle: Bundle(for: self))
let controller = storyboard.instantiateInitialViewController() as! AlertController
controller.text = title!
return controller
}
}
The problem is that when you try to push CartViewController, there is no navigation controller to push it on.
You present AlertController modally, and then try to do:
navigationController?.pushViewController(CartViewController, animated: true). If you put a breakpoint here and print out navigationController, I expect it is nil.
I suggest you have AlertController delegate back to your UITableViewController that is inside a navigation controller, and have the tableview controller push to the next screen. You could also present AlertController inside of a navigation view, but this seems unnecessary.

UIViewControllerTransitioningDelegate on tvOS not being called

So I'm trying to do an animated transition between viewcontrollers on tvOS 10.
The UIViewControllerTransitioningDelegate protocol is available on tvOS so I assumed I could animate it as well. But for some reason none of the functions are ever called when presenting the new viewcontroller.
I have no idea what I'm doing wrong since I'm doing basically the exact same on iOS.
Here is the code I'm using:
Presenting code
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = ResultViewControllerTransitionManager()
viewController.modalPresentationStyle = .custom
navigationController?.present(viewController, animated: true, completion: nil)
}
Transition Delegate
class ResultViewControllerTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var duration = 0.5
var isPresenting = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
(...)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = false
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = true
return self
}
}
You can implement UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate protocols inside your first view controller. Your first UIViewController may have the following code:
class ViewController: UIViewController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = self
viewController.modalPresentationStyle = .custom
self.present(viewController, animated: true, completion: nil)
}
var duration = 0.5
var isPresenting = false
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 2.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
}
}
Also, I present a new controller from the first one (not from the navigationController).
So apparently initialising the animationmanager in the showStuff() function was the problem. Storing it as a property in the Main viewcontroller and passing it along did work.
Wouldn't the issue be that you are using a navigationController, so that navigationController?.delegate needs to equal your ResultViewControllerTransitionManager()?
We have one at work using a push animation (should be the same issue), but we set
navigationController?.delegate = transitionDelegate

UIPageViewController : move programmatically page doesn't work

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.