I just started programming with swift a few months ago and I am currently working on a few random projects to help me learn the language better. Currently, I am trying to create a UI similar to Snapchat's by using a UIPageViewController. The horizontal scroll is working and I can navigate through the pages correctly but I want to change the initial view controller that is presented. In my code below, I have three ViewControllers (vc1, vc2, vc3). I want to keep them in that same order but display vc2 first, so basically have it in the middle of the other two. All help is appreciated. Thanks!
import UIKit
import FirebaseDatabase
import FirebaseAuth
import Firebase
class RootPageViewController: UIPageViewController, UIPageViewControllerDataSource {
let Cards = CardsVC()
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "MessagesVC")
let vc2 = sb.instantiateViewController(withIdentifier: "CardsVC")
let vc3 = sb.instantiateViewController(withIdentifier: "ProfileVC")
return[vc1, vc2, vc3]
}()
override func viewDidLoad() {
super.viewDidLoad()
//if Auth.auth().currentUser?.uid == nil{
//logout()
//}else{
self.dataSource = self
if let firstViewController = viewControllerList.first{
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
//}
}
func logout(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let verifyPhoneVC = storyboard.instantiateViewController(withIdentifier: "enterPhoneNumber")
present(verifyPhoneVC, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.index(of: viewController) else {return nil}
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else {return nil}
guard viewControllerList.count > previousIndex else {return nil}
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.index(of: viewController) else {return nil}
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else {return nil}
guard viewControllerList.count > nextIndex else {return nil}
return viewControllerList[nextIndex]
}
}
You have the entire code ready, all that you need to do is pass the secondViewController from the viewControllerList in viewDidLoad thats all
So replace
if let firstViewController = viewControllerList.first{
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
with
let secondViewController = viewControllerList[1]
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
Hope this helps
Here I have written an extension in swift 3 for navigating next and previous page in UIPageViewController. You can modify as your requirement.
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}
I hope you find it useful.
Open to suggestions.
Related
I'm trying to performSegue() with an animation from left:
class SegueFromLeft: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: -src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
But somehow I get the following error for this line: src.present(dst, animated: false, completion: nil), when performSegue is being executed:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16b607fc0)
I can see the animation, but as soon as the new view is being displayed - the app crashes.
PageViewController in case it helps:
class LeasingTutorialViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "leasingPageOne")
let vc2 = sb.instantiateViewController(withIdentifier: "leasingPageTwo")
return [vc1, vc2]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstViewController = viewControllerList.first as? LeasingPageOneViewController {
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else { return nil }
guard viewControllerList.count > previousIndex else { return nil }
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else { return nil }
guard viewControllerList.count > nextIndex else { return nil }
return viewControllerList[nextIndex]
}
}
Edit:
It's called in a different ViewController ("goToLeasingTutorial") under some conditions:
Another important note: As soon as I restart the app, it works fine again.
#IBAction func goToLeasing(_ sender: Any) {
if leasingTutorialSeen == false && leasingInStartScreen == true {
performSegue(withIdentifier: "goToLeasingTutorial", sender: self)
} else if leasingTutorialSeen == true && leasingInStartScreen == true {
buttonHamburgerTapped(self)
let parentVC = self.parent as! RootPageViewController
parentVC.setViewControllers([parentVC.viewControllerList[1]], direction: .forward, animated: true, completion: nil)
} else if leasingTutorialSeen == true && leasingInStartScreen == false {
performSegue(withIdentifier: "goToLeasing", sender: self)
}
}
I am running into the issue of my viewcontrollers not showing up even though the function calling the viewcontrollers seem to be running. The error I receive in the console is:
Warning: Attempt to present on whose view is not in the window hierarchy!
I have tried the suggestions on the "Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy" thread without any progress. I am running everything programmatically.
func handleNewPage(){
print("this code works")
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
let usersReference = ref.child("patient").child((uid)!)
if usersReference.child("Doctor Code") != nil {
func presentNewPage(){
let firstPage = LandingPage()
let navCon = UINavigationController(rootViewController: firstPage)
present(navCon, animated: true, completion: nil)
}
presentNewPage()
print("PRINT")
} else{
let newPage = PresentViewController() // doctor reg page
let navController = UINavigationController(rootViewController: newPage)
present(navController, animated: true, completion: nil)
}
}
The function is called and the print statements come out valid. Yet, the viewcontrollers will not appear.
You have to create presentNewPage() function outside of your if ???
if usersReference.child("Doctor Code") != nil {
func presentNewPage(){
let firstPage = LandingPage()
let navCon = UINavigationController(rootViewController: firstPage)
present(navCon, animated: true, completion: nil)
}
presentNewPage()
print("PRINT")
} else{
let newPage = PresentViewController() // doctor reg page
let navController = UINavigationController(rootViewController: newPage)
present(navController, animated: true, completion: nil)
}
change it like this
func presentNewPage(){
let firstPage = LandingPage()
let navCon = UINavigationController(rootViewController: firstPage)
present(navCon, animated: true, completion: nil)
}
func handleNewPage(){
print("this code works")
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
let usersReference = ref.child("patient").child((uid)!)
if usersReference.child("Doctor Code") != nil {
presentNewPage()
print("PRINT")
} else{
let newPage = PresentViewController() // doctor reg page
let navController = UINavigationController(rootViewController: newPage)
present(navController, animated: true, completion: nil)
}
}
Basically I'd like to move my PageViewController not only with swipe but also with buttons.
#IBAction func nextButtonAction(_ sender: Any){
}
My PageViewController looks like in this guide
What I have:
PageViewController
3 Viewcontrollers
Sorry if it's a duplicate didn't found exactly same situation
tried to call UIPageViewControllerDataSource on each ViewController
but didn't work also I think this is not the best approach
Edited:
This is my PageViewController
class PageVC: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
lazy var VCarr: [UIViewController] = {
return [self.VCInstance(name: "signUpVerification"),
self.VCInstance(name: "signUpVerification1"),
self.VCInstance(name: "signUpVerification2"),
]
}()
func VCInstance(name:String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier:name)
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firsVC = VCarr.first {
setViewControllers([firsVC], direction: .forward, animated: true, completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCarr.index(of: viewController) else { return nil }
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
return VCarr[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCarr.index(of: viewController) else { return nil }
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCarr.count else {
return nil
}
return VCarr[nextIndex]
}
}
extension PageVC {
func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = VCarr.first {
if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) //for some reasons there's nil {
setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
}
}
}
}
this is Viewcontroller where I call it:
var pageVC = PageVC()
#IBAction func nextButtonAction(_ sender: Any){
pageVC.goToNextPage()
}
Solution:
YourViewController: {
#IBAction func nextButtonAction(_ sender: Any){
let pageViewController = self.parent as! PageVC
pageViewController.nextPageWithIndex(index: 1)
}
}
PageViewController: {
func nextPageWithIndex(index: Int) {
setViewControllers([VCarr[index]], direction: .forward, animated: true, completion: nil)
}
}
To move page viewController to by button click you can achieve very simply by using following extension
extension UIPageViewController {
func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = viewControllers?[0] {
if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) {
setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
}
}
}
func goToPreviousPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = viewControllers?[0] {
if let previousPage = dataSource?.pageViewController(self, viewControllerBefore: currentViewController){
setViewControllers([previousPage], direction: .reverse, animated: true, completion: completion)
}
}
}
}
To move forward
self.pageViewController.goToNextPage()
To move backward
self.pageViewController.goToPreviousPage()
don't forget to check whether the index available or not
for example:
if pageArray.count > currentIdex {
self.pageViewController.goToNextPage()
}
Hope this will help you
Try adding this method in your button Action for moving from first View Controller to second:
self.pageViewController.setViewControllers([self.viewControllerAtIndex(1)!], direction: .forward, animated: true, completion: nil)
pageControl2.currentPage = 1
Rest you can add your current PageTracker using PageControl.
self.pageViewController.setViewControllers([self.viewControllerAtIndex(pageControl.currentPage + 1)!], direction: .forward, animated: true, completion: nil)
pageControl.currentPage += 1
I'm trying to use UIPageViewController, and the slide is taken to account in debug, but the pages don't update:
import UIKit
class TutorialPageViewController: UIPageViewController {
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [UINib(nibName: "FinansicalTradingViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? FinansicalTradingViewController,
UINib(nibName: "VideoViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? VideoViewController,
UINib(nibName: "AllowZeroViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? AllowZeroViewController,
UINib(nibName: "TutorialStartViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? TutorialStartViewController]
}() as! [UIViewController]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
// let vc = UINib(nibName: "VideoViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? VideoViewController
// setViewControllers([vc!], direction: .forward, animated: true, completion: nil)
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
}
extension TutorialPageViewController: UIPageViewControllerDataSource {
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 {
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
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
I am using a tabViewController and want to know how I can make a 3D touch action from the home screen bring up a tab. I already have the code but can't seem to get a specific tab to show up; I can only get a window. My AppDelagate.swift is below. Any help?
enum ShortcutIdentifier: String
{
case First
case Second
init?(fullType: String)
{
guard let last = fullType.componentsSeparatedByString(".").last else {return nil}
self.init(rawValue: last)
}
var type: String
{
return NSBundle.mainBundle().bundleIdentifier! + ".\(self.rawValue)"
}
}
func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) -> Bool
{
var handled = false
guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else {return false}
guard let shortcutType = shortcutItem.type as String? else {return false}
switch (shortcutType)
{
case ShortcutIdentifier.First.type:
handled = true
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("sourcesview") as! UINavigationController
if let tabBarController = navVC.topViewController as? UITabBarController {
tabBarController.selectedIndex = 4
}
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
case ShortcutIdentifier.Second.type:
handled = true
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("searchview") as! UINavigationController
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
default:
break
}
return handled
}
You have to set the selectedTab property on your UITabBarController. I assume that the UINavigationController that you load from the nib contains a UITabBarController as top view controller, so after loading the navigation controller from the nib you have to access the tab bar controller and set the selected tab property to the desired tab.
switch (shortcutType)
{
case ShortcutIdentifier.First.type:
handled = true
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("sourcesview") as! UINavigationController
if let tabBarController = navVC.topViewController as? UITabBarController {
tabBarController.selectedIndex = 1
}
self.window?.rootViewController?.presentViewController(navVC, animated: true, completion: nil)
break
...
}