0

I'm trying to change a value of a variable from another Swift file, but for some reason it does not work and it returns nil.

This is what I have tried:

class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let si = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
    si.idSelected = indexPath.row //Here I change the value of the variable
    performSegue(withIdentifier: "ShowIssueDetail", sender: self)
  }
}

ShowIssueDetail.swift:

class ShowIssueDetail: UITableViewController {
  var idSelected: Int! //This is the variable I want to change its value from the another swift file
    override func viewDidLoad() {
      print(idSelected) //Here it prints out nil instead of the selected row
    }
}

I have also tried it in this way, but same issue:

class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let si = ShowIssueDetail()
    si.idSelected = indexPath.row //Here I change the value of the variable
    performSegue(withIdentifier: "ShowIssueDetail", sender: self)
  }
}

What am I doing wrong?

Thank you in advance!

Note: Both swift files are of different type, ShowIssues.swift is UIViewController and ShowIssueDetail is UITableViewController, I do not know if it does not work due to this.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116

4 Answers4

2

If you have a segue set up in Storyboard, you shouldn't be instantiating the destination view controller from code, the Storyboard will create the view controller for you. By initialising another instance, you end up setting values on an instance that won't be presented to the user.

You need to override prepare(for:) and set the value there.

class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "ShowIssueDetail", sender: indexPath.row)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowIssueDetail", let destinationVC = segue.destination as? ShowIssueDetail, let selectedId = sender as? Int {
          destinationVC.idSelected = selectedId
        }
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • Thank you Dávid, but your code is not working in my case. It gives me two errors: "Pattern matching in a condition requires the 'case' keyword" and "Variable binding in a condition requires an initializer". But nevermind, Scriptable's reply has helped me better, I am just letting you know. –  Nov 07 '18 at 12:42
  • @AdriánT95 there was a typo, but fixed it now – Dávid Pásztor Nov 07 '18 at 13:50
1

performSegue will create a new instance of ShowIssueDetail.

Because of that you never set idSelected

Retterdesdialogs
  • 3,180
  • 1
  • 21
  • 42
1

It looks like you're talking about the variable idSelected in your ShowIssueDetail view controller.

The problem is that in both versions of your code you are creating a different instance of the view controller than the one you are segueing to, and setting a value in that throw-away view controller.

You need to use prepareForSegue to pass the variable to the view controller that you're segueing to.

//Add an instance variable to remember which item is selected.
var selected: Int? 

class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    selected = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
    performSegue(withIdentifier: "ShowIssueDetail", sender: self)
  }
}

func prepare(for: segue, sender: Any) {
   if let dest = segue.destination as? ShowIssueDetail {
     dest.idSelected = selected
   }
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    @Sh_Khan, your edit to my answer is another way to pass the selected item, but it isn't a given that it is THE way to do it. My original answer is/was another valid alternative. Please don't unilaterally edit my answers to use your way of solving the problem. Post a comment, or even your own answer to the question, but editing another answer should be reserved for fixing clear errors, not changes in approach. – Duncan C Nov 07 '18 at 14:11
  • 1
    @Sh_Khan Your using force-unwrapping in an your edit to my answer is a very bad idea in a forum post, especially in an answer to a Swift newbie. – Duncan C Nov 07 '18 at 14:20
0

Your approach is wrong here:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let si = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
    si.idSelected = indexPath.row //Here I change the value of the variable
    performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}

You are creating another instance of the VC and setting the value. That VC is not the actual one that will be shown.

You need to use the prepareForSegue method

var selectedRow: someTypeSameAsRow?

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    selectedRow = indexPath.row
    performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "ShowIssueDetail",
        let vc = segue.destination as? ShowIssueDetail {
        vc.idSelected = selectedRow
    }
}

EDIT:

What you may need to do to resolve the error mentioned in the comments is wait until the detail view is loaded properly. The awakeFromNib function should work here:

// this goes in the view controller
override func awakeFromNib() {
    super.awakeFromNib()

    self.detailView.selected = selected
}

So, with this code you are waiting until the view of the VC and it's subviews are fully loaded and then setting the selected property of showDetailView to the same selected property that is on the VC.

Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • Thank you so much! I understand now, I have tried this but it doesn't work due to the second part of the if (where let vc = segue.destination...), and I have figured out why. The segue goes from ShowIssue to another UIViewController that has a ContainerView which it has the UITableViewController (ShowIssueDetail) as embeded. What should I do in this case? –  Nov 07 '18 at 12:17
  • you must have a property or outlet for the ShowIssueDetail view on the VC, so you should be able to do something like: `vc.detailView.idSelected = selectedRow` – Scriptable Nov 07 '18 at 12:39
  • @AdriánT95 I have updated my answer with a suggestion – Scriptable Nov 07 '18 at 12:52
  • Thank you, it worked! However, I have a question. I would like to know if this has been changed in the latest swift versions because, in this thread, in his reply he is changing the value of a variable from another swift file like I was doing it at first: https://stackoverflow.com/questions/32178410/changing-the-value-of-a-variable-from-another-class-in-swift (that is why I was not using the segue thing) –  Nov 08 '18 at 12:39
  • You can use that method if you are initializing and presenting the view controller yourself, otherwise if you do it through storyboard and segues you need to use `prepareForSegue` – Scriptable Nov 08 '18 at 12:42