I am currently setting up a Tab Bar Application for iOS.
Normally, I would use an overridden method like prepareforSeque for dependency injection when changing viewControllers, but that method is not called when the UITabBarController changes its active child ViewController. How do I correctly do dependency injection into UITabBarController child ViewControllers?
In the RootViewController's viewDidLoad you can iterate thru childViewControllers and find the various child controllers that you want and set the dependency to each of them. In this case the dependency will be available in viewDidLoad of the child view controllers. Tab bar instantiates the child view controller instances but does not load the view until its required.
Once the tab bar view controller is loaded you can use the delegate methods to inject updated dependencies and use it in viewDidAppear because viewDidLoad will not get called once its selected in the tab bar.
With some extra research, I have come up with an answer. Thanks to Will-m for the clue I needed. The current cavaet with this answer is that the first view loaded by the TabBarController will not get injected.
In order to inject data into ViewControllers from a UITabBarController, you need to do the following:
First, you need to set the RootViewController to be its own delegate when it loads.
You don't necessarily need the controller class to be its own delegate
unless you need to inject the required data from another class directly to the UITabBarController.
You also need to declare the delegate class as compliant to UITabBarControllerDelegate protocol.
// Declare UITabBarControllerDelegate protocol
class RootViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set class delegate to self
self.delegate = self
}
}
You need to set the RootViewController's delegate because the
delegate's protocol contains an important method:
tabBarController(_:shouldSelectViewController:). This method is
called when the RootViewController changes its active tab.
The "viewController" parameter of tabBarController(_:shouldSelectViewController:) is the instance of the child ViewController which the TabBarController is switching to. Provided you have assigned a protocol to that ViewController (so that the compiler knows your variable is declared in the class), you can inject the variable into the child.
So add the function to the RootViewController class like this:
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) {
// Get your view controller using the correct protocol.
// Use guard to make sure the correct type of viewController has been provided.
guard let vc = viewController as? YourProtocol
else { fatalError("wrong view controller type") }
// Assign the protocol variable to whatever you want injected into the class instance.
vc.VariableInYourProtocol = InjectedVariable
}
That's it. If you need to support controllers of different protocols, I might write something up on using a switch statement to do that. That
s just not something I've needed to work with as of yet.
Also, as a note, this method is applicable for CoreData practices in which only one managedObjectContext instance is passed between active ViewControllers. This method is used rather than retrieving different instances of the context directly from the application delegate for each ViewController.
Related
i am trying to get the presenting viewController of a viewController's view
The idea is like that :
i have a
viewController = CategoriesViewController
and i am presenting its view inside anther
viewController = HomeViewController
by using
CategoriesViewController.view
so when i want to reach the
HomeViewController from CategoriesViewController
i do this
let vc = self.presentingViewController as? HomeViewController
but it is telling me that it is nil
i tried the
.parentViewController
and it is returning
CategoriesViewController
In case you want to change a variable in HomeVieController using CategoriesViewController you could create your own protocol. You can use protocols to communicate between different controllers.
protocol ChangeVariableProtocol {
func changeVar(variable: Int)
}
In the protocol itself you only declare methods.
In your CategoriesViewController you would create a delegate Varibale like this
var changeVarDelegate: ChangeVariableProtocl?
Whenever you want to change the variable in CategoriesViewController you call your protocol method.
changeVarDelegate?.changeVar(10)
In HomeViewController you need to implement this protocol and initialize the changeVarDelegate variable.
extension HomeViewController: ChangeVarProtocol {
func changeVar(var: variable) {
// Implement your own logic here
self.valueToChange = variable
}
And make sure that you initialize changeVarDelegate when you are instancing your CategoriesViewController.
Hope this helps!
Create a callback closure property both in CategoriesViewController and in its view.
Right before presenting the controller assign the closure to change the value of a property to the callback property in the controller.
In viewDidLoad hand the closure over to the view.
Call the closure in the view to be executed in the presenting controller.
My app is using Tab Bar Controller which contains several View Controller in different tab. When user open the app, they will firstly enter FirstView. I would like to put some method in SecondView which refresh the FirstView. This is my FirstViewController.swift:
class FirstViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
I have tried to put
FirstViewController().viewDidLoad()
in my SecondViewController.swift, but this is not working. Is there any better way to refresh the FirstView?
Try this way by make a static reference of firstViewController and then through this reference you can call any function
class ViewController: UIViewController {
static var firstVC : ViewController?
override func viewDidLoad() {
super.viewDidLoad()
print("m on FirstViewController ")
ViewController.firstVC = self
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("m on SecondViewController ")
ViewController.firstVC?.viewDidLoad()
}
}
you can try this:-
instead of 0 pass the index of your FirstViewController
if let firstVC = self.tabBarController?.viewControllers?[0] as? FirstViewController {
firstVC.viewDidLoad()
}
You should not call viewDidLoad yourself. Its called only once when the view is loaded.
If you want to update the controllers view just before its displayed, you can use viewWillAppear for changing the layout or whatever you want to do.
The issue you're battling it how to tell another view controller that it needs to update its view. For this, you have two potential solutions because effectively, you're determining the best way to communicate between different objects.
Notifications are loosely decoupled and tend to be useful for one to many relationships. One object can fire off a notification and one or more objects can be listening for that notification. In your situation, a notification can be broadcast when a certain piece of state has changed in one view controller, and the other view controller can observe that notification so it can be notified when it should change.
Delegates are more closely coupled because they're one to one. They are often times implemented by creating a delegate property on an object that conforms to some protocol. Another object then assigns that delegate property to itself and implements the protocol so its implementation will be invoked whenever that function is called on the delegate. In your situation, each view controller could have a delegate property for some protocol(s). The tab bar controller can assign the delegate property to itself and handle the implementations of these functions. Therefore, whenever a change happens and a delegate is invoked, the tab bar controller can take can responsibility of telling which view controllers to update their view.
There are also of course other ways of handling your situation such as updating the view in viewWillAppear. This way, whenever a view controller appears on the screen, some code can execute that will update its view.
It ultimately depends on how you're storing application state and the design of your application.
A little background on the setting of our views:
Inside a NavigationController, we have a UITabBarController (with 3 tabs) with a UIViewController that has a UISearchController.
There is an error that if we leave the UISearchController active and switch to another view, when we return to the search view the entire screen is black.
However, when the UISearchController is not active and we switch views this does not happen.
We have tried to set the controller to not be active when segueing between views; however, when the UISearchController is active none of the segueing events get called (no log prints appear from viewWillDissapear, viewWillAppear, etc.)
Looking on other threads, we tried setting self.definesPresentationContext = true
but that does not work.
Has anyone else had this problem or know how to fix it?
Try to set the searchbarController active like this
self.resultSeachController.active = false
before you move on the next View
I faced the same problem and solved it as follows:
I extended UITabBarController and created a custom class TabBarController
class TabBarController: UITabBarController {
In that class I implemented its didSelectItem method, and in that method I called a method of the view controller that closes the search controller
// UITabBarDelegate
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
let vc = viewControllers![selectedIndex] as! CommonViewController
if vc.searchController.active {
vc.searchBarCancelButtonClicked_NoReload()
}
}
viewControllers is an array in UITabBarController that keeps all the view controllers belonging to the UITabBarController, and 'selectedIndex' is the index of the Tab (and the view controller) which was displayed, and thus one can get to the viewController that has the searchController active.
In my app all the view controllers are subclasses of a root class named CommonViewController where I put all the vars and methods that are common to all view controllers, such as all the search functionality. Therefore I simply check if the search controller is active and if it is I call a method that makes it inactive and does some other related cleanup.
I have a UITabBarController linked to 4 UIViewController each embedded in a NavigationViewController.
I need to check if when one of these view controllers is currently selected, and the user clicks the same tab bar item for the same view, it will trigger an unwind segue action.
So I think I need to add a UITabBarControllerDelegate but when I tried to add it to the UITabBarController class in the viewDidLoad() method:
let tabBarDel: UITabBarControllerDelegate = UITabBarControllerDelegate()
I see the following error:
'UITabBarControllerDelegate' cannot be constructed because it has no accessible initializers.
I extended the view controller class with UITabBarControllerDelegate. In the viewDidLoad() method I used self.tabBarController?.delegate = self
UITabBarController is a protocol, not a class. You can't instantiate a protocol. You need to create your own implementation of the protocol.
I've been converting an application to use storyboards. I'm sure this is a simple problem, but somehow I can't seem to figure out the 'correct' way of doing it, coming as we are from the old XIB world.
One of the subsections of it contains a UITabBarController, each with some subviews within it.
The action that launches this set of tabs works perfectly; I detect the segue, and set some data properties within my (custom) UITabBarController.
Next, I would like to be able to pass that data to the child views when they get created. But - because these tabs are simply 'relationships' and not segue's, I can't do what I do everywhere else, which is override the 'prepareForSegue' function.
In the old XIB universe, I'd simply bind some IBOutlets together between the tab controller and the child views. But I can't do that in storyboards, because the parent and children are separate 'scenes'.
I've tried making my UITabBarController class implement its own delegate, override 'didSelectViewController' and doing 'self.delegate = self' which almost works, except for the fact that it is never called with the first tab when the view is initially shown.
What's the "correct" (or 'best') way to do this? Please don't tell me to get/set some value on the app delegate, as this is 'global variable' territory - nasty.
Try looping through the view controllers on the UITabBarController, e.g. in this example the setData method is called from the segue in to the UITabBarController, and it then loops through the child view controllers, making a similar call on the child controller to set the data on that too;
- (void)setData:(MyDataClass *)newData
{
if (_myData != newData) {
_myData = newData;
// Update the view.
[self configureView];
}
}
- (void) configureView {
for (UIViewController *v in self.viewControllers)
{
if ([v isKindOfClass:[MyDetailViewController class]])
{
MyDetailViewController *myViewController = v;
[myViewController setData:myData];
}
}
}