Struct Array in secondVC not being appended from the firstVC - ios

I have created a struct and made an array of that type. The struct consists of two variable:
struct notesarray
{
var prioritycolor : UIColor
var note : String
}
In my secondVC which houses a collectionViewController, I have made an array of type notesarray. I am sending values for prioritycolor and note from firstVC.
I will be setting up CoreData later on, for now I just want this to work in simplest of manners. I am appending data from firstVC to this array like so:
#objc func handleCheckButton()
{
print("Added")
let secondVC = AddedNotesCollectionViewController()
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
print(secondVC.allnotes.count)
taskTextView.text = nil
}
allnotes is the name of the array found in secondVC.
For testing purposes I am printing secondVC.allnotes.count but I am just getting '1' in console no matter how many time I add elements to the array.
I have also tested this by placing print(allnotes.count) under viewDidAppear func in secondVC so that whenever I go to secondVC it gives me count of the elements in the array but it also shows '0' every time.
I don't know what I am doing wrong here. Please help me!

Thats because you end up getting a new instance of AddedNotesCollectionViewController every time you press the button.
let secondVC = AddedNotesCollectionViewController()
And new instance is initiated with an empty array and you add one element to it by calling
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
Hence count is always one. iOS is correct there my friend :)
What you need:
If second VC is already loaded either by pushing a it on to navigation stack of FirstVC or if its presented then get the reference to the presented/pushed VC rather than creating a new one every time. There are many answers in SO which explains how to access the pushed/modally presented VC :)
If you are about to present/push the SecondVC, as you mentioned in the comments you can always make use of prepareForSegue to pass the data.
If in case your AddedNotesCollectionViewController is never presented then rather consider creating singleton instance of notesArray which you will share between multiple VCs.
Hope it helps

Related

Pass array to another View Controller

I have Tab Bar Controller, where I have few view controllers, but I want to pass array of values (workoutNames) to another view in my Tab Bar Controller. I wonder what's best option to do this and I've decided to use way of passing data with property. But when I try to retrieve data I get empty array. I could also use firestore to retrieve data in my Second View Controller, but It lasts too long, so I decided to passing data between views than retrieve data from firestore every time.
First View Controller
class HomeTableViewController: UIViewController
// I need to pass this array to another view
var workoutsName: [String] = []
...
func sendDataToCalendar() {
// IN THIS FUNCTION I RETRIEVE DATA FROM FIRESTORE AND UPDATE ARRAY WITH VALUES
// After all I print this array to check if everything is correct, and my data is here
print("\(workoutsName)")
}
Here is my Second View Controller when I want to use array from First View Controller
class CalendarViewController: UIViewController {
var arrayOfTitles = [String]()
.
.
.
func getArrayFromHome() {
let homeVC = HomeTableViewController()
homeVC.workoutsName = arrayOfTitles
// NOW I PRINT TO CHECK IF DATA EXISTS
print("\(arrayofTitles)"
}
And its empty, so data didn't pass.
Maybe it's not the best way to pass data, but main idea of this is that I will need to use this array in few view controllers. These controllers won't be connected by segue, so I can't use prepareforSegue. It's not one to one relationship (in future I will need this array in few controllers), so I shouldn't use delegate. I don't think that notification will be ok too, so I think that it's best option to pass data by property. But maybe I am wrong, so please correct me.
The reason why it doesn't work is that you instantiate a new HomeTableViewController with empty data.
If this data will be used on lots of place, why not save it locally? user default seems like it fit your needs.
func sendDataToCalendar() {
UserDefaults.standard.set(workoutsName, forKey: "workoutsName")
}
Then you can read it later on
func getWorkoutNameArray() {
let workoutName = UserDefaults.standard.object(forKey: "workoutsName") as? [String]
}
In your getArrayFromHome() function you are not accessing HomeTableViewController instance where you got the data but creating a new instance. That's why the array is empty. You end up with 2 instances of HomeTableViewController, one in use with the correct array and the dummy one created in the function with an empty array.
Would be better if you pass the data in the same place where you have a reference to CalendarViewController.
Let's say that you are creating and presenting CalendarViewController in your HomeTableViewController like:
let calendarViewController = CalendarViewController()
calendarViewController.arrayOfTitles = workoutNames
// Here is the presentation or push of calendarViewController
It will be useful for you to read this SO question

swift; xcode 9.2 - Passing arguments over TabBar & navigation controller

In my storyboard I got:
UIView -> UITabBarController -> UINavigationController -> UITableView
Now I want to pass an object from UIView into UITableview. I do get the object to the TabBarController from the prepare for segue func, but from there I kind of get lost.
How to identify what segue you have on the itemlist from the TabBarController?
Could somebody give some example code for the UITabBar and Navigation controller to pass the data?
Phillip is right.
You can do it as following:
class Model {
static let shared = Model()
var data: String // or anything else
}
in UIView:
Model.shared.data = "some data"
in UITableView
let data = Model.shared.data
//do smth with data...
Anton is suggesting the Singleton pattern. It is important to understand what it is when you decide to use it has both its benefits and potential pitfalls. https://thatthinginswift.com/singletons/ is a place to start reading up.
There are ways to just pass an object from one view to the other and that is useful knowledge to know. Both TabBarVC's and NavigationVC's have their viewControllers property which allows you to access an array of their child vc's. You can use this to pass information to specific child vc's. Depending on your needs this may be more appropriate than creating a singleton.
For example:
let childVC = tabBarVC.viewControllers[0] as! MyCustomVCClass
childVC.inheretedObject = objectIWantToSend
This would pass an object to the vc that ocupies the first tab of a tab bar vc.

How to use an array over multiple view controllers?

I have been playing around with a lot of stuff involving arrays and scrollviews. I have mostly stayed within the confines of view controllers, so usually i'll grab data from firebase, add it to an array, and then send it to the tableview or collectionview. What I'm trying to do now is actually navigate between viewcontrollers (or multiple copies of the same view controller) and applying the array items to each view controller.
For example I want to be able to grab some photos from firebase, put them in an array of url strings or whatever. Then I want to put a photo on the background of a view controller. Then when I push the over button it goes navigates to the next view controller and puts the next photo as the background there, etc.
I understand there are probably multiple ways to do this and I was wondering what is the most efficient way? Do I just put an array in a Global class and access that from all the view controllers? Or do I just keep the array in the first view controller, then as I navigate, keep sending it to the next view controller over and over? Also there will be a LOT of items and objects and arrays here so that's why I'm looking for efficiency. Thanks in advance to anyone able to help with this, and I hope I explained it well enough!
This is a very simple way of adding and retrieving String value from a struct, here you are saving the image url string as a value in a dictionary and it's key is going to be the ViewController name.
struct SavedData {
static private var imagesDictionary: [String: String] = [:]
static func image(for viewController: UIViewController) -> String? {
return imagesDictionary["\(type(of: viewController))"]
}
static func add(image name: String, for viewController: UIViewController) {
self.imagesDictionary["\(type(of: viewController))"] = name
}
}
saving a value is very simple, if you're saving the data in a viewController and you want a specific image to be saved for that viewController you can use self
SavedData.add(image: "img1.png", for: self)
And if you want to save an image for a different viewController, do it like this.
SavedData.add(image: "img2.png", for: SecondViewController())
Retrieving the image is also very simple, you should call this method in the viewController that you want to assign the image to.
let savedImage = SavedData.image(for: self)
print(savedImage!)

Firebase filling array twice -- Swift

I have two String arrays: one that holds order numbers, and one that holds addresses.
I pull data from Firebase in viewDidAppear using a function that contains the following:
self.localOrderNumberArray.removeAll()
self.localAddressArray.removeAll()
self.orderNumbers.removeAll()
self.addresses.removeAll()
self.tableView.reloadData()
if onItsWayCompanyNameStoreNumberCourierNumberRootRef != nil {
let deliveryRef = onItsWayCompanyNameStoreNumberCourierNumberRootRef.childByAppendingPath("deliveries")
deliveryRef.observeEventType(.ChildAdded, withBlock: { snapshot in
self.orderNumbers.removeAll()
self.addresses.removeAll()
print(snapshot.value.objectForKey("orderNumber"))
let orderNumberPulledFromFirebase = snapshot.value.objectForKey("orderNumber") as! String
self.localOrderNumberArray.insert(orderNumberPulledFromFirebase, atIndex: 0)
let addressPulledFromFirebase = snapshot.value.objectForKey("address") as! String
self.localAddressArray.insert(addressPulledFromFirebase, atIndex: 0)
self.orderNumbers = self.localOrderNumberArray
self.addresses = self.localAddressArray
self.tableView.reloadData()
})
}
The function fills a UITableView with the data pulled from Firebase.
Everything works great when I first run the app. I can add data to Firebase through a different function, and the function above will pull the new data into the UITableView just fine.
However, when I segue to a different view controller (another UITableView, in this case), and then come back to the view that holds the function above, the function fills the order number and address arrays twice when I add new data.
If I segue to the other UITableView a second time, and then come back to view that holds the function above, the function fills the order number and address arrays three times when I add new data. And so on and so on.
It's the strangest thing. I can't figure it out, and it's about to drive me over the edge. Please help.
You are calling deliveryRef.observeEventType in viewDidAppear. viewDidAppear will be called each time the ViewController is presented. So when you segue to other ViewController and comes back, viewDidAppear will be called again and deliveryRef.observeEventType is registered again. So effectively there are two listeners doing the same job in your viewController which will add duplicate data to the array.
You have to implement a logic to do observeEventType only once in the ViewController.

Pass data between three viewController, all in navigationController, popToRootView

The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.

Resources