Pushing ViewController from string in Swift - ios

I have a class 'MyViewController.swift' and a string "MyViewController". How can I create an instance of that viewController from the string and push it in the navigation controller?
I've checked this answer (How to create an object depending on a String in Swift?) but it doesn't seem to do the trick.

Assuming you are working with storyboard, you could extend UIStoryboard like:
class func mainStoryboard() -> UIStoryboard {
return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
}
class func myViewController(s: String) -> UIViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier(s) as? UIViewController
}
and then you can use it like
myVC = UIStoryboard.myViewController("controller")
myVC.view.frame = view.frame
view.addSubview(myVC.view)
addChildViewController(myVC)
myVC.didMoveToParentViewController(self)
or
let vc = getVC("controller")
vc!.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
self.presentViewController(vc!, animated: true, completion: nil)
Update:
If you are not using storyboards, you can add a something like this to your controller:
func getVC(s: String) -> UIViewController {
switch s {
case "myVc":
return MyVC() as! UIViewController
default:
// handle default case
}
}

Related

How to pass UIVIewController name as a parameter to particular function using swift?

In my scenario, I need to pass UIVIewController name and some more string values to particular function. I tried below code but not getting result.
Passing Parameters To Particular Function
self.accountoptionscall(vcName: UIViewController(), vcIdentifier: "profileviewcontroller", popUpVC: ProfileViewController.self)
func accountoptionscall<T: UIViewController>(vcName: UIViewController,vcIdentifier: String, popUpVC: T.self) {
let viewcontrollers = self.storyboard!.instantiateViewController(withIdentifier: vcIdentifier) as! vcName
let navController = UINavigationController(rootViewController: viewcontrollers)
self.present(navController, animated:true, completion: nil)
}
I use it in my app, I think it can help you.
extension UIViewController {
/// Load UIViewController type from UIStoryboard
class func loadFromStoryboard<T: UIViewController>() -> T {
let name = String(describing: T.self)
let storybord = UIStoryboard(name: name, bundle: nil)
if let viewController = storybord.instantiateInitialViewController() as? T {
return viewController
} else {
fatalError("Error: No initial view controller in \(name) storyboard!")
}
}
}
Here how you can use it:
func loadVC<T: UIViewController>(controller: T) {
let vc: T = T.loadFromStoryboard()
let navigationVC = UINavigationController(rootViewController: vc)
self.present(navController, animated:true, completion: nil)
// or if use in appDelegate you can do it: window?.rootViewController = navigationVC
}

Swift generic function to push any view controller

I am attempting to write a func that 1) instantiates a subclass of UIViewController and 2) pushes into the navigation controller of the caller UIViewController.
So far, I have this:
func pushAnyViewController<T>(viewController:T, storyboardName:String) {
// Instantiate the view controller of type T
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else {
return
}
viewController.navigationController.pushViewController(nextViewController, animated: true)
}
This produces error
Value of type 'T' has no member 'navigationController'
I am not sure if somehow I should say that T will always be a subclass of UIViewController. If that is the case, it's not clear where I do that. For this, I thought about:
func pushAnyViewController<T>(viewController:T & UIViewController, storyboardName:String)
but that produces errors:
Generic parameter 'T' is not used in function signature
Non-protocol, non-class type 'T' cannot be used within a protocol-constrained type
You need to identify that T is a vc with <T:UIViewController>
func pushAnyViewController<T:UIViewController>(viewController:T, storyboardName:String) {
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else { return }
viewController.navigationController?.pushViewController(nextViewController, animated: true)
}
The top answer didn't suit me, I needed a more generic solution. First, I needed the option to pass data, second, I wanted to have a generic function that can push or present VC, and last, I wanted that my generic function can be called from anywhere, not just from the UIViewController, that's why an extension of UIViewController didn't suit me.
I decided to create a struct with simple init, and two public methods so that I can create the copy anywhere and call those methods.
struct Navigator {
// MARK: - DisplayVCType enum
enum DisplayVCType {
case push
case present
}
// MARK: - Properties
private var viewController: UIViewController
static private let mainSBName = "Main"
// MARK: - Init
init(vc: UIViewController) {
self.viewController = vc
}
// MARK: - Public Methods
public func instantiateVC<T>(withDestinationViewControllerType vcType: T.Type,
andStoryboardName sbName: String = mainSBName) -> T? where T: UIViewController {
let storyBoard: UIStoryboard = UIStoryboard(name: sbName, bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: String(describing: vcType.self))
return destinationVC as? T
}
public func goTo(viewController destinationVC: UIViewController,
withDisplayVCType type: DisplayVCType = .present,
andModalPresentationStyle style: UIModalPresentationStyle = .popover) {
switch type {
case .push:
viewController.navigationController?.pushViewController(destinationVC, animated: true)
case .present:
destinationVC.modalPresentationStyle = style
viewController.present(destinationVC, animated: true, completion: nil)
}
}
}
and example call in some VC, with passing string after push:
class SomeVC: UIViewController {
var navigator: Navigator?
override func viewDidLoad() {
super.viewDidLoad()
navigator = Navigator(vc: self)
}
func pushVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: VC1.self) else { return }
vc.someString = "SOME STRING TO BE PASSED"
navigator?.goTo(viewController: vc, withDisplayVCType: .push)
}
func presentVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: TableViewController.self) else { return }
navigator?.goTo(viewController: vc, withDisplayVCType: .present)
}
}
With Help of some keyword.
enum Storyboard : String {
case Main
}
func viewController(_ viewController: UIViewController.Type) -> some UIViewController {
return UIStoryboard(name: self.rawValue, bundle: nil).instantiateViewController(withIdentifier: String(describing: viewController.self))
}

nil Delegate between two ViewController with two different Bundle (swift)

nil Delegate between two ViewController with two different Bundle using swift 4 (commented in second code)
here is my code :
First ViewController :
class FirstVC : UIViewController, MerchantResultObserver{
var secVC = SecondVC()
override func viewDidLoad() {
secVC.delegate = self
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController()
self.present(controller!, animated: true, completion: nil)
secVC.initSecondVC(data)
}
func Error(data: String) {
print("-------------Error Returned------------- \(data)")
}
func Response(data: String) {
print("-------------Response Returned------------- \(data)")
}
}
Second ViewController :
public class SecondVC: UIViewController {
public weak var delegate: MerchantResultObserver!
public func initSecondVC(_ data : String){
print(data)
}
#IBAction func sendRequest(_ sender: UIButton) {
delegate?.Response(data: “dataReturnedSuccessfully”) // delegate is nil //
dismiss(animated: true, completion: nil) // returned to FirstVC without returning “dataReturnedSuccessfully” //
}
}
public protocol MerchantResultObserver: class{
func Response(data : String)
func Error(data : String)
}
Any help would be appreciated
var secVC = SecondVC()
and
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController() as? SecondVC
These both are different instances.
You can assign a delegate to the controller, like
controller.delegate = self
It will call the implemented delegate methods in First View Controller.
Full Code.
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
if let controller = storyboard.instantiateInitialViewController() as? SecondVC {
//Assign Delegate
controller.delegate = self
//It's not init, but an assignment only, as per your code.
controller.initSecondVC(data)
self.present(controller, animated: true, completion: nil)
}
One more thing, Don't present View in ViewDidLoad. You can put a code in some button or in a delay method.

Passing a String/Object value to another ViewController

I am opening another ViewController using this:
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
let homeViewController: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "IssueViewController")
self.present(homeViewController, animated: true, completion: nil)
Along with this, I need to pass a Person object and a String value to the 2nd ViewController.
struct Person {
var Name: String
var Details: String
}
What changes do I need to do to attach a Person object to my existing code?
EDIT: This is the 2nd ViewController
I am trying to retrieve the values from this view
class IssueViewController: UIViewController {
var person: Person = Person();
override func viewDidLoad() {
super.viewDidLoad()
}
}
//changes in first controller
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let homeViewController: IssueViewController = mainStoryboard.instantiateViewController(withIdentifier: "IssueViewController") as! IssueViewController
homeViewController.person = Person(Name:"ABC",Details:"XYZ")
homeViewController.bindWithData(yourStringObject)
self.present(homeViewController, animated: true, completion: nil)
//changes in second view controller
class IssueViewController: UIViewController {
var person: Person = Person(Name:"",Details:"");
override func viewDidLoad() {
super.viewDidLoad()
print(person.Name)
print(person.Details)
}
func bindWithData(yourStringObject:String){
//your code here.
}
}

How to push and present to UIViewController programmatically without segue in iOS Swift 3

I am using this code for push SHOW and MODALLY programmatically in iOS Objective C.
And now want to know about Swift 3.
NewsDetailsViewController *vc = instantiateViewControllerWithIdentifier:#"NewsDetailsVCID"];
vc.newsObj = newsObj;
//--this(SHOW)
[self.navigationController pushViewController:vc animated:YES];
//-- or this(MODAL)
[self presentViewController:vc animated:YES completion:nil];
Push
do like
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as NewsDetailsViewController
vc.newsObj = newsObj
navigationController?.pushViewController(vc,
animated: true)
or safer
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController {
viewController.newsObj = newsObj
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: true)
}
}
present
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("NewsDetailsVCID") as! NewsDetailsViewController
vc.newsObj = newsObj
present(vc!, animated: true, completion: nil)
or safer
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewsDetailsVCID") as? NewsDetailsViewController
{
vc.newsObj = newsObj
present(vc, animated: true, completion: nil)
}
With an elegant way.
Create an Navigatable protocol:
protocol Navigatable {
/// Storyboard name where this view controller exists.
static var storyboardName: String { get }
/// Storyboard Id of this view controller.
static var storyboardId: String { get }
/// Returns a new instance created from Storyboard identifiers.
static func instantiateFromStoryboard() -> Self
}
Create a default instantiate controller implementation:
/**
Extension of Navigatable protocol with default implementations.
*/
extension Navigatable {
static func instantiateFromStoryboard() -> Self {
let storyboard = UIStoryboard(name: self.storyboardName, bundle: nil)
guard
let viewController = storyboard
.instantiateViewController(withIdentifier: self.storyboardId) as? Self else {
fatalError("Cannot instantiate the controller.")
}
return viewController
}
}
Extends the UIViewController to push a view controller:
extension UIViewController {
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
- Parameter completion: Function to be executed on completion.
Contains the view controller that was pushed when successful and nil otherwise.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type, completion: (T) -> Void) {
let viewController = T.instantiateFromStoryboard()
if let vc = viewController as? UIViewController {
self.pushViewController(vc, animated: true)
}
completion(viewController)
}
/**
Pushes a view controller of the provided type.
- Parameter viewControllerType: Type of view controller to push.
*/
func pushViewControllerOfType<T: Navigatable>(viewControllerType: T.Type) {
self.pushViewControllerOfType(viewControllerType: viewControllerType) { _ in }
}
}
Then you can use the Navigatable protocol for a specific view controller.
class MySuperViewController {
override func viewDidLoad() {
...
}
// ...
}
extension MySuperViewController: Navigatable {
static var storyboardName: String {
return "Main"
}
static var storyboardId: String {
return "MySuperViewControllerId" // From your story board name Main
}
}
// Instantiate your controller
let vc = MySuperViewController.instantiateFromStoryboard()
// Or
//
// Push your view controller
// testViewController.swift
self.pushViewControllerOfType(viewControllerType: MySuperViewController)
//Create object of view controller
let obj = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerIdentifier”) as! ViewControllerName
//Push Controller
self.navigationController?.pushViewController(obj, animated: true)
//Present Controller
self.navigationController?.present(obj, animated: true, completion: nil)

Resources