I got a TabBarController with two Views in my project. Now I want to set a FirstViewController with two buttons as Initial View Controller when the app launches. The first button should show the FirstView in the TabBarController and the second button the second one. When one of the two buttons is pressed the FirstViewController should disappear and it should only be possible to navigate between the two Views with the Tabs in TabBarViewController.
I did some minor edit, and tested the code I wrote and it works. Control drag from firstButton over to the TabBarController and select Kind as "Show". Then do the same with secondButton.
In your view with the two buttons, I call it First:
import Foundation
import UIKit
class First: UIViewController {
var firstWasClicked = false
#IBAction func firstButtonAction(sender: UIButton) {
firstWasClicked = true
}
#IBAction func secondButtonAction(sender: UIButton) {
firstWasClicked = false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let controller = segue.destinationViewController as! TabBarController
controller.firstSelected = firstWasClicked
}
}
then in your TabBarController:
import Foundation
import UIKit
class TabBarController: UITabBarController {
var firstSelected = true
override func viewDidLoad() {
if(firstSelected) {
self.selectedIndex = 0
}
else {
self.selectedIndex = 1
}
}
}
This is probably what you want.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didTapFirst(button: UIButton) {
showViewControllerAt(index: 0)
}
#IBAction func didTapSecond(button: UIButton) {
showViewControllerAt(index: 1)
}
func showViewControllerAt(index: NSInteger) {
let tabBarController = self.storyboard?.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
tabBarController.selectedIndex = index
UIApplication.shared.keyWindow?.rootViewController = tabBarController
}
}
Don't forget to set the Storyboard ID of your UITabBarController.
Related
This question already has answers here:
Swift pass data through navigation controller
(8 answers)
Closed 4 years ago.
I need to send bool value to Second viewController based on some condition.
but in Second ViewController the value of bool variable is always false.
This is First ViewController code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func moveTo(_ sender: Any) {
self.performSegue(withIdentifier: "Move2", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "Move2"
{
let secondVC = segue.destination as? SecondViewController
secondVC?.second = true
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The SecondViewController code is
import UIKit
class SecondViewController: UIViewController {
var second = Bool()
override func viewDidLoad() {
super.viewDidLoad()
print(second)
// Do any additional setup after loading the view.
}
But this Value of second variable is false.
Whats wrong in my code. need help. TIA..
EDIT : In both ViewController i have navigation controller. That's problem. SecondVC is SecondViewController's navigation controller, so that i can't pass data to that secondVC.
Go to your Storyboard and select SecondViewController (the yellow circle on the top left. Go to identity inspector and select SecondViewController from the Class drop down.
See image below:
Keep things simple and clean :
FirstVC :
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "Move2"
{
let secondVC = segue.destination as? SecondViewController
secondVC.second = true
}
}
SecondVC :
class SecondViewController: UIViewController {
var second: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
print(second)
}
}
Just tested this myself. true is printed.
I am using this library: swift-slide-menu
Is there any way to change a view programmatically? I am using the same viewcontroller to show two different datasets in a tableview. I have two buttons in the menu, one for each dataset. When clicking on button1 I go to the ListVC and everything works fine. When clicking on button2 I go to an empty ViewController that sets the url for the dataset and segues right away to ListVC.
When I do the segue, the menu button is not shown in ListVC. This is probably because I am not adding a childView to the BaseViewController when I segue. But is there any way to add a code inside my empty ViewController that adds ListVC as a childView, instead of doing a normal segue?
PS.
I am trying to prevent having the same code inside two different viewcontrollers and having 2 copies of the same vc in storyboard.
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
addChildView("AllDatasetID", titleOfChildren: "All data", iconName: "icon1") //ListVC
addChildView("FilteredDatasetID", titleOfChildren: "My data", iconName: "icon2") //EmptyVC that segues to ListVC
}
}
class EmptyVC: UIViewController {
override func viewDidAppear(_ animated: Bool) {
self.performSegue(withIdentifier: "toListVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toListVC" {
if let destinationVC = segue.destination as? ListVC {
destinationVC.theUrl = Settings.myFilterUrl
}
}
}
}
The solution was to access inside the ListVC a variable from the menu that stored the title for the current view. The menu subclasses BaseViewController and several variables are accessable from the BaseViewController through the navigationController.
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
addChildView("ListVCScreenID", titleOfChildren: "My datapoints", iconName: "icon1")
addChildView("ListVCScreenID", titleOfChildren: "All datapoints", iconName: "icon2")
}
}
class ListVC: UIViewController {
var theUrl: String?
override func viewDidLoad() {
super.viewDidLoad()
let vc = self.navigationController?.viewControllers
var counter = 0
for v in vc! {
if v as? ViewController != nil {
print("title is: \(v.title)")
if(v.title == "My datapoints") {
theUrl = "getMyData"
}
else {
theUrl = "getAllData"
}
}
}
}
}
I want pass data between two ViewControllers throw the TabBarController.
Inside the first ViewController I located textField and button.
Inside the second I located Label.
When I write some text in textField and push button, I expect that this text appear in the Label in the second ViewController. But nothing happens.
And my code:
First ViewController:
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func enter(_ sender: Any) {
if textField.text != "" {
if let window = UIApplication.shared.delegate?.window, let tabBarController = window?.rootViewController as? UITabBarController, let second = tabBarController.viewControllers?.first as? SecondViewController {
second.label.text = textField.text
tabBarController.selectedIndex = 0
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Second ViewController:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var myString = String()
override func viewDidLoad() {
super.viewDidLoad()
label.text = myString
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I think the problem is in
tabBarController.viewControllers?.first as? SecondViewController
You probably want to do this instead:
tabBarController.viewControllers?[1] as? SecondViewController
Don't forget arrays are indexed from 0, so viewControllers?[1] actually returns the second element in the array.
I'm not recommending that you do it this way. This is an explanation for understanding.
The viewControllers in a tabBarController know their tabBarController. You can access it with self.tabBarController.
The secondViewController is the second one in the list of viewControllers so let second = tabBarController.viewControllers?[1] as? SecondViewController.
If you haven't visited the secondViewController yet, its view will not have loaded, so the outlets will still be nil. You can force the view to load with _ = second.view.
If you want to switch to the second tab, then you need to use tabBarController.selectedIndex = 1.
#IBAction func enter(_ sender: Any) {
if textField.text != "" {
if let tabBarController = self.tabBarController as? UITabBarController, let second = tabBarController.viewControllers?[1] as? SecondViewController {
// make sure view has loaded
_ = second.view
second.label.text = textField.text
// change to second tab
tabBarController.selectedIndex = 1
}
}
}
A better way...
Instead of setting the outlet directly, you should instead pass the string to a property of the SecondViewController:
second.myString = textField.text ?? ""
and then assign that string to the label in an override of viewWillAppear.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label.text = myString
}
The reason to set it in viewWillAppear is that viewWillAppear will run every time before the view is displayed. viewDidLoad will only run once when the view is first loaded. Since you want the functionality to work multiple times, viewWillAppear is the correct override.
First i think your view controllers that are representing tabs should be embedded into navigation controllers which would then be linked to the TabBarController.
Secondly, the preferred and recommended way to send data between controllers is through protocols (delegates). Here is a good example you can check out, step by step: https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef
However if you're looking for a quick fix of your solution i think that Bruno Phillipe's answer gets it to some extent, but not quite. We can't really be sure what controller is at what index in the view controller list. I think this should work:
#IBAction func enter(_ sender: Any) {
if textField.text != "" {
if let window = UIApplication.shared.delegate?.window, let tabBarController = window?.rootViewController as? UITabBarController {
//check if there are view controllers in the tabBarController
guard let vcList = tabBarController.viewControllers else {
return
}
for controller in vcList {
if let second = controller as? SecondViewController {
//this will be executed only when a controller is SeconfViewController
second.label.text = textField.text
tabBarController.selectedIndex = 0
}
}
}
}
}
EDIT:
I tried it myself and the problem is that you were trying to set the label.text when in fact the label component was never initialised. I think if you simply stored the textField value into myString variable in SecondViewController it would work (not sure).
However here's the solution using a protocol (delegate) which is the right way to send data between controllers. Ask any questions you might have. This should work:
FirstViewController:
import Foundation
import UIKit
protocol LabelChangeDelegate: class {
func changeLabelWithText(_ text: String?)
}
class FirstViewController: UIViewController {
weak var delegate: LabelChangeDelegate?
#IBOutlet weak var textField: UITextField!
#IBAction func enter(_ sender: UIButton) {
if textField.text != "" {
if let window = UIApplication.shared.delegate?.window, let tabBarController = window?.rootViewController as? UITabBarController {
//check if there are view controllers in the tabBarController
guard let vcList = tabBarController.viewControllers else {
return
}
for controller in vcList {
if let second = controller as? SecondViewController {
//this will be executed only when a controller is SeconfViewController
//set the delegate - who needs the data
delegate = second
//call the delegate function which will commmunicate with the delegate
delegate?.changeLabelWithText(textField.text!)
//don't know why you need this
tabBarController.selectedIndex = 0
}
}
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
SecondViewController:
import Foundation
import UIKit
class SecondViewController: UIViewController, LabelChangeDelegate {
#IBOutlet weak var label: UILabel!
//lazy init
lazy var myString = String()
override func viewDidLoad() {
super.viewDidLoad()
//set label when the view loads, not in the first controller
label.text = myString
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//delegate function
func changeLabelWithText(_ text: String?) {
guard let sentText = text else {
//no text sent
return
}
myString = sentText
}
}
I am trying to pass data back from the second viewController.
I can do that without NavigationController. But now I need to use NavigationController. Then my code does work as before. The data wont pass.
Here is the simple code:
In first viewController
class ViewController: UIViewController, backfromSecond {
#IBOutlet weak var text: UILabel!
var string : String?
override func viewDidLoad() {
super.viewDidLoad()
self.string = "Start here"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.text.text = self.string
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? secondViewController{
destinationViewController.delegate = self
}
}
func back(text: String) {
self.string = text
print(text)
}
}
And Second viewController:
protocol backfromSecond {
func back(text: String)
}
class secondViewController: UIViewController {
var string : String = "nothing here"
var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
delegate?.back(text: string)
// Do any additional setup after loading the view.
}
}
What is wrong here?
Suppose A & B are two controllers and you first navigated from A to B with some data. And now you want to POP from B to A with some data.
Unwind Segues is the best and recommended way to do this.
Here are the steps.
Open A.m
define following method
#IBAction func unwindSegueFromBtoA(segue: UIStoryNoardSegue) {
}
open storyboard
Select B ViewController and click on ViewController outlet. press control key and drag to 'Exit' outlet and leave mouse here. In below image, selected icon is ViewController outlet and the last one with Exit sign is Exit Outlet.
You will see 'unwindSegueFromBtoA' method in a popup . Select this method .
Now you will see a segue in your view controler hierarchy in left side. You will see your created segue near StoryBoard Entry Piont in following Image.
Select this and set an identifier to it. (suggest to set the same name as method - unwindSegueFromBtoA)
Open B.m . Now, wherever you want to pop to A. use
self.performSegueWithIdentifier("unwindSegueFromBtoA", sender: dataToSend)
Now when you will pop to 'A', 'unwindSegueFromBtoA' method will be called. In unwindSegueFromBtoA of 'A' you can access any object of 'B'.
That's it..!
I think your problem is in the prepare for segue method. If the view controller is on a navigation stack i think your code should be something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? UINavigationController).topViewController as! secondViewController{
destinationViewController.delegate = self
}
}
You can use unwind segues to pass data back.
Here's a tutorial
https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
This works me well.
1st VC
class ViewController: UIViewController, backfromSecond {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func Passingfrom1stVCTo2ndVC(_ sender: AnyObject) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3{
vc.dataFrom1StVC = "message send from 1st VC"
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
}
func back(text: String) {
print("data\(text)")
}
}
2nd VC.
protocol backfromSecond: class {
func back(text: String)
}
class ViewController3: UIViewController {
var dataFrom1StVC : String? = nil
week var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func DataSendFrom2ndVCTo1stVC(_ sender: AnyObject) {
self.delegate?.back(text: "Message Send From 2nd vc to 1st VC")
self.navigationController?.popViewController(animated: true)
}
}
I hope it will work you. If any problem then ask me i will help you.
I'm presenting a tutorial of 4 UIViewController when my app starts the first time.
Every UIViewController has a Button with a segue presenting the next ViewController.
The last ViewController has a button "Let's start" which should dismiss the tutorial completely.
Problem:
It this dismiss all ViewControllers except the first. I don't understand why?!
What I expect:
On the last ViewController4 I'm calling the dismissIntroduction() function of the first ViewController, so I except ALL ViewControllers (ViewController1 included) should disappear.
When I put a button on the first ViewController and call the function "dismissIntroduction()" it disappears.
ViewController 1 (WelcomeViewController):
protocol WelcomeViewDelegate {
func dismissIntroduction()
}
class WelcomeViewController: UIViewController, WelcomeViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func dismissIntroduction() {
self.dismissViewControllerAnimated(true, completion: nil)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController2
destination.delegate = self
}
}
ViewController 2:
class ViewController2: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController3
destination.delegate = self.delegate
}
}
ViewController 3:
class ViewController3: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController4
destination.delegate = self.delegate
}
}
ViewController4 (the last one):
class ViewController4: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressLetsStart(sender: AnyObject) {
self.delegate!.dismissIntroduction()
}
}
EDIT:
I got it working, when I put the dismissViewControllerAnimated function TWO times!?
func dismissIntroduction() {
self.dismissViewControllerAnimated(true, completion: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
But why? I don't understand the logic behind...
You are looking at the wrong solution to your problem, what you need to do is look for Unwind Segues. Go through this tutorial: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
I solved the problem now with following function in last ViewController:
#IBAction func pressLetsStart(sender: AnyObject) {
self.dismissModalStack()
}
private func dismissModalStack() {
var vc = self.presentingViewController! as UIViewController
while (vc.presentingViewController != nil) {
vc = vc.presentingViewController!;
}
vc.dismissViewControllerAnimated(true, completion: nil)
}