Swift 4 : Switch Page View Controller programatically [duplicate] - ios

This question already has answers here:
how to pass data from UIPageViewController to child ViewController using delegates
(2 answers)
Closed 5 years ago.
I need to switch Page View Controller using a button.
The initialViewController is the CustomPageViewController.
I tried this in the View Controller :
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func goProfile(_ sender: Any) {
CustomPageViewController().goToProfile()
}
And this in the custom class PageViewController :
class CustomPageViewController: UIPageViewController {
fileprivate lazy var pages: [UIViewController] = {
return [
self.getViewController(withIdentifier: "MainViewController"),
self.getViewController(withIdentifier: "ProfilViewController")
]
}()
fileprivate func getViewController(withIdentifier identifier: String) -> UIViewController
{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: identifier)
}
override func viewDidLoad()
{
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firstVC = pages.first
{
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
func goToProfile(){
setViewControllers([pages.last!], direction: .forward, animated: true, completion: nil)
}
But nothing happens, any ideas ?
Thanks
EDIT : Final MainViewController's code working
#IBAction func goProfile(_ sender: Any) {
let vc = UIApplication.shared.keyWindow?.rootViewController as! CustomPageViewController
vc.goToProfile()
}

The problem is that this line is wrong:
CustomPageViewController().goToProfile()
The expression CustomPageViewController() creates a new, separate custom page view controller, which never appears in the interface and is thrown away in the next line.
What you need is a reference to an actual custom page view controller that is in your interface.

Related

Can I use dismiss for screen movement using NavigationController?

I'm using NavigationController to operate the view.
If the value is exceeded on the first view and the value is not satisfied with the 'if' on the second view, I hope 'dismiss' will work.
I have made a similar simple example.
//First View
class ViewController: UIViewController {
#IBAction func goSecond(_ sender: Any) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(DvC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
//Second View
class SecondViewController: UIViewController {
#IBAction func goThird(_ sender: Any) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
let num = 1
DvC.num = num
self.navigationController?.pushViewController(DvC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
//Third View
class ThirdViewController: UIViewController {
var num = Int()
override func viewDidLoad() {
super.viewDidLoad()
print("num: ")
print(num)
if(num != 2) {
print("Success")
// dismiss(animated: true, completion: nil)
// navigationController?.dismiss(animated: true, completion: nil)
} else {
print("Failed")
}
}
}
The third view shows "Success", but the codes below it do not work.
dismiss(animated: true, completion: nil) // not work
navigationController?.dismiss(animated: true, completion: nil) // not work
Pressing the button on the first view moves to the second view, and pressing the button on the second view moves to the third view.
If the value passed when I go to the third view is not satisfied with 'if', I want to go back to the first view.
I want the second and third views to end, not just the screen shift.
Like the finish() of Android.
How can I exit the second and third views and return to the first view?
You are pushing the view controllers onto the stack. dismiss is for dismissing VCs presented modally.
What you want to do is pop the VC to go back one.
navigationController?.popViewController(animated: true)
or to go back to the root view.
navigationController?.popToRootViewController(animated: true)

How to reload present view controller swift?

I have two Present view controllers. The thing i want to do is when the second Present view controller is dismissed it will automatically reload the first present view controller(Table view). note: first view controller holds a table view, basically i want to reload the table view of first controller.
ViewWillAppear code:
override func viewWillAppear(_ animated: Bool) {
tableViewReloadFromCreateProductVC()
}
func tableViewReloadFromCreateProductVC () {
tableView.reloadData()
}
Calling from second view controller code:
SecondViewController.tableViewReloadFromCreateProductVC()
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
FirstViewController calling 2nd view controller
#IBAction func CallSecondViewButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewControllerIdentifier") as! YourViewController
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
just write the code in viewWillAppear() method of the view controller that you want to reload like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
//perform api call if any
yourTableView.reloadData()
}
2nd view controller
#IBAction func CloseButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
after dissmissing the viewWillAppear method of firstViewController will autometically called.
The First two snippets are for first view controller and the last one is for second view controller
Reloading the entire table view could sometimes be costly and it also sounds like you're making an API call as well so unless you want your table view to be reloaded and the API call made every time the view controller becomes visible whether or not you've made changes to it, you want the reloading to be done only when it's necessary.
You can try it in a few different ways:
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
weak var delegate: CreateProductVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.delegate?.tableView.reloadData()
}
}
}
or
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.isDismissed = { [weak self] in
self?.tableView.reloadData()
}
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
var isDismissed: (() -> Void)?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.isDismissed?()
}
}
}
or if you want more fine-grained control over what to do with the new data:
protocol ReloadVC {
func reload(_ value: String)
}
class CreateProductVC: UITableViewController, ReloadVC {
var dataSource: [String]! {
didSet {
tableView.reloadData()
}
}
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
func reload(_ value: String) {
dataSource.append(value)
}
}
class SecondViewController: UIViewController {
var delegate: ReloadVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
let someValue = "Some Value"
self.delegate?.reload(someValue)
}
}
}

How to call a function in the first controller after dismissing the second controller

I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}

Change page with buttons. UIPageViewCotroller

I have a PageViewController and three view controllers. I want to move to previous and next page with button click. I have three VCs and when I run, I'm in the second one, (main).
Ordered items: (in MainViewController class), and pageViewController is an instance of PageViewController class.
override func viewDidLoad() {
super.viewDidLoad()
orderedItems = self.pageViewController.orderedViewControllers
}
orderdViewControllers in PageViewController Class
private(set) lazy var orderedViewControllers: [UIViewController]? = {
return [
self.newViewController(id: "first"),
self.newViewController(id: "main"),
self.newViewController(id: "second")
]
}()
private func newViewController(id: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "\(id)")
}
this is my code for button taps: (in MainViewController class)
#IBAction func leftBtnPressed(_ sender: AnyObject) {
self.pageViewController.setViewControllers([(orderedItems?.first)!], direction: .reverse, animated: true, completion: nil)
}
#IBAction func rightBtnPressed(_ sender: AnyObject) {
self.pageViewController.setViewControllers([(orderedItems?.last)!], direction: .forward, animated: true, completion: nil)
}
but it fails, nothing happens. How can I do it?

How to redisplay parse login from child view controller in swift

I have a view controller which is triggered from a UITabBarController (which is the root of my app) if a parse session doesn't exist.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.initialiseLogin()
}
func initialiseLogin()
{
if (PFUser.currentUser() == nil) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = storyboard.instantiateViewControllerWithIdentifier("LoginView") as! UIViewController
self.presentViewController(vc, animated: false, completion: nil)
}
}
Which works great. However the problem i'm having is how do i trigger this code when a logout is called from a child view controller in the tab bar controller
#IBAction func logoutAction(sender: AnyObject)
{
PFUser.logOut()
// ... what should i call here...
}
Protocols and delegates might be what you're looking for:
The Swift Programming Language - Protocols
Essentially, you can declare your UIViewControllers to conform to a protocol. Then set the delegates in your root view controller (or wherever you do your initialisation)
Then, you can do something like this:
#IBAction func logoutAction(sender: AnyObject)
{
PFUser.logOut()
delegate?.loggedOut()
}

Resources