I have a "Country" ViewController with two ContainerViews: Side Menu (TocVC) and Data Screen (CountryDetailsVC) on it. Button that controls Side Menu toggling is located in Navigation bar.
Side Menu has a TableView and upon didSelectRowAt indexPath I would like pass its String value back to Data Screen and assign it to a Label on it.
This is what I've tried:
TocVC:
var selectedItem:String = ""
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedItem = contents[indexPath.row]
let VC = CountryDetailsVC()
VC.labelUpdate(dataFrom: selectedItem)
}
CountryDetailsVC:
#IBOutlet weak var label: UILabel!
...
func labelUpdate(dataFrom: String) {
self.label.text = dataFrom
}
But I'm getting Fatal error: Unexpectedly found nil while unwrapping an Optional value as I understand because TocVC passed data but CountryDetailsVC didn't "refresh" itself and run the function.
What is the right way to do what I'm trying to do?
EDIT: Storyboard screenshot
Storyboard
EDIT1: Updated code.
Country:
let detailsVc = CountryDetailsVC()
TocVC:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedItem = contents[indexPath.row]
Country().detailsVc.labelUpdate(dataFrom: selectedItem)
CountryDetailsVC:
#IBOutlet weak var label: UILabel!
func labelUpdate(dataFrom: String) {
print("dataFrom: \(dataFrom)")
self.label.text = dataFrom
}
The function prints out dataFrom string to the console but crashes on the next line trying to assign dataFrom to label.text.
When I change label: UILabel! to label: UILabel? it doesn't crash but not changing text either.
Maybe it has something to do with this similar situation?
EDIT2: Updated code. Reference to self in segue to TocVC. Still doesn't work.
Country:
let detailsVc = CountryDetailsVC()
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue .destination is TocVC {
let vc = segue.destination as? TocVC
vc?.countryVC = self
}
}
TocVC:
var countryVC: Country?
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedItem = contents[indexPath.row]
Country().detailsVc.labelUpdate(dataFrom: selectedItem)
CountryDetailsVC:
#IBOutlet weak var label: UILabel!
func labelUpdate(dataFrom: String) {
print("dataFrom: \(dataFrom)")
self.label.text = dataFrom
}
Instead of creating a new instance of CountryDetailsVC in tableView(:didSelectRowAt:), you should store a reference to CountryDetailsVC in your parent view controller in a variable, for example in prepare(for:sender:) of the embed segue and then call your function on that variable later on.
Related
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
I'm new to iOS development and need your help.
I'm using UITableView to build a table of tasks. Tasks data is transmitted from Firebase database. When I click the button on task cell, modal popup window (within the same ViewController via animate method) is opened.
I want to access elements of modal popup in order to pass the data of the specific task from database to its modal window.
I have TaskViewCell file with all cell elements outlets, such as previewTitleLabel, previewMotivLabel etc.
TasksListScreen file:
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class TasksListScreen: UIViewController {
var db = Firestore.firestore()
var tasksArray = [Task]() // array of tasks using Task struct
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var centerPopupConstraint: NSLayoutConstraint!
#IBOutlet weak var backgroundButton: UIButton!
// show popup
#IBAction func yesButtonTapped(_ sender: Any) {
centerPopupConstraint.constant = 0 // popup appear
// add slide animation for popup
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
self.backgroundButton.alpha = 0.5
})
}
// done button on popup is clicked
#IBAction func closePopup(_ sender: Any) {
centerPopupConstraint.constant = -450 // popup disappear
// add slide animation for popup
UIView.animate(withDuration: 0.1, animations: {
self.view.layoutIfNeeded()
self.backgroundButton.alpha = 0
})
}
}
extension TasksListScreen: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell") as! TaskViewCell
let task = tasksArray[indexPath.row]
cell.previewTitleLabel.text = task.title
cell.previewMotivLabel.text = task.tip
cell.previewHashtagsLabel.text = task.hashtags
// Here I want to access task's modal window to pass data to its elements:
// the same task title and task description form database
}}
Please help!!
TasksListScreen storyboard with modal popup
Files hierarchy view and TasksListScreen storyboard
App appearance
Sooo, I solved this issue.
In order to pass data of the selected row to new VC or modal window, you need to give an identifier to your segue between table view VC and new VC, and use two functions:
// change VC
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
performSegue(withIdentifier: "showDetails", sender: self)
}
// pass the data to new VC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? HeroViewController {
destination.hero = heroes[(tableView.indexPathForSelectedRow?.row)!]
}
}
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
}
}
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
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