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.
Related
I have a view controller that manages my entire view. Inside this view, I would like to create another four views which each have some button selector functions. I was wondering if it would be possible/smart for me to create a new View Controller class that has the functionality of these four sub-views and then instantiate these view controllers in the view controller that manages the full view?
Yes it is certainly possible. And it is often smart to do so. In your case it sounds like you may want to subclass UIView or UIControl though.
One of the main reasons to create a view controller is to keep track of the life cycle events of a view. In the view controller methods such as viewDidLoad() or viewDidAppear()
Creating view controllers works well for cases such as pages and segments, and navigation with UITabViewControllers and UINavigationControllers
You can generally just create view controller's and use them, something like this:
class MyViewController: UIViewController {
override func viewDidLoad() {
let subViewController = AnotherViewController()
view.addSubview(subViewController.view)
}
}
class AnotherViewController: UIViewController {
}
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.
So, I have an app which has as its root view controller a menu bar controller class which I named MenuTabBarController, and which holds a number of UIViewControllers. One of those view controllers is the homepage which I called HomeViewController and which is displayed on app load since it occupies index 0 position. What I'm trying to do is create a subview in the viewDidLoad method of MenuTabBarController, but place this subview within HomeViewController. Here's what I came up with
let homeViewController = HomeViewController()//An instance of HomeViewController created at global level
class MenuTabBarController: UITabBarController,UITabBarControllerDelegate,UIPopoverPresentationControllerDelegate{
var mainBox: UIView!//This is the sub view reference declared as an optional
override func viewDidLoad()
super.viewDidLoad()
self.delegate = self
mainBox = UIView(frame: CGRectMake(0,0,200,200))//Initialize mainBox
homeViewController.view.addSubview(mainBox)//Attempt to add mainBox to homeViewController
}//End viewDidLoad
}//End class definition
Well the mainBox doesn't get added because when I attempt to run the app, a blank page stares at me. If I add the mainBox to the menu bar's view like so
self.view.addSubview(mainBox)
it gets added. How can I add it to the homeView though?
You can also do it by accessing index of tabs by getting all controllers of tab bar controller.
(self.viewControllers[0] as! HomeViewController).view.addSubview(mainBox)
Hope it will work for you!!!
You can't add subview to tabbar controller because it's manage all tabs, it's not visible as VC to users. So if you want to ass view in any tac(VC) then you can add it in it's viewdidload method or If you want to add view from tabbarcntoller class then you can get all view controllers in array by calling viewControllers by self. self.viewControllers[0] is you first VC (Home in your case) where self.viewControllers[1] should be second.
Hope this will help :)
It seems that you are creating an extra instance of the home view controller (that is never made visible btw.) and add the sub view into it.
I think what you really want to do is to create and add the sub view in viewDidLoad of your HomeViewController class.
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.
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.