I have a main view controller which has the following tuple:
var textsForTabs = (tab1: "this is tab one text", tab2 : "this is tab 2 text")
from the main view controller i want this data to pass to a tab controller with 2 childs: tab 1 and tab 2 which have labels on them, the first tab should show in it's label the text from textsForTabs.tab1 and the second textsForTabs.tab2.
What are my options here and the best practice?
I tried to instantiate the tabbarcontroller and in it, get the first view controller and assign the value from the tupple to an instance variable, later to be used when view is being shown: like so:
let tabBarController = segue.destinationViewController as UITabBarController
let ftc = tabBarController.viewControllers![0] as firstTabController
let stc = tabBarController.viewControllers![1] as secondTabController
ftc.setLabelTxt(textsForTab.tab1)
stc.setLabelTxt(textsForTab.tab2)
I got that from here:
how to send data from UIViewController to UITabBarControllers first tab in swift ios
But i read somewhere this is hacky, what are my other options?
I suggest to set the label txt when viewDidLoad is called for both firstTabController and secondTabController
Here an example for firstTabController :
class FirstTabController: UIViewController {
override func viewDidLoad() {
if let tabBarController = self.tabBarController as? MyCustomTabBarController {
self.setLabelTxt(tabBarController.textsForTabs.tab1)
}
}
...
}
Related
I have login few screens and controllers in my app. First screen is screen with button and moves user to next login view with username, password field and login button. On the controller i have function onClickButton and when i have good data i request to the server with this data.
When server give me callback i have many params about user to set in label in next view.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
My code is
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "afterLoginView" {
if let secondVC = segue.destination as? TabBarViewController {
secondVC.finalName = self.username
}
}
}
When i want transfer my data directly to tableViewController i have error
Thread 1: signal SIGABRT
I do not understand what I'm doing wrong
You'll need these values in almost all view controllers. Create a singleton class to store the logged in user values like this
class UserDetails: NSObject, Codable {
static let shared = UserDetails()
private override init() {
super.init()
}
var finalName: String?
var otherDetails: String?
}
Now when you receive the response from the login api, assign the values in this singleton class.
UserDetails.shared.finalName = "something"//Name received from server callback
Now you can access these values from any view controller.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(UserDetails.shared.finalName)
}
}
You have some work to do to get to the right view controller. Since your segue is only pointing at the UITabBarViewController, you should put in another guard or if/let statement to get you to the UINavigationController, and then another to finally get you to the UITableViewController, where you can actually refer to your finalName variable.
That would look something like:
if let secondVC = segue.destination as? TabBarViewController {
if let navCon = secondVC.viewController[0] as? UINavigationController {
if let tableVC = navCon.topViewController as? nameOfYourTableVC {
tableVC.finalName = self.username
}
The code is untested, just typed off the top of my head, so please proceed with due caution. Issues such as which tab is the correct NavController would also need to be addressed.
You need to use the actual name of your tableView class in that last if/let. A generic UITableViewController will not include your custom variables.
When server give me callback i have many params about user to set in label in next view.
This is a great example of why you should keep the M in MVC. When you get a response back from the server, store the returned data in your data model. (If you don't have a data model, you should make one.) When a view controller gets some data from the user, such as a user name, it should store that in the model. There's little reason to pass raw data back and forth between view controllers directly... just make sure that all your view controllers have a reference to the model, and have them get and set values there as needed.
This kind of approach will make your code a lot more flexible. It allows view controllers to worry about what they need to do their job, and it gets them out of the business of caring what other view controllers need.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
It might make more sense to load the tab bar controller and then present the login view controller(s) modally. The view controllers that are managed by the tab bar controller can all be set up to refuse to do anything useful until the data they need is present in the data model, and that lets the tab bar controller be the root view controller. That will make it easy to set the model for each of it's child view controllers when the app starts up, and the app can then present the modal login view controllers, also set up with references to the model.
I have a problem when it comes to the status bar and hiding it.
The setup
I have a BaseViewController that has a slide out menu. This BaseViewController is also the root controller of the application [as set inside AppDelegate]:
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = BaseController()
As soon as I select a menu item the BaseViewController is populated by the corresponding ViewController [after I embed it into a navigation controller].
Menu item A: ViewControllerA
Menu item B: ViewControllerB
Menu item C: ViewControllerC
Say that I select the Menu Item A (the following code takes place inside BaseViewController):
let activeVC = UINavigationController(rootViewController: ViewControllerA())
view.addSubview(activeVC.view)
addChild(activeVC)
When I select another menu item (say item B), I first remove the previous active view controller (in this case item A) and then I add the ViewControllerB the same way as I did with ViewControllerA:
This is how I remove the previous active view controller:
activeVC.view.removeFromSuperview()
activeVC.removeFromParent()
Manipulating the status bar in each view controller separately:
I set the View controller-based status bar appearance to YES in plist control the appearance of the status bar in every view controller:
Then I go into the ViewController I want to hide the status bar and I add the following code:
override var prefersStatusBarHidden: Bool {
return true
}
The problem
If I want to hide the status bar inside any of the ViewController A, B, or C, I can't. Overriding the prefersStatusBarHidden and setting it to "true" will do nothing.
If I override the prefersStatusBarHidden and setting it to "true" into the BaseViewController, then the BaseViewController and also any of the ViewController A, B, and C will hide the status bar.
What I want
I want to be able to hide the status bar on ViewControllerB without hiding it on the rest. Also a million dollars, but I will settle with the solution!
Thanks in advance!
You'll need to override var childForStatusBarHidden: UIViewController? for BaseController and for UINavigationController. For example:
override var childForStatusBarHidden: UIViewController? {
return children.first
}
and
extension UINavigationController {
open override var childForStatusBarHidden: UIViewController? {
return topViewController
}
}
I had a use case where I had to present a ViewController nested inside a navBar controller the only way which worked for me is adding the following on initilization of a presented ViewController.
modalPresentationCapturesStatusBarAppearance = true
I have a UITabBarController implemented by a custom class (eg HomeTabBarController) and in my storyboard I've attached to it 3 ViewController as it's child.
I know that I can use, in order to select a particular view controller, in my UITabBarController:
selectedIndex = 2
But I would like to make my project a bit more flexible, so I would like to select a child tab only knowing it's type, not it's position. How can I do it?
As described in the StackOverflow documentation I'm going to answer my own question.
Let's make an example, you have a UITabBarController with 3 childs:
HomeTabBarController
CustomAViewController
CustomBViewController
CustomCViewController
In your HomeTabBarController controller you can put a func as this:
func selectCustomATab() {
var tab = 0
for v in viewControllers! {
for k in v.childViewControllers {
if k is CustomAViewController {
tab = viewControllers!.indexOf(v)!
}
}
}
selectedIndex = tab
}
And that's all, you can repeat for every ViewController child as you like.
Then in any ViewController child you can do something like that in order to switch tab:
(self.tabBarController as! HomeTabBarController).selectCustomATab()
Completely ignoring what's the CustomAViewController position in the tab array.
set you're tab bar index value
tabBarController?.selectedIndex = 2
I want to pass data from one tabBar controller to another,
I am switching tabBar using tabBarController?.selectedIndex = 0
I tried many option but unable to pass data to another tab, is there any simple solution like we pass data using navigation controller?
Tabs are usually some custom UIViewControllers. From these view controllers (tabs) you can also get access to the UITabBarController with something like:
if let mainController = UIApplication.sharedApplication().delegate?.window??.rootViewController as? YourMainTabBarControllerClass {
mainController.someVariable = 123
}
Here you have to be careful, because the tab bar controller may not be the rootViewController, see this question for more details.
In order to store some properties in the UITabBarController you have to implement your own class by extending UITabBarController and then set the custom class in the StoryBoard. The class will then look like:
class YourMainTabBarControllerClass: UITabBarController {
// some custom variables here...
var someVariable = 0
...
}
I have 1 tab bar controller in storyboard and 1 UIViewController associated with it. I would like to re-use the same UIViewController in order to create second item in tab bar. When I am creating second relation from tab bar to view controller I need to specify 2 different items names. How can I re-use same view controller and set different items names from storyboard? If not possible to do it in storyboard, then do I have to rename each in tab bar controller class or there is better way?
I was going to provide different data to view controller in prepareforsegue.
UPDATE:
little more details and clarification
In above screenshot marked VC at the moment is reachable a) directly from tab, b) through 3 transitions. I want to add another DIRECT relation to initial tab bar, just like in case of "a".
I can give you a little tweak for that and at least that worked for me.
Drag a tabbarcontroller and associated tab item view controllers to
your storyboard. Name them as you like.
Create an extra view controller that you want to reuse from your storyboard.
Add container views to each tab item view controllers and remove their default embedded view controllers.
Create embed segue from each tab item controller to your re-usuable view controller.
The configuration looks something like the following:
Thus you can use the same embedded VC for different tabbar item. Obviously if you need the reference of the tabbarcontroller, you need to use self.parentViewController.tabBarController instead of self.tabBarController directly. But it solves the issue of reusing a VC right from the storyboard.
I've found much simpler solution using storyboard only.
Setup your storyboard like this:
Then in your Navigation Controller Identity Inspector set Restoration ID like this:
And in your ViewController class file put the following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = parent?.restorationIdentifier
label.text = parent?.restorationIdentifier
}
or do what you like based on parent?.restorationIdentifier value
If you don't want the Navigation TopBar to appear on the ViewController just set it to None in Attributes Inspector of the desired Navigation Controller like this:
That's it! Hope it helps.
Yes you can.
All you need to do is to create a new View Controller in StoryBoard as if there is going to be a different View Controller for tab 2. Then Select the 2nd view controller and simply add its class name the same classname of view controller 1
Things to note:
When you are sharing the same view controller class (.m ad .h) files, each tab will create a NEW instance of that class.
Edit:
This works as long as you have either a "custom" cell scenario (i.e. reusing two table view controllers) OR, have all your views inside a "container view" (i.e. reusing UIView).
I needed slightly different solution than the accepted answer. I needed to use same Table View Controller with the different data source for different tab bar items. So in the storyboard, i created two Navigation Controllers with same classes like this;
I also give different "Restoration ID" to each of them.
For the first one, I gave "navCont1" and "navCont2" for the second one.
In subclass("GeneralNavCont") of these Navigation Controllers; I override init method and check restoration id of self. Then i initiate my TableViewController and set its data source based on ids like this;
class GeneralNavCont: UINavigationController {
var dataSource1 = [Countries]()
var dataSource2 = [Cities]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initiateTableVCBasedOnId()
}
func initiateTableVCBasedOnId() {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let tableVC = storyBoard.instantiateViewController(withIdentifier: "tableVC") as! MyTableViewController
if self.restorationIdentifier == "navCont1" {
tableVC.dataSource = self.dataSource1
self.viewControllers = [tableVC]
}
else if self.restorationIdentifier == "navCont2" {
tableVC.dataSource = self.dataSource2
self.viewControllers = [tableVC]
}
}
}
Hope it helps someone. Cheers.