How to pass data from First TabBarItem to Second TabBarItem - ios

Above I have a TabBarController with 4 buttons. When I press the second TabBarItem(Cars) or third TabBarItem(Bicycles) I want to pass some values from the firstTabBarItem(HomeScreen) .
I'm using Swift 5 and I don't have a SceneDelegate in my main project (is a 3 years old project), I have only the AppDelegate and this is why I wasn't able to try the only example which I found from Stackoverflow about a similar question like mine.
I managed somehow to pass the data between ViewControllers through TabBarController but I don't think is the right approach anymore (as some people from this community are also saying this on some threads) because when I click on different TabBarItems the data get lost and after is showing up again but with the wrong values etc.
I will attach below a GIF with the bugs which I'm getting while I navigate and also my current code with a link to the DEMO project on GitHub (https://github.com/florentin89/PassDataTabBarController/)
Can you help me please to pass the data using the right implementation ?
Here is my code for HomeScreen ViewController:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet var textfieldHoldingCarsValue: UITextField!
#IBOutlet var textfieldHoldingBicyclesValue: UITextField!
var valueForCarsScreen = String()
var valueForBicyclesScreen = String()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated: Bool) {
valueForCarsScreen = textfieldHoldingCarsValue.text ?? String()
valueForBicyclesScreen = textfieldHoldingBicyclesValue.text ?? String()
if let navController = self.tabBarController?.viewControllers?[1] as? UINavigationController{
if let carsTab = navController.children.first as? PostcodeViewController{
carsTab.receivedValueFromHomeScreen = valueForCarsScreen
}
}
if let navController = self.tabBarController?.viewControllers?[2] as? UINavigationController{
if let bicyclesTab = navController.children.first as? PostcodeViewController{
bicyclesTab.receivedValueFromHomeScreen = valueForBicyclesScreen
}
}
}
// Logout the user and navigate to Login screen
#IBAction func logoutTapped(_ sender: UIBarButtonItem) {
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
}
Here is my code for the ViewController used for second and third TabBarItem:
import UIKit
class PostcodeViewController: UIViewController {
#IBOutlet var receivedValueLabel: UILabel!
var receivedValueFromHomeScreen = String()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
if self.tabBarController?.selectedIndex == 1 {
receivedValueLabel.text = "Value received for Cars: \n" + receivedValueFromHomeScreen
}
if self.tabBarController?.selectedIndex == 2 {
receivedValueLabel.text = "Value received for Bicycles: \n" + receivedValueFromHomeScreen
}
}
}
Here is a GIF with the bugs/glitches which I'm getting when I navigate between ViewControllers:
Thank you for reading this !

The reason is that viewWillAppear of PostcodeViewController is called before the selectedIndex of your tabbar is changed. The first time it works ok. Just put the line:
print("selected index = \(self.tabBarController?.selectedIndex ?? -1)")
in your viewWillAppear and you will see.
The easiest way to fix your code is to move the logic of PostcodeViewController from viewWillAppear to viewDidAppear.

a simple workaround can be.. store data in a static variable in login class, you can access it in other classes.
long workaround is,
create a Custom TabBar programmatically, pass data from login viewController to parent CustomTab bar, and pass data to next screen with the help of delegates.

The fix was to move the code of PostcodeViewController from viewWillAppear to viewDidAppear like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("Selected index: \(self.tabBarController?.selectedIndex ?? -1)")
if self.tabBarController?.selectedIndex == 1 {
receivedValueLabel.text = "Value received for Cars: \n" + receivedValueFromHomeScreen
}
if self.tabBarController?.selectedIndex == 2 {
receivedValueLabel.text = "Value received for Bicycles: \n" + receivedValueFromHomeScreen
}
}
Now everything works perfect !
GIF below:

Related

How can you update a label when the tab/view controller of a tab bar changes? (swift)

I am working on a app with a tab bar with four separate tabs. I for example save a number that can be changed/modified in three of those tabs. A label, that displays that number is included in all four of those tabs. However, when I change the number in one tab, the labels of the other tabs are not updated when I switch tabs.
I tried including this in each viewDidLoad() of the view controllers of the tabs:
self.tabBarController?.delegate = self
and then used:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 2{
updateLabel()
}
}
If I do that in all viewControllers and select the tabs, the view is still changed, but the tabBarController fails, so that the function updateLabel() isn't called at all.
If I only include the code in the First View Controller and expand this part:
if tabBarIndex == 2{
updateLabel()
}
to cover all tabs, the respective functions (updateLabel()) of the classes are called but the Label itself is nil.
#IBOutlet weak var HoursLabel: UILabel!
func updateLabel(){
if HoursLabel != nil{
//code
}
}
And the label isn't updated.
Does someone know how to fix this? Thank you so much in advance :)
hey i got some solutions for you hope this is what you want.
i'm updating your tab label value with Notification Center here is some code
// TabbarController Code
import UIKit
class TabbarViewController: UITabBarController, UITabBarControllerDelegate {
//MARK Life View Cycle
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(self.TabbarNoitifuntionCall), name: NSNotification.Name(rawValue: "CallTabBarNotificationsCenter"), object: nil) // this code will call when ever you update your value in view controller
}
//MARK:- Private Functions
#objc func TabbarNoitifuntionCall(_ notification: Notification) {
self.viewControllers![0].title = "First " + String(notification.object as! Int)
self.viewControllers![1].title = "Second " + String(notification.object as! Int)
}
}
// First Tabbar Controller
import UIKit
class FirstVc: UIViewController {
//MARK:- IBOutlet
#IBOutlet weak var update_lbl: UILabel!
//MARK:- Variables
var count = Int()
//MARK:- Life View Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK:- Private Function
#IBAction func buttonAction(_ sender: UIButton) {
count = count + 1
update_lbl.text = String(count)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "CallTabBarNotificationsCenter"), object: count) // here we will call notifications center so our labal value upadate
}
}
// Second Tabbar Controller
import UIKit
class SecondVc: UIViewController {
//MARK:- IBOutlet
#IBOutlet weak var update_lbll: UILabel!
//MARK:- Variables
var count = Int()
//MARK:- Life View Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK:- Private Function
#IBAction func button_action(_ sender: Any) {
count = count + 1
update_lbll.text = String(count)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "CallTabBarNotificationsCenter"), object: count) // here we will call notifications center so our labal value upadate
}
}
i hope this is the simple way if anyone add something then please go head
Thank you.

delegate method not getting called with UITabBarController

In FourthViewController, I have a slider, which has values ranging from 1 to 1000. The value that is set gets sent via the delegate to PatternViewController, where it should be used to do sth (I put the print for testing purposes).
I've worked with delegates before and it was all ok, checked the code multiple times and multiple answers here on stack, I can't seem to find the issue. Any help would be much appreciated
update: I have added a button so that it would be easier to track along. It turns out that by pressing first time the button, nothing happens. but if I first checkout the PatternViewController, then I go back to FourthViewController and press the button, the delegate gets triggered. anyone got any idea on why is this happening?
FourthViewController
import UIKit
class FourthViewController: UIViewController {
//MARK: Outlets
#IBOutlet var persistenceButton: UIButton!
#IBOutlet var persistenceSlider: UISlider!
#IBOutlet var persistenceLabel: UILabel!
weak var delegate: FourthViewControllerDelegate?
//MARK: Stored Properties - Constants
let userDefaults = UserDefaults.standard
let keyName = "sliderValue"
//MARK: Initializer
override func viewDidLoad() {
super.viewDidLoad()
loadSliderValue()
initialSetUp()
}
//MARK: Actions
#IBAction func handleValueChanged(_ sender: UISlider) {
updateLabel()
persistSliderValue(value: persistenceSlider.value, key: keyName)
}
//MARK: Methods
func updateLabel() {
persistenceLabel.text = String(format: "%.2f", persistenceSlider.value)
}
func persistSliderValue(value: Float, key: String) {
userDefaults.set(value, forKey: key)
}
func loadSliderValue() {
let persistedValue = userDefaults.float(forKey: keyName)
persistenceSlider.value = persistedValue
updateLabel()
}
}
func initialSetUp() {
persistenceButton.addTarget(self, action: #selector(handleButtonPressed), for: .touchUpInside)
}
#objc func handleButtonPressed() {
delegate?.valueChanged(value: persistenceSlider.value)
}
}
PatternViewController
import UIKit
class PatternViewController: UIViewController, FourthViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
func setUp() {
if let tabBar = self.tabBarController, let viewController = tabBar.viewControllers, let fourthViewController = viewController[3] as? FourthViewController {
fourthViewController.delegate = self
}
}
func valueChanged(value: Float) {
print(value)
}
}
It depends upon how you instantiated the tab view controller. If you do it with storyboards, for example, the view controllers for the respective tabs are instantiated lazily, only instantiated as the user taps on them. (This helps reduce latency resulting from instantiating all four of the tabs’ view controllers.)
While you theoretically could go ahead and have the tab bar controller instantiate the four view controllers programmatically up front, rather than just-in-time via the storyboard, I might instead consider specifying a UITabBarControllerDelegate for the tab bar controller. Have the tab bar controller’s delegate method update the relevant tab’s view controller’s model.
Here is an example with two tabs, the first has a slider and the second has a label that displays the slider’s value. In this simplified example, I’ve moved the model object (the value associated with the slider) into the tab bar controller, and it passes it to the second view controller when you select the associated tab.
// TabViewController.swift
import UIKit
class TabBarController: UITabBarController {
var value: Float = 0.5
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
// MARK: - UITabBarControllerDelegate
extension TabViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let viewController = viewController as? SecondViewController else { return }
viewController.value = value
}
}
And
// FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
guard let tabBarController = tabBarController as? TabViewController else { return }
slider.value = tabBarController.value
}
#IBAction func didAdjustSlider(_ sender: UISlider) {
guard let tabBarController = tabBarController as? TabViewController else { return }
tabBarController.value = sender.value
}
}
And
// SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var value: Float = 0 { didSet { updateLabel() } }
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
updateLabel()
}
func updateLabel() {
label?.text = formatter.string(for: value)
}
}
Probably needless to say, I not only set the base view controller class for the two tab’s view controllers, but also set the base class for the tab bar controller’s storyboard scene to the above TabBarController.

Swift 3 - Delegate always returns nil value

UPDATED:
I have designed custom tabBar using buttons. I have 3 tabs,
First tab has Messages icon, Second has Profile icon and Third has Photos icon. For third tab button, I have used uiCollectionView() where I need to set images.
For the Third tab's ViewController,there is one condition that I need to check, before changing the title of the first tab button. If messages JSON array is not empty then set "new message" title on the first tab button, else the Messages icon won't change.
There is one ParentTabViewController which has these 3 tabs, I have used uiView, where I change the content according to the tab buttons pressed. I tried to access the values of 3rd tab in ParentTabViewController by using delegate, but the delegate is always nil. I did like this:
class ParentTabViewController: UIViewController,MessageDelegateProtocol{
#IBOutlet weak var contentView: UIView!
#IBOutlet var tabBarButtons : [UIButton]!
#IBOutlet weak var firstTabButton: UIButton!
var MessageVC : UIViewController!
var ProfileVC : UIViewController!
var PhotosVC : UIViewController!
var viewControllers : [UIViewController]!
var message : String!
var selectedIndex:Int = 0
var photoVC = PhotosVC()
override func viewDidLoad() {
super.viewDidLoad()
photoVC.newMessageDelegate = self
let storyBoard = UIStoryboard(name:"Main", bundle:nil)
MessageVC = storyBoard.instantiateViewController(withIdentifier: "messagevc")
ProfileVC = storyBoard.instantiateViewController(withIdentifier: "profile")
PhotosVC = storyBoard.instantiateViewController(withIdentifier: "photos")
viewControllers = [MessageVC, ProfileVC, PhotosVC]
tabBarButtons[selectedIndex].isSelected = true
didPressTabs(tabBarButtons[selectedIndex])
}
#IBAction func didPressTabs(_ sender: UIButton)
{
let previousIndex = selectedIndex
selectedIndex = sender.tag
tabBarButtons[previousIndex].isSelected = false
let previousVC = viewControllers[previousIndex]
previousVC.willMove(toParentViewController: nil)
previousVC.removeFromParentViewController()
previousVC.view.removeFromSuperview()
sender.isSelected = true
let presentVC = viewControllers[selectedIndex]
addChildViewController(presentVC)
presentVC.view.frame = contentView.bounds
contentView.addSubview(presentVC.view)
presentVC.didMove(toParentViewController: self)
if selectedIndex == 2{ // this is what I thought of doing.Correct me if wrong.
// check the condition
// if messagesArray != nil
// set the first tab title "new message"
}
else{
// do not change the button image
}
}
func sendMessage(message : String)
{
self.message = message
print("message........", self.message, "\n\n")
}
}
Here is the View Controller for 3rd tab:
import UIKit
protocol MessageDelegateProtocol:class {
func sendMessage(message : String)
}
class PhotosVC: UIViewController,UICollectionViewDataSource, UICollectionViewDelegate{
#IBOutlet weak var collectionView: UICollectionView!
var userMessageArray = [UserMessageClass]() // array of model class
var newMessage : String!
weak var newMessageDelegate : MessageDelegateProtocol?
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
loadData() // function to get json reponse
}
// implement collectionView delegate and dataSource methods
func getData(newMsg : UserMessageClass) //func to get values from model class
{
newMessage = newMsg.messageString // here I get the "new message" String
newMessageDelegate?.sendMessage(message: newMessage)
} enter code here
func loadData()
{
// get json response. And pass the payload to UserMessageClass using that class's array
userMessageArray.append(UserMessageClass(dict : jsonData))
var msgData = UserMessageClass(dict: jsonData)
getData(alarm: msgData)
}
}
I tried searching a lot about accessing tab buttons in another VC, but didn't find any nearby approach as such. Also I am not able to figure out why delegate is always nil. Suggestions or Help would be grateful. Many Thanks :)
The problem is the following line.
let firstTab = storyBoard.instantiateViewController(withIdentifier: "parentVC") as! ParentViewController
You are probably expecting it to give you the instance of ParentViewController which you have setup initially. However, it will give you the instance of a newly initiated ParentViewController which is not what you want.
To counter this problem you can either make use of a delegate or completion block defined which will be defined inside your ParentViewController class.
Update:
Try adding PhotosVC.newMessageDelegate = self under the line
PhotosVC = storyBoard.instantiateViewController(withIdentifier: "photos")
Also change var PhotosVC : UIViewController! to var photosVC: PhotosVC!
This should work now.

Why the app crashed when using KVO for observe in page view controller ? and How to make the KVO to be effected?

I'm trying to use KVO to observe the update change when using drag in my page view controller's child content view controller's scrollView, but when the app launch, it crashed says:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7ff003d3f5b0 of class KVOPageVC.ContentViewController was deallocated while key value observers were still registered with it."
Here below is my code and screenshot:
APP SCREENSHOT
CODE
PageViewController.swift
import UIKit
class PageViewController: UIPageViewController {
var pageLabels: Array<String> = ["First Page", "Second Page", "Third Page"]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
self.setViewControllers([contentViewForPage(0)], direction: .Forward, animated: true, completion: nil)
}
func contentViewForPage(index: Int) -> ContentViewController {
let contentVC = storyboard!.instantiateViewControllerWithIdentifier("ContentVC") as! ContentViewController
contentVC.pageIndex = index
contentVC.label = pageLabels[index]
return contentVC
}
}
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == 0 || index == NSNotFound {
return nil
}
index -= 1
return contentViewForPage(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == NSNotFound {
return nil
}
index += 1
if index == self.pageLabels.count {
return nil
}
return contentViewForPage(index)
}
}
ObeserverViewController.swift
it's 'a view controller' embedded in 'content view controller's' 'Container View', when the user drag and release the scroll below, i want the emoji face to be replaced by the text "to be notified!"
import UIKit
class ObeserverViewController: UIViewController {
#IBOutlet weak var notifyLabel: UILabel!// when the user drag the scroll view and release, i hope its value will be changed accordingly.
var contentVC: ContentViewController! //the variable to hold the object
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.contentVC = self.storyboard!.instantiateViewControllerWithIdentifier("ContentVC")
self.contentVC.addObserver(self, forKeyPath: "changingLabel", options: [], context: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "changingLabel" {
notifyLabel.text = "to be notified!"
}
}
deinit {
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
}
ContentViewController.swift
Page view controller's child view controllers, 3 pages in total. It includes a Scroll View and a Container View(embedded ObeserverViewController)
import UIKit
class ContentViewController: UIViewController {
var label: String!
var pageIndex: Int!
dynamic var changingLabel: String = ""
#IBOutlet weak var contentLabel: UILabel!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel.text = label
self.scrollView.delegate = self
self.contentView.backgroundColor = UIColor.greenColor()
}
}
extension ContentViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -50 {
if contentView.backgroundColor == UIColor.greenColor() {
contentView.backgroundColor = UIColor.yellowColor()
self.setValue("hah", forKey: "changingLabel")
} else {
contentView.backgroundColor = UIColor.greenColor()
self.setValue("wow", forKey: "changingLabel")
}
}
}
}
My questions is:
How can i make the emoji label text to be notified to change in one controller when i drag and release the scroll view in another controller?
Thank you very much in advance!
Firstly you are adding observer to one object and removing from another. When you are calling instanciateViewController... it will create you new object of passed view controller identifier. Than you are signing to it's changes via KVO. But in viewWillDissapear you are not getting the same object that was crated in viewWillAppear, but crating new one(it has nothing with that was created in viewWillAppear). Than you are resigning from it's notification, still as it is not the same object that you has created previously and signed to him(with KVO), such resigning won't lead to needed result. What you need to do is to save firstly created object to some variable and than resign this variable where it's needed.
Secondly you need to remove observer not only in viewWillDissapear method but as well in
deinit {
// perform the deinitialization
}
Than you will be sure that if your object is deleted than it as well will be resigned from notifications.
Your error message tells that object that was signed for notifications was marked as deleted, still it was not signed out and it will be receiving notification even if it's deleted(sound not really good as memory that he belongs to may already be used for some other objects, that may lead to undefined behavior of your app).

Load information from previous ViewControllers swift

I think we all have been through problems in Swift. I wasn't able to find the correct answer for my question. I've tried over and over again.
I've represented an image where it explains how our app should works.
Users can select an option per view. So in a last one the Labels will change depending of each choose.
I hope you can give me a hand with that guys. Thanks
class ViewController1: UIViewController {
#IBOutlet weak var colorLabelSelected: UILabel!
#IBOutlet weak var nextOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
nextOutlet.hidden = true
}
#IBAction func greenButton(sender: AnyObject) {
colorLabelSelected.text = "You have selected a green BG color"
nextOutlet.hidden = false
}
#IBAction func blueButton(sender: AnyObject) {
colorLabelSelected.text = "You have selected a blue BG color"
nextOutlet.hidden = false
}
#IBAction func pinkButton(sender: AnyObject) {
colorLabelSelected.text = "You have selected a pink BG color"
nextOutlet.hidden = false
}
}
ViewController 2 and ViewController 3 are like ViewController 1
And I have created a .swift class called Attributes but It's empty for now.
I don't know how to make this work.
When you push your next viewController, override prepareForSegue method, get reference to your destinationViewController and pass to it information you need. This will help pass that data around.
Or create a singleton object, some kind of model, where you write the value user has selected on the first screen, and then on the second screen - you read the data from that singleton object.
Define your model, for example:
class MyData {
var background = UIColor()
var title = ""
var level = ""
}
Use appDelegate. In AppDelegate.swift,
var myData = MyData()
In other screens, for instance first screen, use
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.myData.background = yourColor
Same to others
use prepareForSeguefor your concept
for example
#IBAction func pinkButton(sender: AnyObject) {
colorLabelSelected.text = "You have selected a pink BG color"
nextOutlet.hidden = false
self.performSegueWithIdentifier("thirdController", sender: self)
}
override func prepareForSegue(segue:(UIStoryboardSegue!), sender:AnyObject!)
{
if (segue.identifier == "thirdController")
{
var svc = segue!.destinationViewController as! thirdController
print("i set the loading thingy")
svc.toPass = colorLabelSelected.text
}
}
ThirdViewController
create the global variable like
var toPass:String!
for tutorial reference see this link
you have to create array and create one property of array in second and third class and pass color in secondviewcontroller and in secondviewcontroller add title in array of property and pass it to thirdviewcontroller
you can easily manage using create property.

Resources