How to Pass data between tabBarControllers - ios

I am currently trying to pass data between two UINavigationControllers with a UITableViewController attached to each. I am navigating between these two controllers via a UITabBarController. I have been trying to use vacawama's solution on Changing VC issue in Swift. How to pass data between views in tab bar controller? I am using the following code.
import UIKit
class placeData: Equatable {
var description : String
var selected : Bool
init (description : String, selected : Bool) {
self.description = description
self.selected = selected
}
}
class PlacesTabBarController: UITabBarController {
var placeDataArray = [placeData]()
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.
}
}
This is in the tabBarController custom class. In the other two controllers I declare the array I want to be pass between the view controllers and use the method in the link to populate the UITableViewController
var placeDataArray = [placeData]()
override func viewWillAppear(animated: Bool) {
placeDataArray = (self.tabBarController as PlacesTabBarController).placeDataArray
}
When the controllers load, however, the arrays are empty. In the example in the link, all the rest of the code is within the viewWillAppear function, where I need my array to be available to all of the corresponding tableView functions. I am not sure if I am just equating the arrays to zero on load. But my thought was that they would repopulate. Not sure what the correct way to go about this is. Any help would be great.
EDIT: My current structure is as follows:
UITabBarController
| |
UINavigation UINavigation
Controller Controller
| |
UITableView UITableView
Controller Controller
The array is populated when the first tab loads. What I am trying to do is have the populated array in the second view controller, and if I edit it in the second view controller, I want the edits to stay in the first. So I want it to be passed by reference.

I would subclass the UITabBarController and make it a delegate for the two UITableViewControllers.
CustomTabBarController
protocol CustomTabBarDelegate {
var places:Array<placeData> { get set }
}
class CustomTabBarController: UITabBarController, CustomTabBarDelegate {
var places = Array<placeData>()
override func viewDidLoad() {
places = [PlaceData(),PlaceData()]
var table1 = CustomTableViewController()
var table2 = CustomTableViewController()
table1.delegate = self
table2.delegate = self
var navController1 = UINavigationController(rootViewController: table1)
var navController2 = UINavigationController(rootViewController: table2)
self.viewControllers = [navController1, navController2]
}
}
Then your TableViewControllers simply access the delegates array like so.
CustomTableViewController
class CustomTableViewController: UITableViewController {
var delegate:CustomTabBarDelegate!
override func viewDidAppear() {
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return delegate.places.count
}
}
Any changes to the array will be visible in each table after you .reloadData() - I have set the CustomTableViewController in my example to reload data every time the view appears, so when you change tabs they should refresh to show the latest changes.
It's worth mentioning that in time it would probably be cleaner to have a separate class that manages your data instead of holding the array in the TabBarController.

You can always use the app delegate..
Set a property there and call it from anywhere in your application.
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var yourNeededData = appDel.yourPassingAroundDataProperty

Related

Is there a way to pass data back to a view controller when swiping down to dismiss another view controller?

I have a view controller, lets call it vc1, which passes some data to another (vc2) using prepare for segue, and then calling performSegue.
Is there a way to pass some data back from vc2 to vc1 when vc2 is dismissed by swiping down?
Thanks,
Edit --
Apologies for the lack of information, very new to swift so unsure of the correct question to ask in this situation.
To elaborate, the root of the issue at the moment is that vc2 is not dismissed programatically. ie there is currently no function called, it is simply dismissed by the user swiping down.
Is there some function that I can include to capture this dismissal, and use it to send data back to vc1?
I would prefer not to add any buttons to vc2 if possible.
Apologies again, and I appreciate all the help given already!
Try This
class VCOne: UIViewController {
//Create a shared instance of VCOne
static var sharedInstance:VCOne?
//Let the data to be passed back to VCOne is of type string
var dataToBePassedBack:String?
override func viewDidLoad() {
super.viewDidLoad()
//set the sharedInstance to self
VCOne.sharedInstance = self
}
}
Class VCTwo:UIViewController{
//function in which you are dismissing your current VC you can use the shared
instance to pass the data back
func dismissVC(){
//before dismissing the VCTwo you can set the value for VCOne
VCOne.sharedInstance?.dataToBePassedBack = "data"
}
}
Using Protocol And Delegate You Do or Other Option is NSotificationcenter.
One way yo do it is to create another file that it the controller of everything and then have a delegate that always notifies the view controllers when new changes are available. I will walk it through.
protocol HeadControllerDelegate {
// Create a function that sends out the data to the delegates when it is called
// You can use your custom struct here to pass more data easly
func didReciveNewData(myData: String?)
}
struct HeadController {
// Create a shared instance so that the viewcontroller that conforms to the view as well as when we sends out the data the delegate is correct
static var shared = HeadController()
// Creates the delegate, every view can asign it to
public var delegate: HeadControllerDelegate?
// Add all your values here you want to pass back
var myValue: String? {
// The didSet gets called every time this value is set, and then is it time to call the delegate method
didSet {
// Calls the delegates didReciveMethod to notify the delegates that new data exsists
delegate?.didReciveNewData(myData: myValue)
}
}
}
Now in your viewcontroller class where you would like the data to be avaiable (as you said when you swipe down)
class ViewController: UIViewController {
// Here you create a property of the shared instance
let headController = HeadController.shared
override func viewDidLoad() {
super.viewDidLoad()
// Set yourself as the delegate for the headController delegate to recive data
headController.delegate = self
}
}
extension ViewController: HeadControllerDelegate {
// here will the data be recived
func didReciveNewData(myData: String?) {
// handle the data here, you have now got newData
print(myData)
}
}
In the class where you want to pass data you just do it like this. The beauty of this is that you can have multiple classes or structs that writes to the head controllers data (just make sure you do it thought the shared instance). It is also a good pracice according to we to use the delegate pattern.
class Sender {
var headController = HeadController.shared
func sendData(data: String) {
// Here you change the data of the headcontroller wich will send the data to all the delegates
headController.myValue = data
}
}
Hope this answer helps. If you have any questions please let me know.
UPDATE -- EASIER SOLUTION
Here is an easier solution but is less scalable as the previous one according to me.
In prepareForSegue simply pass over your current viewContorller as a field in the destination view controller. Then when viewDidDissapear in the new view controller you can simply pass back the data. Not to worry, I will show you!
In prepare for Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dc = segue.destination as? SecondViewController {
dc.viewController = self
}
}
And declare the secondViewContorller as following. The ViewDidDisappear method will be called when the view has dismissed, and therefore can you pass over the data to the view controller you have set before using the prepare for segue method.
class SecondViewController: UIViewController {
var viewController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidDisappear(_ animated: Bool) {
(viewController as? ViewController)?.value = 2
}
}
Then you could update the UI using a didSet, which simply will be called when the property is set, which will be done in the view did disappear method.
var value: Int = 0 {
didSet {
print(value)
text?.text = "\(value)"
}
}
Hope this helps!

How to pass data when the back button clicked?

I'm trying to pass data from UITableview to another UITableview, I succeed to pass data when I press on cell but I couldn't pass data back when I press on back button of navigation bar; I've tried global variables to solve this problem but since I'm storing date of those variables in database it causes a lot of problems to me, because it enforces all stored reminders to save the same data, I could to do some tricks to solve this problem but we all know that global variable is bad practice and does't confirm to OOP neither MVC, the question is how to pass data back with using Prepare for segue function; note that the table view are static not dynamic.
and if your were asking what data I'm trying to pass is days of week, when Friday and Saturday are selected for example it sends back true values to previous view and if Friday and Saturday are deselected false values are sent back to previous view
so how can I solve this problem?
thanks in advance
You need to do four things:
First
You should have a custom protocol such as:
public protocol DataBackDelegate: class {
func savePreferences (preferencesRestaurantsArray : [Bool], preferencesCusinesArray : [Bool])
}
as you see, I supposed that you want to send back two arrays, but you can edit that in order to send whatever data and datatypes you want.
Second
Your first view controller (or table view controller since table view controller is just a subclass of view controller) has to conform to your custom protocol, such as:
class MainTableViewController: UITableViewController, DataBackDelegate
Third
In your second view controller, you need to have a variable for that protocol, such as:
weak var delegate: DataBackDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.savePreferences(Preferences2ViewController.preferencesRestaurantsArray, preferencesCusinesArray: Preferences2ViewController.preferencesCusinesArray)
Fourth
In your first main controller, in the segue that fires the second view controller, you should set the deleage to self, such as:
destination.dataBackDelegate = self
where destination is the second view controller
I don't have implement the tableviews, I just show you the concept.
VIEWCONTROLLER A (first UITableView) :
import UIKit
class vc1: UIViewController {
//Just to store days and the selected state (at start they should be unselected I presume)
var selectedDays : [String : Bool] = [
"Monday":false,
"Tuesday":false,
"Wednesday":false,
"Thursday":false,
"Friday":false,
"Saturday":false,
"Sunday": false
]
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//set this current view controller to the other UIViewController which will be pushed (vc2)
//You will need it later when pass back data
let secondViewController = segue.destinationViewController as! vc2
secondViewController.previousVC = self
}
}
VIEWCONTROLLER B (your 2nd UITableView) :
class vc2: UIViewController {
var previousVC : ViewController
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
//Suppose that I selected the day of monday and the day thursday
//get the datasource from the previousVC UIViewController and set the correct selected days
previousVC.selectedDays["monday"] = true
previousVC.selectedDays["thursday"] = true
//Here your previousVC selectedDays dictionary variable contains now the new values
//So you can do all you want with it when you will come back on your previous viewcontroller
/*
var selectedDays : [String : Bool] = [
"Monday":true,
"Tuesday":false,
"Wednesday":false,
"Thursday":true,
"Friday":false,
"Saturday":false,
"Sunday": false
]
*/
}
}
Is this what you wanted ?

Issues with StoryBoard ViewControllers in a Tabbed IOS App

In the struggle of developing a Tabbed IOS App with Swift 1.2 and Xcode 6.3 based on MVC, I'm using the visual Storyboard elements instead of to do it programatically because I'm not an experienced developer. In the image attached below you can see the Architecture in the StoryBoard of the App:
The App consists in:
One TabBarController with four TabBar Items.
Each Item has its own ViewController in Storyboard.
All of them are linked with the relationship seque(ViewControllers) in StoryBoard.
Each ViewController in the StoryBoard has its own Class.
The last Item has an embedded NavigationController because I'm using a PageMenu project https://github.com/uacaps/PageMenu to include a paging menu controller with a two child ViewControllers
The Issues I'm having until this point are:
The two child ViewControllers are not linked with the Last TabBar Item in the StoryBoard,as you can see in the figure above, only are instantiated in the parent ViewController Class(PageMenuViewController1), normally this PageMenu works but sometimes the last TabBar Item dissapears, I'm very confused with this issue.
The override func viewWillAppear into the default child ViewController is called twice at the first time, I've include a println("ClubsController viewWillAppear").
The code of the ViewControllers is
import UIKit
class ClubsViewController: UIViewController, UITableViewDataSource{
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var clubs: [Club]!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("ClubsController viewWillAppear")
apiClient.clubsService.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview?.reloadData()
}
else {
println("error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clubs?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("clubObjectCell") as! ClubTableViewCell
cell.clubObject = self.clubs?[indexPath.row]
return cell
}
}
The code of the PageMenuViewController is
import UIKit
class PageMenuViewController1: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidAppear(animated: Bool) {
println("PageMenuViewController1 viewWillAppear")
super.viewDidAppear(animated)
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller1 = storyboard!.instantiateViewControllerWithIdentifier("ClubsViewController")! as! ClubsViewController
controller1.title = "CLUBS"
controllerArray.append(controller1)
var controller2 = storyboard!.instantiateViewControllerWithIdentifier("PartiesViewController")! as! PartiesViewController
controller2.title = "PARTIES"
controllerArray.append(controller2)
// Customize page menu to your liking (optional) or use default settings by sending nil for 'options' in the init
// Example:
var parameters: [CAPSPageMenuOption] = [
.MenuItemSeparatorWidth(4.3),
.UseMenuLikeSegmentedControl(true),
.MenuItemSeparatorPercentageHeight(0.1)
]
// Initialize page menu with controller array, frame, and optional parameters
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}
}
Appreciate help to accomplish the best practices until this point.
I've not familiar with the CAPSPageMenu but there is nothing wrong with having scenes in a storyboard that aren't connected with a segue - this is just a convenience to help with transitions, and instantiating them with instantiateViewControllerWithIdentifier is totally legitimate.
Something that does stand out looking at your storyboard is the way your table view controller with the navigation view controller is wired up.
The navigation viewcontroller should have the relationship with the tab bar controller - not the table viewcontroller.
Here's a screenshot of how the connection should look. Possibly this is why you're sometimes loosing a tab.

passing data from one tab controller to another in swift

My application has three viewController associated with three tab of tabBar. I want to switch from 1st tab to 3rd tab and pass some data from 1st view controller to 3rd view controller. I can't do it with segue, because segue create navigation within the selected tab. That is not my requirement. I want to switch tab in tabBar and pass some data without any navigation. How can i do it ?
Swift 3 in latest Xcode version 8.3.3
First you can set a tab bar delegate UITabBarControllerDelegate in FirstViewController. You can use this when you want to send data from one view controller to another by clicking the tab bar button in UITabBarViewController.
class FirstViewController: UIViewController ,UITabBarControllerDelegate {
let arrayName = ["First", "Second", "Third"] 
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController.isKind(of: FirstsubsecoundViewController.self as AnyClass) {
let viewController = tabBarController.viewControllers?[1] as! SecondViewController
viewController.arrayData = self.arrayName
} 
return true
}
}
Get data in SecondViewController:
class SecondViewController: UIViewController {
var arrayData: [String] = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
print(arrayData) // Output: ["First", "Second", "Third"]
}
}
you can use this code : Objective C
[tab setSelectedIndex:2];
save your array in NSUserDefaults like this:
[[NSUserDefaults standardUserDefaults]setObject:yourArray forKey:#"YourKey"];
and get data from another view using NSUserDefaults like this :
NSMutableArray *array=[[NSUserDefaults standardUserDefaults]objectForKey:#"YourKey"];
swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toTabController" {
var tabBarC : UITabBarController = segue.destinationViewController as UITabBarController
var desView: CaseViewController = tabBarC.viewControllers?.first as CaseViewController
var caseIndex = overviewTableView!.indexPathForSelectedRow()!.row
var selectedCase = self.cases[caseIndex]
desView.caseitem = selectedCase
}
}
Okay, i tried with creating a singleton object in viewController of first tab and then get that object's value from viewController of third tabBar. It works only for once when third tab's view controller instantiates for the 1st time. I never got that singleton object's value in third tab's view controller except the first time. What can i do now ? In my code - In first tab's controller, if i click a button tab will be switched to third tab. Below is my code portion -
In First tab's controller -
#IBAction func sendBtnListener(sender: AnyObject) {
Singleton.sharedInstance.brandName = self.searchDisplayController!.searchBar.text
self.tabBarController!.selectedIndex = 2
}
In Third tab's Controller -
override func viewDidLoad() {
super.viewDidLoad()
//Nothing is printed out for this portion of code except the first time
if !Singleton.sharedInstance.brandName.isEmpty{
println(Singleton.sharedInstance.brandName)
}else{
println("Empty")
}
}
In Singleton Object's class -
class Singleton {
var name : String = ""
class var sharedInstance : Singleton {
struct Static {
static let instance : Singleton = Singleton()
}
return Static.instance
}
var brandName : String {
get{
return self.name
}
set {
self.name = newValue
}
}
}
Edited :
Okay at last it's working. For others who just want like me, please replace all the code from viewDidLoad() to viewWillAppear() method in third tab's (destination tab) controller and it will work. Thanks.

trying to understand protocols and delegates in swift

I'm trying to wrap my head around protocols and delegates, but seems to be having some issues. I have 2 ViewControllers that I'm trying to pass data from. ViewController A has a text field that I want to be optionally populated from ViewController B. So there is a button on ViewController A that segues you over to ViewController B This is how I have B set up.
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class B-Controller: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
..etc
}
I'm using this code in my viewControllers B class to dismiss the currentView when a cell is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("did select")
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName? = "New Name"
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It's not allowing me to set the players Name property inside the protocol here. It keeps returning me Nil when I check it from ViewController A.
View Controller A looks like this:
class A-ViewController: UIViewController, UITextFieldDelegate, AddPlayersViewControllerDelegate{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
//then I'm just trying to print out the new name the was set in ViewController B
override func viewWillAppear(animated: Bool) {
println("this is the selected players name \(addPlayerDelegate?.playersName)") - returns nil
}
I'm sure I'm not fully understanding something, but I feel that I just keep reading and trying out examples only to end back up here where I started from.
//************************* UPDATE *************************//
I'm going to try and simplify my set up. I have 2 View Controllers, VC-A, and VC-B.
VC-A has a text field and a button. VC-B has a tableview. I want the option to have the textField to be populated from the cell.text from CB-B, but only if the user taps the button to view VC-B. So the first time that VC-A loads, it should being back nil from my playersName string from the protocol, because VC-B has never been called as of yet. But once the user taps the button inside VC-A to view VB-B and then selected a cell, which would dismiss VC-B and populate the playersName string inside the protocol on the VC-B class, then I'm using the viewWillAppear method to check to see if playersName has been set and if so use it. Here is my updated code from the help you have given me.
VC-A
class FirstViewController: AddPlayersViewControllerDelegate{
var playersName:String?
let svc = LookUpViewController()
override func viewDidLoad() {
super.viewDidLoad()
svc.addPlayerDelegate = self
}
}
VC-B
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class LookUpViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate: AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName = "Ziggy"
println("the name to be pass is \(addPlayerDelegate?.playersName)")
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It seems that I'm still getting nil even when I got back to VC-A from VC-B. All I want is to be able to get some data (string) from VC-B and use it in VC-A, but only after the user uses the VC-B class. Does that make sense?
You have shown that in the BController you have a property addPlayerDelegate:
var addPlayerDelegate:AddPlayersViewControllerDelegate?
And you have shown that in the BController you talk to that property:
addPlayerDelegate?.playersName? = "New Name"
But you have not shown that at any point in the lifetime of this controller, its addPlayerDelegate property is ever set to anything. For example I would want to see code like this:
someBController.addPlayerDelegate = someAController
If that doesn't happen, then that property remains at its initial value of nil.
Another problem with your code is that this line makes no sense:
class A-ViewController : // {
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
The AController doesn't need this property. They don't both need delegates! What the AController needs is a playersName property. Without it, it doesn't conform to the AddPlayersViewControllerDelegate protocol. In fact, I'm surprised that without that property your code even compiles. Are you sure you are reporting it correctly?

Resources