I would like to implement routing in Swift like ReactJS, I have implemented protocols to serve for Routing.
But it's getting crashed in UIViewController extention. Can anyone help me with the solution.
Here is my code.
import Foundation
import UIKit
extension UIViewController {
func presented(_ animated: Bool) {
print("\(#function)")
present(Route.destination, animated: animated,
completion: nil)
}
func pushed(_ animated: Bool) {
print("\(#function)")
_ = navigationController?.pushViewController(Route.destination,
animated: true)
}
}
protocol Router {
static func toController <T: UIViewController>(_ controller:T,
params: Any) -> T
}
class Route : Router {
static var destination: UIViewController!
static func toController<T:UIViewController>(_ controller: T,
params: Any) -> T {
let viewController : T = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: String(describing: T.self)) as! T
destination = viewController
return viewController
}
}
class ViewController: UIViewController {
#IBAction func navigate() {
Route.toController(SecondViewControlller(), params: [])
.presented(true)
}
}
The app is crashing because you are going to present the same
viewController on itself.
The reason is below method takes destination viewController as an argument and return itself as destination.
static func toController <T: UIViewController>(_ controller:T, params: Any) -> T
In addition to that, whenever presented(_ animated: Bool) gets called from Route.toController(SecondViewControlller(), params: []).presented(true), self and Route.destination are same. So, it leads to presenting the same viewController on itself and causing sort of below error or crashing the application.
Attempt to present on
whose view is not in the
window hierarchy!
Try this:
extension UIViewController {
func presented(_ animated: Bool) {
print("\(#function)")
self.present(Route.destination, animated: animated, completion: nil)
}
func pushed(_ animated: Bool) {
print("\(#function)")
_ = self.navigationController?.pushViewController(Route.destination, animated: true)
}
}
protocol Router {
static func toController <T: UIViewController, T2: UIViewController>(_ controller: T2, from source: T, params: Any) -> T
}
class Route : Router {
static var destination: UIViewController!
static func toController <T: UIViewController, T2: UIViewController>(_ controller: T2, from source: T, params: Any) -> T {
let viewController : T2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: String(describing: T2.self)) as! T2
destination = viewController
return source
}
}
//Your ViewController.swift
#IBAction func onButtonTap(_ sender: Any) {
Route.toController(SecondViewControlller(), from: self, params: []).presented(true)
}
Related
I've followed the instructions here but I'm still unsure about this part:
modalVC.delegate=self;
self.presentViewController(modalVC, animated: true, completion: nil)
I've tried instantiating the view controller programmatically but still to no avail.
here's my code for when dismissing the modal view controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true) {
//
}
}
I'm using storyboard to segue with modal view.
This is the data I wish to transfer back to the parent view controller:
var typeState = "top"
var categoryState = "casual"
Which are two String values.
I've tried to pass data from the modal view controller as shown:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendValue(value: "success")
if let presenter = presentingViewController as? OOTDListViewController {
presenter.receivedValue = "test"
}
}
whereas on the parent view controller I did as such:
func sendValue(value: NSString) {
receivedValue = value as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(receivedValue)
}
I still couldnt receive any value when I hit the print button.
Modal view controller:
protocol ModalViewControllerDelegate
{
func sendData(typeState: String, categoryState: String)
}
var delegate:ModalViewControllerDelegate!
var typeState = "top"
var categoryState = "casual"
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendData(typeState: typeState as String, categoryState: categoryState as String)
}
Parent view controller:
class parentViewController: UICollectionViewController, ModalViewControllerDelegate {
var typeState: String?
var categoryState: String?
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState as String
self.categoryState = categoryState as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(typeState)
}
Here's my new code without using delegate method:
Modal view Controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
if let presenter = presentingViewController as? OOTDListViewController {
presenter.typeState = typeState
presenter.categoryState = categoryState
}
}
OOTDListViewController:
#IBAction func presentModalView(_ sender: UIBarButtonItem) {
let modalView = storyboard?.instantiateViewController(withIdentifier: "filterViewController") as! ModalViewController
let navModalView: UINavigationController = UINavigationController(rootViewController: modalView)
self.present(navModalView, animated: true, completion: nil)
}
#IBAction func printValue(_ sender: UIButton) {
print(typeState)
print(categoryState)
}
Depending on the data you want to pass, you can create a property in the presenting view controller, which you can set when dismissing the modal view controller, so you can spare yourself the delegate.
For example, you have a ContactsViewController, holding a var contacts: [Contact] = [] property. When you want to create a new contact, you present a modal view controller with the different values you need to create a new Contact object. When you are done and want to dismiss the view controller, you call the function as you did in your code, but set the property in the ContactsViewController. It will look something like this:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let presenter = presentingViewController as? ContactsViewController {
presenter.contacts.append(newContact)
}
dismiss(animated: true, completion: nil)
}
If you don't want to use a delegate, this is how you go about it:
In your OOTDListViewController :
var testValue: String = ""
#IBAction func printReceivedValue(_ sender: UIButton) {
print(testValue)
}
In your modal view controller (I'll call it PresentedViewController) :
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
// if your OOTDListViewController is part of a UINavigationController stack, this check will probably fail.
// you need to put a breakpoint here and check if the presentingViewController is actually a UINavigationController.
// in that case, you will need to access the viewControllers variable and find your OOTDListViewController
if let presenter = presentingViewController as? OOTDListViewController {
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
If you want to use a delegate, this is how to do it:
In your OOTDListViewController:
protocol ModalDelegate {
func changeValue(value: String)
}
class OOTDListViewController: ModalDelegate {
var testValue: String = ""
#IBAction func presentViewController() {
// here, you either create a new instance of the ViewController by initializing it, or you instantiate it using a storyboard.
// for simplicity, I'll use the first way
// in any case, you cannot use a storyboard segue directly, bevause you need access to the reference of the presentedViewController object
let presentedVC = PresentedViewController()
presentedVC.delegate = self
present(presentedVC, animated: true, completion: nil)
}
func changeValue(value: String) {
testValue = value
print(testValue)
}
}
In your PresentedViewController:
class PresentedViewController {
var delegate: ModalDelegate?
var testValue: String = ""
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let delegate = self.delegate {
delegate.changeValue(testValue)
}
dismiss(animated: true, completion: nil)
}
}
If using a navigation controller you will have to first grab the UINavigation Controller and then get the correct ViewController from the Navigation Controller stack.
Here's how my code looked in that case.
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! OOTDListViewController
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
I am using the a tab bar so the working code is as below:
if let tabBar = self.presentingViewController as? UITabBarController {
let homeNavigationViewController = tabBar.viewControllers![0] as? UINavigationController
let homeViewController = homeNavigationViewController?.topViewController as! HomeController
homeViewController._transferedLocationID = self.editingLocationID!
}
You need to call the delegate method in dismissViewController method
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
delegate?.sendData(typeState: "top", categoryState: "casual")
self.dismiss(animated: true) {
//
}
}
in you Modal ViewController class create delegate
weak var delegate: MyProtocol?
create a protocol with the method name sendData in MyProtocol and in your presentingViewController where you are assigning the delegate, implement the MyProtocol method
protocol MyProtocol: AnyObject {
func sendData(typeState: String, categoryState: String)
}
class ViewController: UIViewController, MyProtocol {
var typeState: String?
var categoryState: String?
override func viewDidApear() {
super.viewDidApear()
presentNewModalVC()
}
func presentNewModalVC() {
let modalVC = NewModalViewControllerToBePresented()
modalVC.delegate = self
present(modalVC, animated: true)
}
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState
self.categoryState = categoryState
}
}
I've followed the instructions here but I'm still unsure about this part:
modalVC.delegate=self;
self.presentViewController(modalVC, animated: true, completion: nil)
I've tried instantiating the view controller programmatically but still to no avail.
here's my code for when dismissing the modal view controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true) {
//
}
}
I'm using storyboard to segue with modal view.
This is the data I wish to transfer back to the parent view controller:
var typeState = "top"
var categoryState = "casual"
Which are two String values.
I've tried to pass data from the modal view controller as shown:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendValue(value: "success")
if let presenter = presentingViewController as? OOTDListViewController {
presenter.receivedValue = "test"
}
}
whereas on the parent view controller I did as such:
func sendValue(value: NSString) {
receivedValue = value as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(receivedValue)
}
I still couldnt receive any value when I hit the print button.
Modal view controller:
protocol ModalViewControllerDelegate
{
func sendData(typeState: String, categoryState: String)
}
var delegate:ModalViewControllerDelegate!
var typeState = "top"
var categoryState = "casual"
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendData(typeState: typeState as String, categoryState: categoryState as String)
}
Parent view controller:
class parentViewController: UICollectionViewController, ModalViewControllerDelegate {
var typeState: String?
var categoryState: String?
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState as String
self.categoryState = categoryState as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(typeState)
}
Here's my new code without using delegate method:
Modal view Controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
if let presenter = presentingViewController as? OOTDListViewController {
presenter.typeState = typeState
presenter.categoryState = categoryState
}
}
OOTDListViewController:
#IBAction func presentModalView(_ sender: UIBarButtonItem) {
let modalView = storyboard?.instantiateViewController(withIdentifier: "filterViewController") as! ModalViewController
let navModalView: UINavigationController = UINavigationController(rootViewController: modalView)
self.present(navModalView, animated: true, completion: nil)
}
#IBAction func printValue(_ sender: UIButton) {
print(typeState)
print(categoryState)
}
Depending on the data you want to pass, you can create a property in the presenting view controller, which you can set when dismissing the modal view controller, so you can spare yourself the delegate.
For example, you have a ContactsViewController, holding a var contacts: [Contact] = [] property. When you want to create a new contact, you present a modal view controller with the different values you need to create a new Contact object. When you are done and want to dismiss the view controller, you call the function as you did in your code, but set the property in the ContactsViewController. It will look something like this:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let presenter = presentingViewController as? ContactsViewController {
presenter.contacts.append(newContact)
}
dismiss(animated: true, completion: nil)
}
If you don't want to use a delegate, this is how you go about it:
In your OOTDListViewController :
var testValue: String = ""
#IBAction func printReceivedValue(_ sender: UIButton) {
print(testValue)
}
In your modal view controller (I'll call it PresentedViewController) :
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
// if your OOTDListViewController is part of a UINavigationController stack, this check will probably fail.
// you need to put a breakpoint here and check if the presentingViewController is actually a UINavigationController.
// in that case, you will need to access the viewControllers variable and find your OOTDListViewController
if let presenter = presentingViewController as? OOTDListViewController {
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
If you want to use a delegate, this is how to do it:
In your OOTDListViewController:
protocol ModalDelegate {
func changeValue(value: String)
}
class OOTDListViewController: ModalDelegate {
var testValue: String = ""
#IBAction func presentViewController() {
// here, you either create a new instance of the ViewController by initializing it, or you instantiate it using a storyboard.
// for simplicity, I'll use the first way
// in any case, you cannot use a storyboard segue directly, bevause you need access to the reference of the presentedViewController object
let presentedVC = PresentedViewController()
presentedVC.delegate = self
present(presentedVC, animated: true, completion: nil)
}
func changeValue(value: String) {
testValue = value
print(testValue)
}
}
In your PresentedViewController:
class PresentedViewController {
var delegate: ModalDelegate?
var testValue: String = ""
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let delegate = self.delegate {
delegate.changeValue(testValue)
}
dismiss(animated: true, completion: nil)
}
}
If using a navigation controller you will have to first grab the UINavigation Controller and then get the correct ViewController from the Navigation Controller stack.
Here's how my code looked in that case.
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! OOTDListViewController
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
I am using the a tab bar so the working code is as below:
if let tabBar = self.presentingViewController as? UITabBarController {
let homeNavigationViewController = tabBar.viewControllers![0] as? UINavigationController
let homeViewController = homeNavigationViewController?.topViewController as! HomeController
homeViewController._transferedLocationID = self.editingLocationID!
}
You need to call the delegate method in dismissViewController method
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
delegate?.sendData(typeState: "top", categoryState: "casual")
self.dismiss(animated: true) {
//
}
}
in you Modal ViewController class create delegate
weak var delegate: MyProtocol?
create a protocol with the method name sendData in MyProtocol and in your presentingViewController where you are assigning the delegate, implement the MyProtocol method
protocol MyProtocol: AnyObject {
func sendData(typeState: String, categoryState: String)
}
class ViewController: UIViewController, MyProtocol {
var typeState: String?
var categoryState: String?
override func viewDidApear() {
super.viewDidApear()
presentNewModalVC()
}
func presentNewModalVC() {
let modalVC = NewModalViewControllerToBePresented()
modalVC.delegate = self
present(modalVC, animated: true)
}
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState
self.categoryState = categoryState
}
}
I have 3 view controllers.
let's name them as A, B & C.
A presents B and then C should be present from A after dismissing B.
A <=> B
A -> C
How can I achieve this?
If the question is unclear then do let me know, I would be happy to edit it.
Well, I achieved it this way.
Note: I am inside B.
let cViewController = // getting a handle of this view controller from Storyboard
let aViewController = self.navigationController?.presentingViewController
self.dismiss(animated: true) {
aViewController?.present(cViewController, animated: true)
}
You can use custom notification observer like below:
In Controller A:
override func viewDidLoad() {
super.viewDidLoad()
// Register to custom notification
NotificationCenter.default.addObserver(self, selector: #selector(presentC), name: NSNotification.Name(rawValue: "BDismissed"), object: nil)
// Rest of your code
}
func presentC {
// Controller C presentation code goes here
}
In Controller B:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "BDismissed"), object: nil, userInfo: nil)
}
Inside B try this
self.dismiss(animated: true) {
let aVC = UIApplication.shared.keyWindow?.rootViewController as! AVC
let cVC = ///
aVC.present(cVC, animated: true, completion: nil)
}
Write a protocol in B like:
protocol VCBDelegate {
func VCBDismissed()
}
Class VCB: UIViewController {
weak var delegate: VCBDelegate?
....
}
Now where you are dismissing B, call the delegate method in completion.
func dismissB() {
self.dismiss(animated: true) {
self.delegate.VCBDismissed()
}
}
Now conform this protocol in A.
extension VCA: VCBDelegate {
func VCBDismissed() {
//Here you present C
.....
}
}
Don't forget to make the delegate self where you are presenting B.
Hope this helps, for any queries please feel free to leave a comment.
You can use closures, it's better and simple.
Your A will present B and give it a closure to call when it dismiss, this closure will present C.
Here is an example :
class ViewControllerA : UIViewController{
func showViewControllerB(){
let vc = ViewControllerB()
vc.callOnDismiss = { [weak self] in
self?.showViewControllerC()
}
self.present(vc, animated: true, completion: nil);
}
func showViewControllerC(){
let vc = ViewControllerC()
self.present(vc, animated: true, completion: nil);
}
}
class ViewControllerB : UIViewController{
var callOnDismiss : () -> () = {}
func actionOnDismiss(){
self.dismiss(animated: true, completion: nil)
self.callOnDismiss()
}
}
class ViewControllerC : UIViewController{
}
I am having issues trying to pass the data back to the ViewController (from BarCodeScannerViewController to TableViewController)
SecondVC (BarCodeScannerViewController.swift):
#objc func SendDataBack(_ button:UIBarButtonItem!) {
if let presenter = self.presentingViewController as? TableViewController {
presenter.BarCode = "Test"
}
self.dismiss(animated: true, completion: nil)
}
FirstVC (TableViewController.swift):
// The result is (BarCode - )
var BarCode: String = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("BarCode - \(BarCode)")
}
Each time ViewWillAppear is running the value is not set, what could be causing this issue?
You should use the delegate pattern. I doubt in your code above that self.presentingViewController is actually set.
An example of using the delegate pattern for this:
// BarCodeScannerViewController.swift
protocol BarcodeScanningDelegate {
func didScan(barcode: String)
}
class BarCodeScannerViewController: UIViewController {
delegate: BarcodeScanningDelegate?
#objc func SendDataBack(_ button:UIBarButtonItem!) {
delegate?.didScan(barcode: "Test")
}
}
// TableViewController
#IBAction func scanBarcode() {
let vc = BarCodeScannerViewController()
vc.delegate = self
self.present(vc, animated: true)
}
extension TableViewController: BarcodeScanningDelegate {
func didScan(barcode: String) {
print("[DEBUG] - Barcode scanned: \(barcode)")
}
}
I've got 3 methods that are very similar in structure, I would like to extract a common method, looking like this:
private func navigateToViewController(animated: Bool, viewControllerType: T, viewControllerNibName: String, mode: MenuMode) { ... }
however, I don't know how to handle the type parameter, any good suggestions? Thanks!
private func navigateToEditorView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.editorViewController == nil {
let editor = EditorViewController(nibName:"EditorViewController", bundle: nil)
editor.exitCallBack = self.setBackgroundImage
self.editorViewController = editor
}
if let editor = self.editorViewController {
self.navigationController?.pushViewController(editor, animated: animated)
}
self.currentMenuMode = .editor
}
private func navigateToStorageView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.storageViewController == nil {
let storage = StorageViewController(nibName:"StorageViewController", bundle: nil)
storage.exitCallBack = self.setBackgroundImage
self.storageViewController = storage
}
if let storage = self.storageViewController {
self.navigationController?.pushViewController(storage, animated: animated)
}
self.currentMenuMode = .storage
}
private func navigateToGalleryView(animated: Bool) {
self.dismiss(animated: false, completion: nil)
if self.galleryViewController == nil {
let gallery = GalleryViewController(nibName:"GalleryViewController", bundle: nil)
gallery.exitCallBack = self.setBackgroundImage
self.galleryViewController = gallery
}
if let gallery = self.galleryViewController {
self.navigationController?.pushViewController(gallery, animated: animated)
}
self.currentMenuMode = .gallery
}
I think using protocol is the best way to handle all of your questions
enum MenuMode {
case editor
case storage
case gallery
}
protocol ExitCallBackHandler where Self: UIViewController {
var exitCallBack: (() -> Void)? { get set }; // I don't know what it is
var currentMenuMode: MenuMode { get }
}
Interface of each ViewController
class EditorViewController: UIViewController, ExitCallBackHandler {
var exitCallBack: (() -> Void)?
var currentMenuMode: MenuMode {
return .editor
}
// ...
}
class StorageViewController: UIViewController, ExitCallBackHandler {
var exitCallBack: (() -> Void)?
var currentMenuMode: MenuMode {
return .storage
}
// ...
}
class GalleryViewController: UIViewController, ExitCallBackHandler {
var currentMenuMode: MenuMode {
return .gallery
}
var exitCallBack: (() -> Void)?
// ...
}
Finally
private func navigateToViewController<T: ExitCallBackHandler>(animated: Bool, viewControllerType: T.Type) {
self.dismiss(animated: false, completion: nil)
var vc = T(nibName: String(describing: viewControllerType), bundle: nil)
vc.exitCallBack = ...
self.currentMenuMode = vc.currentMenuMode
self.navigationController?.pushViewController(vc, animated: animated)
}
Use it like this way:
navigateToViewController(animated: true, viewControllerType: StorageViewController.self)
I think your function just needs to be generic over T:
func navigateToViewController<T: UIViewController>(viewControllerType: T, nibName: String) {
let vc = T(nibName: nibName, bundle: nil)
print(vc)
}
Make your controllers conform to protocol Exitable
protocol Exitable{
var exitCallBack: (()->Void)? {get set}
}
Make your controllers conform to that:
class EditorViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
class StorageViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
class GalleryViewController: UIViewController, Exitable{
var exitCallBack: (() -> Void)?
}
Now make func:
func navigateToViewController<T: UIViewController & Exitable>(viewControllerType: T?, nibName: String, withExitBlock exitBlock: #escaping ()->Void, animated: Bool) {
var vc = T(nibName: nibName, bundle: nil)
vc.exitCallBack = exitBlock
self.navigationController?.pushViewController(vc, animated: animated)
}