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)
}
Related
I have created a CustomAlertView.
protocol CustomAlertViewControllerDelegate{
func retryToFetchData()
}
class CustomAlertViewController: UIViewController {
#IBOutlet weak var alertView: UIView!
var delegate: CustomAlertViewControllerDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.alertView.alpha = 0
self.alertView.frame.origin.y = self.alertView.frame.origin.y + 50
UIView.animate(withDuration: 0.4) {
self.alertView.alpha = 1.0
self.alertView.frame.origin.y = self.alertView.frame.origin.y - 50
}
}
#IBAction func retryButton(_ sender: UIButton) {
delegate?.retryToFetchData()
self.dismiss(animated: true, completion: nil)
}
}
I have created a static function to show that view. The view is just a UIViewController that will have a child View which will act as a popUP, with a transparent background.
func showErrorBox(view: UIViewController, message: String, delegate: CustomAlertViewControllerDelegate){
let customAlert = view.storyboard?.instantiateViewController(withIdentifier: "customAlertViewController") as! CustomAlertViewController
customAlert.providesPresentationContextTransitionStyle = true
customAlert.definesPresentationContext = true
customAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
customAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
customAlert.delegate = delegate
view.present(customAlert, animated: true, completion: nil)
}
Now, I am calling this VC anywhere I needed to show a popUP with showErrorBox(view: self, message: message, delegate: self) now my issue is I need to show this popup in a ViewController which is inside of a TabBarController, when I change the view between the UITabBar, and try pressing the reload button, the app throws an error,
presentViewController does not work: view is not in the window
hierarchy
Edit:
There is a Retry button on the ErrorBox(popUp). The error happens only when the error box is loaded and I changed the view to different tab and then hit the reload button. In normal scenario like, hitting the reload button when I am in the same page works fine.
I am not sure of issue, but it has something to do with when I change the view between tabs when the error box is present.
try self.present(customAlert, animated: true, completion: nil) instead view.present(customAlert, animated: true, completion: nil). If I understand your problem, should works
Solution,
The problem was, when I changed the views in TabController the reference to the customAlertView was removed from the window hierarchy. So, As soon as I leave the TabView which was loading the CustomAlertView need to be removed.
So to solve the issue, I have added following code in CustomAlertViewController
override func viewWillDisappear(_ animated: Bool) {
super. viewWillDisappear(animated)
self.dismiss(animated: true, completion: nil)
}
Try to getting the TopMost ViewController and presenting CustomAlertViewController from it instead of "self".
refer this link to :
GetTopMostViewController
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.
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 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)
}
}
I am presenting two view controller like so
self.presentViewController(choosePlace, animated: true, completion: nil)
self.presentViewController(shareController, animated: true, completion: nil)
and I want to dismiss both of them like this:
println("parent \(self.parentViewController)")
self.dismissViewControllerAnimated(true, completion: {
self.parentViewController?.dismissViewControllerAnimated(true, completion: nil)
})
self.parentViewController is always nil though. How can I dismiss two at the same time?
You can:
self.dismissViewControllerAnimated(true, completion: {
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
})
So, make sure that you are presenting your view controllers in order:
parentViewController -> choosePlace -> shareController
(arrows indicate "self.presentViewController")
Swift 4
There where some issue I faced while trying Michael Voline's answer.
As the comment on the answer it did not work for me. it was because of the presentingViewController was nil.
So we need to set in a different property say presentingController but this will not work if you are setting it on the viewDidLoad
private var presentingController: UIViewController?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
then
dismiss(animated: false, completion: {
self.presentingController?.dismiss(animated: false)
})
I made the animation false so that the user will not see the UIof presenting view controller in the split seconds of dismissing.
You can reference the presenting view controller during the function 'viewWillDisappear' of your view controller (i.e ShareController dismisses choosePlace just before it leaves scope).
//place the below in shareController
override func viewWillDisappear(_ animated: Bool) {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
If Michael Voline's solution doesn't work , try the following code.
let customViewController = self.presentingViewController as? CustomViewController
self.dismiss(animated: true) {
customViewController?.dismiss(animated: true, completion: nil)
}