2

I have an app that utilizes multiple view controllers. One of the view controllers has a bunch of textFields that requests information from the user. This information is then passed to a .swift file that I made named "Variables.swift." Inside of this swift file, I created a struct named Variables and created all of my variables in it. For some reason, when I assign the data to the variables, the data is erased when I navigate through my other View Controllers.

In other words, look at it like this:

View Controllers: A -> B -> C -> D -> E -> F ... the information is gathered at View Controller "B", the user continues through the other view controllers (doing the delegated tasks) and then the information that they inputed at View Controller "B" is supposed to be displayed at View Controller "F." For some reason, when I get to View Controller F, the variables are completely empty... the data no longer stored in them - just gone.

Any help would be much appreciated... this is very frustrating. I'm passing the information via prepareForSegue.

Example (ignore the comments):

   override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        print("The info segue was executed.")
//        let vc = autoPDFBuilder()

        var variables = Variables()

        if clientTextField.text != nil {
//            vc.client = clientTextField.text!
//            print(vc.client)
            variables.client = clientTextField.text!
        }
        if claimNumberTextField.text != nil {
//            vc.claimNumber = claimNumberTextField.text!
            variables.claimNumber = claimNumberTextField.text!
        }
        if fileNumberTextField.text != nil {
//            vc.fileNumber = fileNumberTextField.text!
            variables.fileNumber = fileNumberTextField.text!
        }
        if lossDateTextField.text != nil {
//            vc.dateOfLoss = lossDateTextField.text!
            variables.dateOfLoss = lossDateTextField.text!
        }
        if insuredTextField.text != nil {
//            vc.insured = insuredTextField.text!
            variables.insured = insuredTextField.text!
        }
        if claimantTextField.text != nil {
//            vc.claimant = claimantTextField.text!
            variables.claimant = claimantTextField.text!
        }
        if picsByTextField.text != nil {
//            vc.picsBy = picsByTextField.text!
            variables.picsBy = picsByTextField.text!
        }
        if dateTakenTextField.text != nil {
//            vc.dateTaken = dateTakenTextField.text!
            variables.dateTaken = dateTakenTextField.text!
        }

    }
Christian W
  • 353
  • 2
  • 12
  • It'll be helpful if you show how the destination view controller uses `variables`. – Caleb Jun 12 '20 at 19:25
  • @Caleb, I'm printing the data in the variables onto a PDF. When I try to access the data in the variables though, they're empty. I know the data is being properly stored in the Variables.swift file from Controller B, because I did a little print() test. For some reason though, after I navigate through all of the other controllers and finally arrive at Controller F, the variables are completely empty. – Christian W Jun 13 '20 at 02:39
  • I wasn't clear above; what I meant was that you should *show the code* that accesses `variables` in the destination view controller. It's pretty clear that there's a problem in your code, but we can't correct what we can't see. So please post the code where you think the destination controller should have the data in question, and show the code that accesses that data. – Caleb Jun 13 '20 at 04:00

2 Answers2

1

Although you are initializing the variable and populating the data into it you don't seem to pass it to the destination view controller here. You'll need to pass it forward like this:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    var variables = Variables()
    // do data population here
    if let controller = segue.destination as? ViewControllerC {
        controller.variables = variables
    }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • I'm trying to pass it all the way to Controller F. The user still has to navigate through all of the other controllers before getting to F. As I told Caleb, I essentially just want Variables.swift to act as a storage unit where I can pass the data to and access it later on from another Controller. I tried changing it to a class, but, assuming I did it right (just change the word to "class" from "struct"), it still didn't work. – Christian W Jun 13 '20 at 02:36
0

variables is a local variable:

var variables = Variables()

so it's going to go out of scope and be forgotten as soon as prepare(for segue:,sender:) returns. As Frankenstein points out, you can pass it along to the destination view controller. You could also set one of B's properties to that value, like:

self.variables = variables

if you want your instance of B to hold onto a copy. Just remember that if Variables is a struct, it'll be passed by value, so if you do this:

self.variables = variables
if let controller = segue.destination as? ViewControllerC {
    controller.variables = variables
}

then changes that controller makes to its variables property won't be reflected in B's variables property. If you want all your view controllers to share the same instance of Variables, then make Variables a class instead of a struct.


Update based on comments

I'm just trying to store the data somewhere for access later on in the app. I tried changing the word "Struct" to "Class", but it still didn't work...

That's the right idea, but I think you're missing some important details.

For starters, make sure you understand the difference between a structure or class and an instance of that structure or class, a.k.a. an object. In your question, you wrote:

Inside of this swift file, I created a struct named Variables and created all of my variables in it.

You need to understand that the structure you created is like a blueprint for an object that stores data. The Variables struct is a description of a thing that can store data, but that thing doesn't exist until you instantiate, or create an instance of, that struct. That's what you did when you wrote:

var variables = Variables()

Now you have one thing that can store data according to the definition of the struct called Variables. We can create three more like this:

var foo = Variables() var bar = Variables() var baz = Variables()

Those are three independent objects that all conform to the same definition, just as you can build three houses all from the same set of plans. We can set the different values for the various properties of foo, bar, and baz.

Next, you need to understand the how long an instance of a struct or class lives. Local variables exist only as long as the scope in which they're declared. Global variables live for the duration of the program.

So, when you wrote:

var variables = Variables()

you were creating a specific instance of Variables, setting a bunch of that instance's properties, and then just letting the object disappear when it went out of scope.

I'm not trying to pass the data to Controller C, I'm trying to pass it to a "storage unit," essentially, so I can access it in Controller F ... I want ANY view controller to be able to access the data, and when they do, it all be the same data.

So again, you need to understand the difference between Swift's struct and class keywords. There's a lot written about this all over, including in the Swift docs, but the short version is: struct creates a value type, whereas class creates a reference type. When you assign an instance of a struct to something or pass it to a function, a copy of that struct is created and assigned or passed, and any changes made to the copy don't affect the original. When you assign an instance of a class, or pass the instance to a function, a reference to that original object gets passed around, and any changes happen to that original object and are visible to anyone who has a reference to that object. That's why I said above that you probably want a class instead of a struct.

So I tried using a global variable, and that works. I was always told in my programming classes that global variables are bad though - don't use them. Is this true, or no?

You need to know more about how to manage data in an app. This is something that people ask about all the time, and you should read Passing Data between View Controllers to get a better understanding.

It's not quite right to say that global variables are bad, it's just that they come with risks that can be hard to understand until you've had to deal firsthand with the problems they create. Global variables, and their often-abused first cousins, singletons, are attractive because they're easy to use: you can access them from anywhere! But they're also dangerous: you can access them from anywhere! The very fact that they're so accessible means that any part of the program can change them at any time, and in no time you start wondering why the heck your data is changing when you don't expect it to.

A point about style: Variables is a terrible name for a structure or class, and variables is probably an even worse name for a thing that is itself a variable. I can see why you chose it, but do yourself a favor and choose almost any other name; it'll help you avoid a lot of confusion when you're trying to talk to someone else about your code.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • I'm just trying to store the data somewhere for access later on in the app. I tried changing the word "Struct" to "Class", but it still didn't work... I'm not trying to pass the data to Controller C, I'm trying to pass it to a "storage unit," essentially, so I can access it in Controller F. – Christian W Jun 13 '20 at 02:30
  • I re-read your comment and see what you're talking about. The answer is, yes, I want ANY view controller to be able to access the data, and when they do, it all be the same data. The best way I can think to do this is by having a "storage unit" where the data is sent and can be accessed from anywhere at any time. In this specific instance, I want to store the data until the user reaches Controller F, where it is then printed to a PDF. I tried changing it to a class, but it didn't work. Would you mind showing me an example of what you're talking about, just in case I did it wrong? – Christian W Jun 13 '20 at 02:47
  • Update: So I tried using a global variable, and that works. I was always told in my programming classes that global variables are bad though - don't use them. Is this true, or no? If it is true, then we're back to square one... trying to create a working storage unit. – Christian W Jun 13 '20 at 03:09