How get visible / current viewcontroller in non-viewcontroller class? Swift - ios

I have information box object - "Info view". And I wanted to append this view to current VC view, sometimes directly in VC class sometimes outside VC class, ex. in my framework.
I don't want always pass current VC in method argument, like
class InfoView: UIView {
/* Initialization methods */
func show(viewController: ViewController) {
/* some code */
viewController.addSubview(self)
}
}
I want get current VC directly in my "Info view" class:
class InfoView: UIView {
/* Initialization methods */
func show() {
let viewController = /* need get current VC */
/* some code */
viewController.addSubview(self)
}
}
rootViewController property in UIApplication.sharedApplication() returns current VC, but after transitions this VC not changed.
How I can get need get current VC?

rootViewController is a good way to go. Your root VC is probably a navigation controller, so you could use something like this:
func currentVC() -> UIViewController? {
guard let navigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController else { return nil }
return navigationController.viewControllers.last
}

Hi Artem and DeyaEldeen,
If I am not wrong you want to access view controller on storyboard.
You can get instance of any view controller
Steps
1 set storyboard id for view controller in its Custom Class Attribute.
2 In your info view class
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
MyViewcontroller *c = [storyboard instantiateViewControllerWithIdentifier:#"MyViewcontrollerStoryboardID"]
I hope it help you

Related

Is it possible to set a coordinator property on a UITabBarController?

I'm trying to implement a slide out menu in my tab-based app. I have a container controller which holds the MenuViewController and the UITabBarController, and it conforms to a MasterDelegate protocol which tells the container to slide out the MenuViewController when the hamburger button located on an individual tab is pressed. My problem is that the coordinator property for the UITabBarController is not set until after the UITabBarController is configured, and thus the property in the UITabBarController as well as the ViewController for the tab containing the button is always nil.
Here is the code belonging to the container that sets up the UITabBarController:
func configureHomeController() {
centerController = MainTabBarController()
// Make sure to set delegate of homeController
centerController.masterDelegate = self
print("MainContainterController: Setting the MainTabBarController delegate")
if centerController.masterDelegate != nil {
print("MainContainerController: MainTabBarController Delegate set")
}
view.addSubview(centerController.view)
// Add the navigationController containing the HomeController as a child to the ContainerController
addChild(centerController)
// Home controller didMove to the parent container of self, which is the ContainerController
centerController.didMove(toParent: self)
}
In the TabBarController, there is a print statement that executes after the TabBarController is configured which tells me if the masterDelegate property is nil or not. This is printed before the container controller prints the two above statements. Since the 2nd print statement is printed, I know that the coordinator is being set, but it's set too late.
My question is how do I get the masterDelegate to be set before the UITabBarController is done being configured? Or is there a way to set that property after the fact?
Create a convenience init for your tabbarcontroller and pass the delegate to it. Something like this:
convenience init(masterDelegate: MasterDelegate) {
self.init(nibName: nil, bundle: nil)
self.masterDelegate = masterDelegate
}
the when you are setting up the TabBarController, instantiate it like this:
centerController = MainTabBarController(masterDelegate: self)
centerController.configure()
Also, make a configure function in MainTabBarController. In there, set up your viewControllers.
(in MainTabBarController)
func configure() {
viewControllers = [viewController, viewController2, ....]
}

Switching containerView content from within viewController

I have three viewControllers Main, A, B. Main ViewController holds ContainerView and other content as well does all transactions in containerView. ViewControllerA has ButtonA when pressing it content of container has to change to ViewControllerB
how can I do that? I cannot find any similar examples.
You will need to create create delegate for that.
First create a protocol
protocol ViewControllerADelagate {
func didPressOnButtonA()
}
In ViewControllerA add following delegate variable
class ViewControllerA {
....
var delegate : ViewControllerADelagate?
....
}
In ViewControllerA add following on button press
#IBAction buttonAPressed(sender : UIButton) {
self.delegate?.didPressOnButtonA()
}
In MainViewController assign the delegate of ViewControllerA to self
like
vcA.delegate = self
Implement the delegate method in MainViewController like
func didPressOnButtonA {
let storyboard : UIStoryboard = UIStoryboard(name: storyboard, bundle: nil)
let vcB : UIViewController = storyboard.instantiateViewController(withIdentifier: viewControllerIdentifier) as! ViewControllerB
self.addChildViewController(vcB)
self.containerView.addSubview(vcB.view)
vcB.didMove(toParentViewController: self)
vcB.view.frame = CGRect.init(x: 0, y: 0, width: self.containerView.frame.size.width, height: self.containerView.frame.size.height)
}
While click on ButtonA. Post a notification to mainView. There remove viewController A from Container and add View Controller B .
I have created a storybaord with sample. you can download it from here.
You need to change the embed view to a navigation controller and then you can use segue to show second view on button press. also hide/show navigation bar depends on requirement.
I am not fond of using these but you can get the child view controller from parent controller by accessing an array childViewControllers. On view did load you would need to go through all of these and find the one that can be typecast into your view controller type like
childViewControllers.flatMap { $0 as? AViewController }.first.
Now that you found the correct view controller I suggest you to either assign yourself as a delegate
childViewControllers.flatMap { $0 as? AViewController }.first.delegate = self
or simply add a button target
childViewControllers.flatMap { $0 as? AViewController }.first.button.addTarget...
Now this can easily be done if you simply embed the 2 view controllers at the same time (have 2 content views) and hide one or the other depending on which you show. At least this way you can assign connection straight away. When this is not the case then you will need to iterate again when setting a new controller or assign connections where you initialize a new view controller.
In this case it then seems better to turn the system around: When child view controller is loaded rather call
self.delegate = parentViewController as? AViewControllerDelegate
Although this will work it seems wrong that a child view controller will control who its listener is so I advise you to avoid such coding.
I in general use a custom implementation for container view which you could do the same or maybe at least subclass the native one and target the methods so that your code will look something like:
private onBPressed() {
containerView.setViewController(viewController: BViewController(delegate: self), animation: .fade)
}
When you pressing Button A from View A:
#IBAction func BtnAPress(_ sender: Any)
{
//Moving Storyboard
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let MainVC : UIViewController = Storyboard.instantiateViewController(withIdentifier: "ViewControllerB")
self.present(MainVC, animated: true, completion: nil)
}

Trigger segue in another UITabBarController VC with payload?

I can swap from one UIViewController to another within a UITabBarController using tabBarController?.selectedIndex = targetIndex. I would like to know how to trigger a segue after the switch.
I have tried doing if let vc = tabBarController?.viewControllers?[0] as MyViewController {} and calling vc.performSegue() but it errors out, complaining that it's a UINavigationController. (I couldn't even get vc.prepare() to work but I can't work out how to create a UIStoryBoardSegue, which it requires as the first argument, from scratch using the segue identifier.) I think this is because all of my UIViewControllers in the UITabBarController are within Navigation Controllers.
How do I switch from index 0 to 1 in the tab bar controller, and then trigger a segue in the view controller embedded in the navigation controller? Note, I need to pass in a copy of the calling object (my UIViewController) or a payload for contextual purposes to the UIViewController being segued to.
Well like you said the viewController which is presented at the index is a UINavigationController. You should get the rootViewController of the UINavigationController and perform the Segue on the RootViewController.
Below code should work:
self.tabBarController?.selectedIndex = 1
guard let vc = self.tabBarController?.viewControllers?[1] as? UINavigationController else {
return
}
guard let rootVC = vc.viewControllers.first as? SecondViewController else {
return
}
rootVC.vc = self
rootVC.performSegue(withIdentifier: "test", sender: self)

Can't Call Segue By Identifier from AppDelegate

So I have a segue from ViewController to SecondViewController. This segue is triggered by a UIButton in ViewController and the modally presents SecondViewController. The Segue's Identifier is set ("SegueIdentifier") and I am able to call the segue programatically from within my ViewController.
When I try to do the same in my AppDelegate, I get the error that the compiler can't find a segue with the Identifier I set.
let viewController = ViewController()
viewController.performSegueWithIdentifier("SegueIdentifier", sender: nil)
Again, I literally copied and pasted the performSegueWithIdentifier method call from the aforementioned method in ViewController in which I also call performSegueWithIdentifier for the same segue and it works.
Any ideas?
In my one project I am managing this situation like,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSString *identifier;
BOOL isSaved = [[NSUserDefaults standardUserDefaults] boolForKey:#"loginSaved"];
if (isSaved)
{
identifier=#"home1";
}
else
{
identifier=#"dis1";
}
UIStoryboard * storyboardobj=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *screen = [storyboardobj instantiateViewControllerWithIdentifier:identifier];
[self.window setRootViewController:screen];
return YES;
}
It is in objective c and just for reference. If it can help you :)
Update :
In swift something like,
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var identifier: String = String()
let isSaved = NSUserDefaults.standardUserDefaults().boolForKey("loginsaved")
if isSaved
{
identifier = "home1"
}
else{
identifier = "dis1"
}
let storyboardobj: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let screen: UIViewController = storyboardobj.instantiateViewControllerWithIdentifier(identifier)
self.window!.rootViewController = screen
// Override point for customization after application launch.
return true
}
Hope this will help :)
If you are not trying to show your initial view then do like this:
UIApplication.sharedApplication().keyWindow?.rootViewController.performSegueWithIdentifier("SegueIdentifier", sender: nil)
And also it depends that you segue is set to your RootViewController. Please check that also before you do this.
Segue connection has some info with it like Source Controller, Destination Controller. So while you are calling the performSegue method from ViewController class, it will work because that class has the info of this Segue connection.
While you are calling that same Segue method from App Delegate, it will not work. Because App Delegate class doesn't have the info or definition about that Segue object.
You should also check whether the calling object comes under Navigation Controller or not.
A storyboard segue has to be tied to the specific viewController that you are trying to segue from. So you have to have an instance of the current view controller on the screen. You may use the UIApplication.sharedApplication().keyWindow?.rootViewController
Method to get access to the rootViewController in the window, however if you presented a view controller above the rootViewController and you want to perform the segue on the presented view controller you have to access the rootViewController.presentedViewController. And since you may have multiple presentedViewControllers, what you do is this:
//get the root view controller
var currentViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
//loop over the presented view controllers and until you get the top view controller
while currentViewController.presentedViewController != nil{
currentViewController = currentViewController.presentedViewController
}
//And finally
currentViewController.performSegueWithIdentifier("identifier", sender: nil)

Swift how to create a protocol delegate from root VC to embedded VC

I have a root VC that embeds a table view through a container view with segue. So that makes both the root VC and child VC visible at the same time.
if segue.identifier == "TableSegue" {
let toView = segue.destinationViewController as! TableViewController
toView.delegate = self
}
How can I implement a protocol delegate between the root vc and child vc since the child VC is embedded inside the root vc?
What I want to do is to have a functions fired in the child VC once a button in root VC is clicked.
I have tried to implement a protocol delegate the normal way but it seems to not be picked up in the child VC
protocol TableViewInterface {
func someWork()
}
class RootVC:UIViewController {
var delegate: TableViewInterface?
func callDelegate() {
delegate?.someWork()
}
}
class TableViewController: UITableViewController, TableViewInterface {
func someWork() {
//Perform your the work you want done or the action you want fired in your tableView here...
}
}
The above is an example of a standard delegate pattern in swift. It looks like you are trying to set the delegate in prepareForSegue(), but IRRC it won't get called with a containerView. You probably want to get a reference to you tableView through your container view.
You should be able to get a reference to it by doing something like this in your RootVC
if let tableVC = childViewControllers[0] as? TableViewController {
self.tableViewController = tableVC
}
I hope this all makes sense

Resources