Swift: variable gets value assigned in the wrong order - ios

I'm probably missing something basic here but I have two ViewControllers called ListController(starting VC) and ExamplesController and a variable called selectedCell that is declared in ListController then its value is changed in ListController by the tableView didSelectRowAt indexPath function based on the row the user taps on. When a user taps on a cell, ExamplesController will be presented (via segue in IB), but the value of selectedCell doesn't change until I go back to ListController. So the order in which things execute now is:
selectedCell gets initialised with a value of 0
user taps on a cell (let's say index 3)
ExamplesController is presented with title 0
user goes back to ListController
selectedCell gets assigned the value 3
Here's a simplified version of the code.
var selectedCell = 0
class ListController: UIViewController, UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
print("Tapped on \(selectedCell)")
}
}
class ExamplesController: UIViewController{
#IBOutlet weak var chapterTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
chapterTitle.text = "\(chapterTitles[selectedCell])"
}
}
Any ideas what I'm doing wrong?

You need to hook the segue from the vc itself not from the cell and use inside didSelectRowAt
self.performSegue(withIdentifer:"SegueName",sender:indexPath.row)
func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identider == "SegueName" {
let des = segue.destination as! ExampleVC
des.selectedCell = sender as! Int
}
}

Related

Override func tableview runs after

Edit: mentioned at the bottom I was using another stackoverflow question for guidance. Turns out its the same issue that I don't believe ever got solved after the person edited their question. So I copied the exact same issue into my code: How to know which cell was tapped in tableView using Swift
There's probably many things wrong with my code at this point, but the main issue is the very edit at the bottom of the post this author puts on his question. I know that makes this a duplicate then but no one has answered that. All the accepted answers have the same outcome as my issue.
I'm very new to swift and I'm creating a flashcard type app where tapping on a tableview cell for details shows a term and definition. These terms and definitions are stored in two parallel arrays and the index is supposed to be the indexPath.row.
My issue is that int selectedCell which should be the index of the array for whichever cell the user taps always runs the code to display the term and definition before running the code to find the selected cell.
My earlier error before this was that if I made selected cell an optional (var selectedCell: Int?), the program would crash because its nil. To fix that, I made it var selectedCell: Int = 0 and that worked until I realized that no matter what I tap, the first selectedCell will always be 0.
How can I get the selectedCell before the terms and definitions are displayed.
This is the code inside CardViewController, the controller brought up after the user touches a cell for details. There are only two labels (term and definition) so the code is pretty scarce here.
override func viewDidLoad() {
super.viewDidLoad()
// Testing
print("new selectedCell: \(selectedCell)")
// Update labels for term and definition
termLabel.text = "Term: \n" + cards[selectedCell]
definitionLabel.text = "Definition: \n" + details[selectedCell]
}
The code inside CardTableViewController where the very last override func is what gives me the selectedCell. I have checked that the number is correct when tapped, it just runs after cardviewcontroller apparently.
This is the code for CardTableViewController, where it is tableview cells of each term from the flashcard listed.
Not to be confused with CardViewController which is the little detail flashcard screen that pops up
import UIKit
var cards = [String]()
var details = [String]()
var newCard:String = ""
var newDetail:String = ""
var study = [String]()
var selectedCell: Int = 0
class CardTableViewController: UITableViewController {
#IBOutlet var createCardButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cardCell", for: indexPath)
cell.textLabel?.text = cards[indexPath.row]
return cell
}
#IBAction func cancel(segue:UIStoryboardSegue) {
}
#IBAction func create(segue:UIStoryboardSegue) {
let addCard = segue.source as! AddCardViewController
newCard = addCard.term
newDetail = addCard.definition
print("term: \(addCard.term)")
print("definition: \(addCard.definition)")
cards.append(newCard)
details.append(newDetail)
study.append(newCard)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
print("selectedCell: ", selectedCell)
}
}
I know it's out of order because of the print statements I put in. Instead, selectedCell should print before new selectedCell. Notice how new selectedCell will be 0 due to initialization (nil if I didn't initialize it) and then is always lagging one cell touch behind what it should be?
the segues for cancel and create are bar button items on the (details of the flashcard screen). They are unwind segues that I followed some online tutorial on for how to create an text field and unwind.
for background on the addCardViewController and unwind segues, this is the code inside that:
class AddCardViewController: UIViewController {
#IBOutlet weak var cardTerm: UITextField!
#IBOutlet weak var cardDefinition: UITextField!
var term:String = ""
var definition:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "createSegue" {
term = cardTerm.text!
definition = cardDefinition.text!
}
}
Here's the printing results for the cell indexes
new selectedCell: 0
selectedCell: 0
new selectedCell: 0
selectedCell: 1
new selectedCell: 1
selectedCell: 1
Honestly not sure if there's a way to call that function first or if I'm choosing the selectedCell wrong (I got the idea off another post on stack overflow: How to know which cell was tapped in tableView using Swift)
Storyboard for my app. Shows the list of cards tableview controller and the card view controller:
So in my storyboard, I set up two ViewControllers (CardsTableViewController and CardViewController)
The segue in between these two view controllers is called CardSegue and is set up to present modally.
The reuse identifier for the prototype UITableViewCell in CardsTableViewController is CardCell.
This is how the CardsTableViewController looks like:
import UIKit
struct Card {
let term: String
let definition: String
}
class CardsTableViewController: UITableViewController {
var selectedCell: Int = 0
let cards: [Card] = [Card(term: "Привет", definition: "Hello"), Card(term: "Да", definition: "Yes")]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CardCell", for: indexPath)
cell.textLabel?.text = cards[indexPath.row].term
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
performSegue(withIdentifier: "CardSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? CardViewController {
let selectedCard = cards[selectedCell]
vc.card = selectedCard
vc.selectedCell = selectedCell
}
}
}
This is the CardViewController:
import UIKit
class CardViewController: UIViewController {
var card: Card = Card(term: "<Set me>", definition: "<Set me>")
var selectedCell: Int = 0
#IBOutlet weak var termLabel: UILabel!
#IBOutlet weak var definitionLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Testing
print("new selectedCell: \(selectedCell)")
termLabel.text = "Term: \n" + card.term
definitionLabel.text = "Definition: \n" + card.definition
}
}
I created a Card struct which has a term and a definition, both being Strings.
I created an array of two Cards with two Russian words. This is the data we're working with.
In didSelectRowAt indexPath, I have set up the same setting of "selectedCell", which is defined at the top.
Directly after, I call performSegue, which will send the user to the CardViewController, which will display the term and the definition.
The prepare(for segue) method is always called whenever performSegue is called. In the view controller, if you start typing..."prepare(for...." Xcode will probably fill it out for you.
In this method, I get the selected card, and I pass the card to the CardViewController. In this example, I pass selectedCell, but I don't know if it's really necessary, it depends on what you are trying to achieve, I guess.
This is how the two view controllers should be communicating.
Here's some good information about how to pass information from one view controller to the next: See this section: Passing Data Between View Controllers Using Segues

Passing data from a UITableView to another ViewController depending on what row is selected

I have two ViewControllers one which contains a UITextView and the other one contains a UITableView. I would like my app to pass data for the selected row from the SecondViewController which contains the UITableView to the UITextView in the first ViewController depending on what row the user select. I am using the below code in the firstViewController (Just to give you a bit of history what I have is a UITextView inside the firstViewController and the user have the option of either entering a custom value or exert a longpressgesture then a popover Window get displayed containing the UITableView in the secondViewController. What I would like to achieve is when a row is selected from the popoverView which contains the UItableView the popoverView get closed and the value highlighted in the table get displayed in the UITextView in the firstViewController):
class ViewController: UIViewController, UITextViewDelegate, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var indicativeDesignWorkingLifeTextView: UITextView!
var textInsideIndicativeDesignWorkingLifeTextView: String? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
indicativeDesignWorkingLifeTextView.text = textInsideIndicativeDesignWorkingLifeTextView
indicativeDesignWorkingLifeTextView.attributedText = placeholderTextInIndicativeDesignWorkingLifeTextView
}
}
and the below code in the secondViewController:
#objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
func prepare(for segue: UIStoryboardSegue, sender: UITableViewCell?) {
let toFirstViewController = segue.destination as! ViewController
// Pass the selected object to the new view controller.
if let indexPath = self.indicativeDesignWorkingLifeTable.indexPathForSelectedRow {
let selectedRow = years[indexPath.row]
toFirstViewController.textInsideIndicativeDesignWorkingLifeTextView = selectedRow
}
}
}
However, when I run the simulator and select a row from the table nothing happens inside the UITextView in the firstViewController? All what happens is that the firstViewController gets displayed. Any help is much appreciated.
Thanks,
Shadi.
Update your code as:
#objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "<set identifier String>", sender: indexPath)
}
func prepare(for segue: UIStoryboardSegue, sender:Any?) {
let toFirstViewController = segue.destination as! ViewController
// Pass the selected object to the new view controller.
if let indexPath = sender as? IndexPath {
print("indexPath - \(indexPath)")
let selectedRow = years[indexPath.row]
print("selectedRow - \(selectedRow)")
toFirstViewController.textInsideIndicativeDesignWorkingLifeTextView = selectedRow
}
}

Issue when performing a segue on a tableview cell

I'm currently learning Swift and trying to perform a segue when the user taps on one of the tableview cells that the app presents. At the moment, whenever the user performs this action, the next view controller is loaded successfully, but it seems that, for some reason, I cannot access any of its UI elements, as each time that I try to do it, I end up getting this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
The error points to the line in which I try to modify the text of one of the labels that are displayed on the next view controller
This is the didSelectRowAt function:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
self.performSegue(withIdentifier: "segue1", sender: self)
}
and this is the prepareForSegue function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue1" {
let destinationVC = segue.destination as! UserViewController
let selectedRow = tableView.indexPathForSelectedRow!
let selectedCell = tableView.cellForRow(at: selectedRow) as! CustomCell
destinationVC.usernameLabel.text = selectedCell.userName.text //this is where the error is pointing to
destinationVC.bioLabel.text = selectedCell.bio.text
destinationVC.userImage.image = selectedCell.photo.image
}
}
I have no idea about what is causing this problem. My goal is to pass the data from the tapped cell to the next view controller, but this obviously is preventing me from doing so. Does anyone know how I can fix this? Thanks in advance.
Note: I assumed that userName and bio were both UITextFields
Why don't you try something like this?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue1" {
let destination = segue.destination as! UserViewController
// Use of optional binding to make sure an indexPath exists
if let indexPath = tableView.indexPathForSelectedRow {
let cell = tableView.cellForRow(at: IndexPath(row: indexPath.row, section: indexPath.section)) as! CustomCell
// Notice how we are not directly updating the label as before.
destination.usernameText = cell.userName?.text
destination.bioText = cell.bio?.text
}
}
}
Now in UserViewController:
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var bioLabel: UILabel!
// What we will be passing the text to instead.
var usernameText: String?
var bioText: String?
override func viewDidLoad() {
super.viewDidLoad()
// update the labels with the text from the proper cell.
usernameLabel?.text = usernameText
bioLabel?.text = bioText
}
You can just do the same for your image, just different types. This has to do with the outlets not being allocated when used in prepare(for segue:).
i had great issue with the prepare for segue method when trying the same thing with a UICollectionView. The 2 are very similar so you should be able to change collectionview to tableview easily.
this is what i did... using variable selectedPack
in the view controller you want to segue to you need to set the variable
// passed packName from PackViewController
var selectedPack: String!
then in the viewcontroller you are selecting the cell
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle the segue to JourneyViewController with variable "selectedPack"
// not sure why you need to set the storyboard but it works
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//create instance of the viewcontroller
let transportJourneyViewController = storyBoard.instantiateViewController(withIdentifier: "JourneyViewController") as! JourneyViewController
//value to pass - has been defined in journey
transportJourneyViewController.selectedPack = INSERT_YOUR_VALUE_TO_PASS
//present the vc
self.present(transportJourneyViewController, animated:true, completion:nil)
}
JourneyViewController is the storyboardID and ClassName of the viewcontroller you want to go to.set in the interface builder.
You'll also need to have the tableviewdatasource and tableviewdelegate defined at the top level of your view controllers and in the storyboard itself.
class JourneyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

Is it valid to reuse a second view controller with dynamic cells to display elements of a different array in Swift?

I currently have 2 table view controllers. I've added two disclosure indicators on two static cells for marital status and home state (canton). The user clicks on one of both and is taken to another view controller where he makes the appropriate selection.
The code is currently working for marital status. My question is if here I could reuse the second view controller (i.e. the one with the dynamic cells) for the same purpose but utilising a different array (in this case an array with states' names). For me it is clear that I could simply add a new view controller and implement the states' list there. Here is a screenshot of the storyboard:
First View Controller code:
import UIKit
class FirstTableViewController: UITableViewController, DataEnteredDelegate {
#IBOutlet var maritalStatusCell: UITableViewCell!
#IBOutlet var maritalStatusLabel: UILabel!
func userDidEnterInformation(info: String) {
maritalStatusLabel.text = "Marital Status: (\(info))"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "maritalStatusSegue" {
let sendingVC: SecondTableViewController = segue.destination as! SecondTableViewController
sendingVC.delegate = self
}
}
}
Second View Controller code:
import UIKit
protocol DataEnteredDelegate {
func userDidEnterInformation(info: String)
}
class SecondTableViewController: UITableViewController {
let maritalStatusArray: [String] = ["Single", "Married"]
let cantonArray: [String] = ["ZG", "ZH", "BE", "LU", "AG"]
var delegate: DataEnteredDelegate? = nil
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return maritalStatusArray.count
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if delegate != nil {
let information: String? = tableView.cellForRow(at: indexPath)?.textLabel?.text
delegate!.userDidEnterInformation(info: information!)
dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
cell.textLabel?.text = maritalStatusArray[indexPath.row]
return cell
}
}
Does is make sense here to use the second table view controller for the states' list as well ? If yes, how can I implement that ? Thanks.
Yes you can use the Same View controller for displaying the Array of your states' names which I think you have declared in cantonArray, what you need to do is declare a bool variable in Second View Controller (In case if you want to manage only two arrays, if you want to manage more arrays then declare an enum). Then in the segue get from which index that segue is fired, you can get the selected indexPath like this
if let indexPath = tableView.indexPathForSelectedRow{
}
Now check the indexPath.row, if it is 0 then you have selected Marital State so you need to show maritalStatusArray array so make the bool variable true if you get indexpath.row = 1 then make that variable false
Now in Second View Controller add a condition as per the bool variable and show the data from that array like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
if showMaritalArray {
cell.textLabel?.text = maritalStatusArray[indexPath.row]
} else {
cell.textLabel?.text = cantonArray[indexPath.row]
}
return cell
}
This is how you can declare enum
enum SelectedRow {
case MaritalStatus
case States
case ThirdRow
}
var selectedRow = SelectedRow.MaritalStatus

Why is my variable is changing in printLn but not in the tableview Swift

I've spent ages trying to solve this but with no resolve.
I've finally got to a point where I am pulling my data from one one controller and moving it to the destination controller when I unwind segue, however, when the variable is only reloading in the println but not in the tableview.
I'll try to explain this a bit better with my code as it sounds complicated.
I have a label on one controller which when pressed, presents a UISearchController modally. when you select a cell, it dismisses the view with an unwind segue and passes the data from the cell back to the previous controller to change the label of the button.
I set the label.text in a variable at the top of the initial controller like so
var selectedStation = "Search Stations"
here is my shoddy named function which is used to println the variable to see if it works which it does:
func updateStuff() {
println("you selected \(selectedStation)")
tableView.reloadData()
}
and i declare the label text in my cellForRowAtIndexPath like so:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("searchFieldCell", forIndexPath: indexPath) as! searchFieldTableViewCell
cell.backgroundView = UIImageView(image: UIImage(named: "red-full"))
cell.destinationLabel.text = selectedStation
}
then in my UISearchController i have the following to pass that variable back
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
println(stationArray[indexPath.row])
selectedStation = stationArray[indexPath.row]
self.performSegueWithIdentifier("unwindToSet", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.destinationViewController .isKindOfClass(SetAlertController) {
var VC = segue.destinationViewController as! SetAlertController
VC.selectedStation = self.selectedStation
VC.updateStuff()
}
}
essentially my controller retrieves the updated variable but doesn't update it in the tableview, it only updates it in the println.
i set up a quick demo project with the following viewcontrollers:
class MainViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBAction func unwind(segue: UIStoryboardSegue) {
println("unwinding")
if let sourceViewController = segue.sourceViewController as? ModalViewController {
label.text = sourceViewController.selectedText
}
}
}
tapping on the label results in the modalviewcontroller to show. i set this up in storyboard.
class ModalViewController: UITableViewController {
var selectedText: String?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
selectedText = cell.textLabel?.text
performSegueWithIdentifier("unwindToSet", sender: self)
}
}
everything works as expected! feel free to ask if anything is unclear...
you can find the demo project here: https://www.dropbox.com/sh/u2blzmo3ztaaini/AADq8hOMMS71wvBH1eH4Bz_4a?dl=0

Resources