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.
Related
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
}
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))
}
I'm having issues with Custom alerts and sending actions back to VC from which alert was called.
I have two classes:
Factory
ConfirmationAllert
User journey I'm trying to achieve:
The user performs actions in the Factory class after he finishes I call ConfirmationAllert using such code:
func showAlert() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
(view as? UIViewController)?.present(myAlert, animated: true, completion: nil)
}
In ConfirmationAllert class I have button, which:
dismisses alert
sends action to Factory - this action is to dismiss Factory VC and go back to previous VC.
First action completes successfully, the second action not working. I'm using protocols to send the second action to Factory VC, but something is not working, and I don't know what.
Here is my code:
Factory
final class FactoryViewController: UIViewController {
let alert = ConfirmationAllert()
#IBAction func didPressSave(_ sender: UIButton) {
showAlert()
}
func goToPreviousVc() {
alert.delegate = self
print("Inside factory") -> print don't get called
// navigationController?.popViewController(animated: true) -> none of this works
// dismiss(animated: true, completion: nil) -> none of this works
}
}
extension FactoryViewController: ConfirmationAllertDelegate {
func dismissVC() {
goToPreviousVc()
print("Go to previous")
}
}
ConfirmationAllert
protocol ConfirmationAllertDelegate {
func dismissVC()
}
class ConfirmationAllert: UIViewController {
var delegate: ConfirmationAllertDelegate?
#IBAction func didPressOk(_ sender: UIButton) {
self.delegate?.dismissVC()
}
}
I didn't include viewDidLoad methods as I'm not calling anything there.
My issue is that method goToPreviousVc() doesn't perform any actions.
Thank you in advance for your help!
I guess your problem is that you setup your ConfirmationAllertDelegate at goToPreviousVc that supposed to be called using that delegate.
Instead, try to set up you delegate when you creating myAlert object
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert")
(myAlert as? ConfirmationAllert).delegate = self
// the rest of your code
After that, your alert will have a delegate since it was created and when you press the button, it should work as you expect.
Try to use below code
func showAlert() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "ConfirmationAllert") as! ConfirmationAllert
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
myAlert.delegate = self
present(myAlert, animated: true, completion: nil)
}
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.
}
}
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)