I have a UITabBarController as root view controller.
In this UITabBarController, I have 2 tabs: tabA and tabB.
tabA is a general view controller, and tabB is a viewController with a Container View in which a pageViewcontroller C is embeded.
Now there is a button in tabA, I want to realize the effect that when I click this button, it will jump to tabB and show the second page in C, everytime when click the button, it will call the function:
func swipeToIndex(ToIndex: Int, completion: (()->Void)? = nil) {
if self.currentPageIndex > 2 {
return
}
if ToIndex < self.currentPageIndex {
let toViewController = self.orderedViewControllers[ToIndex]
self.setViewControllers([toViewController], direction: .reverse, animated: true, completion: {finish in
self.currentPageIndex = ToIndex
completion?()
})
}
else if ToIndex > self.currentPageIndex {
let toViewController = self.orderedViewControllers[ToIndex]
self.setViewControllers([toViewController], direction: .forward, animated: true, completion: {finish in
self.currentPageIndex = ToIndex
completion?()
})
}
}
I can only realize it from the second time that I click the button. And the first time, it goes to the first page in C. I found it's something to do with the viewDidLoad(). when it calls the function swipeToIndex for the first time after
self.setViewControllers([toViewController], direction: .forward, animated: true, completion: {finish in
self.currentPageIndex = ToIndex
completion?()
})
it will call viewDidLoad, inside there sets the viewcontroller again like following:
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
I have no clue how to avoid this when the first time call swipeToIndex
You will need to forward message a bit smarter and check if views have already been loaded.
In your page controller you should check if a view is loaded:
func swipeToIndex(ToIndex: Int, completion: (()->Void)? = nil) {
guard isViewLoaded else {
self.pageToSwipeTo = ToIndex
return
}
So you need to add
var pageToSwipeTo: Int? = nil
Then in viewDidLoad at the end try
if let pageToSwipeTo = pageToSwipeTo {
self.pageToSwipeTo = nil
self. swipeToIndex(pageToSwipeTo)
}
This is assuming the page view controller does already exist in your situation. This might actually not be true due to your hierarchy so you might need to forward the message even through the tab bar view controller... But try this procedure first.
Related
In SwiftUI, there're couple of ways to present a modal view like .popover.
My background is I would like to present a UIKit modal view somewhere else rather than under the current view page with
private func presentGlobally(animated: Bool, completion: (() -> Void)?) {
var rootViewController = UIApplication.shared.rootViewController
while true {
if let presented = rootViewController?.presentedViewController {
rootViewController = presented
} else if let navigationController = rootViewController as? UINavigationController {
rootViewController = navigationController.visibleViewController
} else if let tabBarController = rootViewController as? UITabBarController {
rootViewController = tabBarController.selectedViewController
} else {
break
}
}
UIApplication.shared.rootViewController?.present(self, animated: animated, completion: completion)
}
The above approach does not work because SwiftUI gave an error of
`` [Presentation] Attempt to present <BuyersCircle_iOS.GlobalModalUIKit: 0x15b82f000> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentGS1_GS1_GS1_GS1_V16BuyersCircle_iOS11ContentViewGVS_30_EnvironmentKeyWritingModifierGSqCS2_11UserService___GS4_GSqCS2_11GlobalModal___GS4_GSqCS2_9CartModel___GS4_GSqCS2_19ReachabilityMonitor___GS4_GSqCS2_19PartialSheetManager___: 0x158715600> (from
So I'm thinking
If I can get the current SwiftUI modal view controller, so I can present the UIKit modal based on it. I don't know how to make it work.
In you algorithm you're calculating rootViewController but then presenting self to UIApplication.shared.rootViewController, not to the found rootViewController
Replace
UIApplication.shared.rootViewController?.present(self, animated: animated, completion: completion)
With
rootViewController?.present(self, animated: animated, completion: completion)
I have a UIButton (also created programmatically) in my View Controller that's in the Page View Controller. I would like to switch to another View Controller in that same Page View Controller. Here is what I have tried to use:
#objc func myOpinionAction(_ sender:UIButton!)
{
PageViewController().setViewControllers([suggestionPage()], direction: UIPageViewController.NavigationDirection.forward, animated: true)
}
This plain out doesn't work (I am trying to switch to suggestionPage) Another method I've tried is this:
PageViewController().transition(from: PageViewController().viewControllers![0], to: PageViewController().viewControllers![2], duration: 0.3, options: UIView.AnimationOptions.curveEaseInOut, animations: nil, completion: nil)
This gives me a Thread 1: signal SIGABRT error on my:
class AppDelegate: UIResponder, UIApplicationDelegate {
First line of code in my AppDelegate, I'd assume it's due to an unwrapping of an option issue?
Does anyone know how I'm either using the functions wrong or have a better way of doing this?
Try this
func forward() {
if currentPageIndex < totalPageIndex {
pageController?.setViewControllers([viewControllerAtIndex(index: currentPageIndex + 1)], direction: .forward, animated: true, completion: nil)
}
}
func backward() {
if currentPageIndex > 0 {
pageController?.setViewControllers([viewControllerAtIndex(index: currentPageIndex - 1)], direction: .reverse, animated: true, completion: nil)
}
}
Change your code as given below. When you are writing PageViewController() it means you are creating new object and of type PageViewController and you are trying to set page on that object instance, which is not added to your view.
#objc func myOpinionAction(_ sender:UIButton!)
{
pageViewController.setViewControllers([suggestionPage()], direction: UIPageViewController.NavigationDirection.forward, animated: true)
}
i have a viewpagercontroller and have it running correctly with 3 pre set UIViewControllers, however when i load the app it first displays the first UIViewController, i would instead, like to display the second UIViewController first, however i am having trouble achieving this.
Here is my ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
Here is my orderedViewControllers
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newProfileViewController(vc: "UserContentViewController"),
self.newProfileViewController(vc: "ProfileViewController"),
self.newProfileViewController(vc: "NotificationViewController")]
}()
you can this function with your desired index,
func scrollToViewController(index newIndex: Int) {
if let firstViewController = viewControllers?.first,
let currentIndex = orderedViewControllers.indexOf(firstViewController) {
let direction: UIPageViewControllerNavigationDirection = newIndex >= currentIndex ? .Forward : .Reverse
let nextViewController = orderedViewControllers[newIndex]
scrollToViewController(nextViewController, direction: direction)
}
}
All you need to do is change the first bit of code that sets the viewcontrollers:
So change this:
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
To this:
if orderedViewControllers.count > 0 {
let secondViewController = orderedViewControllers[1]
setViewControllers([secondViewController],
direction: .forward,
animated: false,
completion: nil)
}
This will set the page view controller to the second view controller straight away and then your delegate methods on the page view controller should handle the swiping before and after to load the correct view controller.
I have a game menu with button start game:
#IBAction func startGame(_ sender: AnyObject) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "gameViewController") as? GameViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
Game over code:
if lifes == 0 {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "mainMenuViewController") as? MainMenuViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
When user click on button i show new view controller with Sprite Kit scene. But when game is over i go back to menu. And if we click Start game again, fps falls from 60(in my case) to 30, then if again to 20 etc. Seems like old view controller still working. How to dismiss it ?
I read similar questions, but didn't find answer in them.
Ok, now your issue is clear.
You should not present a new MainViewController when game is over. This will lead you to potentially infinite stack of view controllers like this:
Main -> Game -> Main -> Game -> ...
Instead you should dismiss your Game vc and return to the previous controller, so that you will always have one or two controllers in memory.
So you should replace this:
if lifes == 0 {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "mainMenuViewController") as? MainMenuViewController {
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
With this:
if lifes == 0 {
dismiss(animated: true, completion: nil) //will bring you to previous Main vc
}
Edit:
If you have more than two controllers to show, you should consider navigation controller approach. Basically you create it with rootViewController (MainVC), then push GameVC, then push GameOver.
In MainVC:
self.navigationController?.pushViewController(gameVC, animated: true)
In GameVC:
self.navigationController?.pushViewController(gameOverVC, animated: true)
To pop only only one controller:
self.navigationController?.popViewController(animated: true)
To pop to the first controller:
self.navigationController?.popToRootViewController(animated: true)
I have been trying to figure this out for hours, and I must be misunderstanding how UIPageViewController works.
I have 5 setup screens (separate view controllers that are loaded into the UIPageViewController) that I can swipe right + left to see.
BUT! I want to be able to programmatically show the next view controller in line after they dismiss an alert on one of those view controllers.
And so far, the only solution that I've found that gets close, is this solution here ( https://github.com/jeffaburt/UIPageViewController-Post ) that allows a "next" button, but the button isn't on the view controller that's showing, it's on the root view controller that holds the UIPageViewController.
I have the UIPageViewController set up to show 5 different view controllers, with this code.
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("setup1"),
self.newColoredViewController("setup2"),
self.newColoredViewController("setup3"),
self.newColoredViewController("setup4"),
self.newColoredViewController("setup5")]
}()
Then the newColoredViewController function does this:
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)")
}
and here's my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let SetupViewController = orderedViewControllers.first {
setViewControllers([SetupViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
I must be missing some fundamental understanding, because I've tried so many different ways to change the page programmatically, and nothing happens.
I've tried doing this, but it does nothing:
setViewControllers([orderedViewControllers[3]],
direction: .Forward,
animated: true,
completion: nil)
I've tried doing this, but it does nothing:
func scrollToNextViewController() {
if let visibleViewController = viewControllers?.first,
let nextViewController = pageViewController(self,
viewControllerAfterViewController: visibleViewController) {
scrollToViewController(nextViewController)
}
}
Please point me in the right direction!! I can research all day and usually can figure it out thanks to previous stackoverflow questions that are already answered, but here I'm stumped.
Thanks in advance!!
I figured out an easier way to do it within the structure I'd already set up. In the UIPageViewController, added this function:
func nextPageWithIndex(index: Int)
{
// let nextWalkthroughVC = newColoredViewController("setup4")
let nextWalkthroughVC = orderedViewControllers[index]
setViewControllers([nextWalkthroughVC], direction: .Forward, animated: true, completion: nil)
}
and then in one of the setup View Controllers I created a button action that ran this code:
#IBAction func yeaaa(sender: AnyObject) {
let parent = self.parentViewController as! SetupViewController
parent.nextPageWithIndex(3)
}
(where "SetupViewController" is the UIPageViewController)
YEEHAWWWW that was driving me nuts.
Here's the video that gave me the idea of "self.parentViewController as! SetupViewController" and the "nextPageWithIndex" code: https://www.youtube.com/watch?v=K3CSBxX5VXA
I guess in newColoredViewController you create the instances of the view controllers that are to be shown by the pageViewController.
There you shoud pass into a reference to the showing view controller. Something like
newSetupViewConroller.setNextDelegate(self).
Define a protocol NextDelegate that has a method performNext
That should be the type of the parameter (and the correlating instance variable) of setNextDelegate.
(Alternatively to a setter method you can of course declare some property of type NextDelegate)
Your presenting view controller should conform to this protocol and implement the performNext method.
When you did that then you have access to the presenting view controller (as an object that conforms to the protocol) and you can call its method performNext.
Within the method performNext you are in your presenting view controller and have access to the pageViewController and go from there.
(There are easier ways of doing this, but I think this is a proper one. That is what protocols are made for.)
First, we need to access parent class as PageViewController
Swift 5 :
if let controller = self.parent as? yourPageViewController
controller.goToNextPage()
}
PageViewController Extension can be used like that
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)
}
}