Get the instance of another view controller in the same tab bar - swift

My tab bar has 3 tabs. If I'm on the view controller of tab 1 first and then sometime later in the app I end up in tab 2, how do I get the instance of tab 1 (in code) that is currently loaded? The view controller's viewDidLoad function only gets called once so as long as I visited a tab, it's view controller is still somewhere.
Is it possible to get a reference to an instance of another view controller in a tab bar controller? I don't want to instantiate a new view controller, I want the current instance that has been loaded.

Yes, it is possible. You can get it's instance by providing the index of the UIViewController you want to the viewCotnrollers property in the UITabBarController.
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
let firstViewController = viewControllers?[0]
print(firstViewController?.title)
}
}
If you're trying to get the firstViewController from the secondViewController which is embedded inside the UINavigationController use this:
class SecondController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
let firstViewController = navigationController?.tabBarController?.viewControllers?[0]
print(firstViewController?.title)
}
}
And here's the extended version in case you're not sure:
class SecondController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
if let navigationController = navigationController {
if let tabBarController = navigationController.tabBarController {
let firstViewController = tabBarController.viewControllers?[0]
print(firstViewController?.title)
} else {
print("I'm not embedded in a tabBar controller")
}
} else {
print("I'm not embedded in a navigation controller")
if let tabBarController = tabBarController {
let firstViewController = tabBarController.viewControllers?[0]
print(firstViewController?.title)
} else {
print("I'm not embedded in a tabBar controller")
}
}
}
}

If you are inside a vc in tab 2 for example, you can get viewController from tab 1 like this:
if let firstTabVc = tabBarController?.viewControllers.first {
//you have vc here
}

Related

Mixed programmatic and storyboard. Button click will not open next VC in a navigation controller

I have a tab bar controller that is built programatically. And it looks something like this:
class NewTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
createTabbar()
}
func createTabbar() {
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
deliveryViewController?.tabBarItem.image = #imageLiteral(resourceName: "icon_deliver")
deliveryViewController?.title = "Delivery"
planDictionary["planType"] = Constants.Mealplan.deliveryPlan
deliveryViewController?.planDictionary = planDictionary
// Excluded other tabs and view controller creations since they are the same
}
Now this DeliveryViewController is created in storyboard and embedded in a nav controller
It has a button click action:
#IBAction func saveNameButton(_ sender: UIButton) {
let addressViewController = storyBoard.instantiateViewController(identifier: "AddressViewController") as? AddressViewController
addressViewController?.planDictionary = planDictionary
navigationController?.pushViewController(addressViewController!, animated: true)
}
The button click action was working when the tabbar was in the storyboard. But after refactoring programatically, it is does not place the next VC on the navigation stack.
Help would be appreciated.
Almost certainly...
This line:
let deliveryViewController = storyBoard.instantiateViewController(identifier: "DeliveryViewController") as? DeliveryViewController
Is instantiating an instance of DeliveryViewController and setting that as the View Controller for a Tab. But what you want to do is load a UINavigationController and make DeliveryViewController the root view controller of that NavController, and set that NavController as a Tab item.

Refreshing tableview data after dismiss() called on another tableview

Setup: table view controller has button (Add) that pops up another view controller with a form. I'm using Realm to store data, so no need to pass data back. However, when I dismiss() the view controller, and return to the table view controller, I cannot get tableView.reloadData() to work.
I have tried viewWillAppear() and viewDidAppear() but neither appear to be in the call stack.
Any ideas where I need to put this?
You need a delegate
let second = ///
second.delegate = self
When you dismiss in 2nd vc
delegate?.refresh()
Probably your second controller is being displayed modally. According to Apple Developer Documentation:
If a view controller is presented by a view controller inside of a popover, this method is not invoked on the presenting view controller after the presented controller is dismissed.
You can solve your problem with a delegate:
protocol ControllerBDelegate {
func willDismiss()
}
class ControllerA {
func open() {
let vc = ControllerB()
vc.delegate = self
self.present(vc, animated: true)
}
}
extension ControllerA : ControllerBDelegate {
func willDismiss() {
self.tableView.reloadData()
}
}
class ControllerB {
weak var delegate: ControllerBDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.willDismiss()
}
}

perform segue after view controller is dimissed

I am using UINavigationController. I want to show an intermediate screen eg. White and then from there I want to dimiss and segue to green.
The reason I don't create a segue from white to green is because in the case the user goes back they should go back to blue because blue is my main screen.
Here's the code:
class BlueViewController: UIViewController {
#IBAction func tapBlue(_ sender: Any) {
self.performSegue(withIdentifier: "whiteSegue", sender: self)
}
}
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
navigationController?.popViewController(animated: true)
weak var pvc = self.presentingViewController
dismiss(animated: true){
pvc?.performSegue(withIdentifier: "greenSegue", sender: self)
}
}
}
Here's the codebase
https://github.com/omenking/DismissAndSegue
No error occurs but when white is dismissed it doesn't go to green.
I know this has been asked before on StackOverflow but the other examples did not work or were out of date with latest iOS.
The main issue is that since you are using a navigation view controller and pushing view controllers on and off the stack, the self.presentingViewController variable will be nil. That is used for modal presentations, not navigation view controllers.
Try this:
class WhiteViewController: UIViewController {
#IBAction func tapGreen(_ sender: Any) {
// Get navigation stack, remove last item (White VC)
var viewControllers = navigationController?.viewControllers
viewControllers.removeLast()
// Instantiate new Green VC from storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil) //Change bundle name
let greenViewController = storyboard.instantiateViewController(withIdentifier: "GreenViewController") //Change storyboard ID
viewControllers.append(greenViewController)
// Perform the transition to Green VC with animation
navigationController?.setViewControllers(viewControllers, animated: true)
}
}
A slightly different solution is to link your view controllers blue->white->green, and then in the green view controller, just remove the white view controller from the navigation stack.
Your green view controller then becomes as simple as this.
class GreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}
}
}
No special handling needed in the other view controllers.
I do this, in white controller
performSegue(withIdentifier: "showSchedule", sender: date)
if let count = self.navigationController?.viewControllers.count {
self.navigationController?.viewControllers.remove(at: count - 2)
}

Swift - Present UIAlertController in extension function

I've wrote a label extension which detect links in the label.
It's attributed text with NSLinkAttributeName for links and I'm using
func handleTapOnLabel(_ tapGesture:UITapGestureRecognizer){} to detect where is the tap and which link to choose to open. The problem is that I use the function in extension for UILabel and I want to present an UIAlertController to ask the person - "You are about to open this link in Safari. Would you like to proceed?"... So I can't access viewcontroller to use the function present(UIAlertController... to display the alert. Any suggestions how this can be happened in extension? How to access label's viewcontroller directly from extension ?
Method 1) Present UIAlertController on top most ViewController in ViewController's hierarchy
extension UILabel { //your extension
func openLinkAction() { //your extension custom method
//code
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
}
// topController should now be your topmost view controller
// present alert on this controller
}
//and present here with vcInstance
}
}
Method 2) Pass ViewController's object as parameter
extension UILabel { //your extension
func openLink(vc: UIViewController) { //your extension custom method function
// code
// present alert on this vc
}
}
// For getting top most controller you ca use..
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
// topController
if let topController = UIApplication.topViewController() {
}

Swift: Switch between NSViewController inside Container View / NSView

I want to achieve a really simple task—changing the ViewController of a Container View by pressing a button:
In my example the ViewController1 is embedded into the Container View using Interface Builder. By pressing the Button ViewController2 I want to change the view to the second ViewController.
I’m confused because the Container View itself seems to be a NSView if I create an Outlet and as far as I know a NSView can’t contain a VC. Really appreciate your help!
Just note that in order for this to work you have to add storyboard identifiers to your view controllers, which can by going to your storyboard then selecting the Identity Inspector in the right hand pane and then entering the Storyboard ID in the Identity subcategory.
Then this implementation of ViewController would achieve what you are looking for.
import Cocoa
class ViewController: NSViewController {
// link to the NSView Container
#IBOutlet weak var container : NSView!
var vc1 : ViewController1!
var vc2 : ViewController2!
var vc1Active : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Make sure to set your storyboard identiefiers on ViewController1 and ViewController2
vc1 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController1") as! ViewController1
vc2 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController2") as! ViewController2
self.addChild(vc1)
self.addChild(vc2)
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
vc1Active = true
}
// You can link this action to both buttons
#IBAction func switchViews(sender: NSButton) {
for sView in self.container.subviews {
sView.removeFromSuperview()
}
if vc1Active == true {
vc1Active = false
vc2.view.frame = self.container.bounds
self.container.addSubview(vc2.view)
} else {
vc1Active = true
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
}
}
}
maybe this is a late answer but I will post my solution anyways. Hope it helps someone.
I embedded NSTabViewController in ContainerView. Then, in order not to see tabs on the top I did this:
go to NSTabViewController in storyboard
in Attributes inspector change style to be Unspecified
then click on TabView in Tab Bar View Controller, and set style to be "tabless":
After this you need to:
store tabViewController reference to mainViewController in order to select tabs from code
add a button to mainViewController (where your container is) with which you will change tabs in tabViewController.
You do this by storing the reference to tabViewController when overriding prepare for segue function. Here is my code:
first add property to the mainViewController
private weak var tabViewController: NSTabViewController?
then override this function and keep the reference to tabViewController:
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
guard let tabViewController = segue.destinationController
as? NSTabViewController else { return }
**self.tabViewController = tabViewController as? NSTabViewController**
}
After this you will have reference to tabViewController all set up.
Next (last) thing you have to do is make an action for button in order to move to first (or second) view controller, like this:
#IBAction func changeToSecondTab(_ sender: Any) {
self.tabViewController?.selectedTabViewItemIndex = 0 // or 1 for second VC
}
All the best!