How to display item from array based on didSelectRowatIndexPath position? - swift

I am coding an app which has a UITableView. I currently have a segue set up set up for the cells in the table as such:
var selectedRow = Int() //global variable so it can be used in both VCs
import UIKit
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "rowSelected", sender: Any?.self)
selectedRow = indexPath.row
}
}
The segue works fine. However, in the swift file that controls the viewController (only being used to change the text of a label) does not work appropriately. Here is the code from that VC:
override func viewDidLoad() {
super.viewDidLoad()
firstLabel.text = infoArray[selectedRow]
print(selectedRow)
}
The infoArray is set up correctly, but the label is not always change to the correct text... printing selectedRow returns inconsistent numbers... if I hit the first cell it will return 0 sometimes, but it also returns 1, 3, 2, etc... It seems random and isn't correctly returning the current int (and therefore the label text isn't set correctly). Why is this?

What you're doing is not the way to pass information from one view controller to another. To pass information, pass the information. Instead of dropping the information in a global, implement prepare(for:sender:), where you can get the segue's destination view controller as it prepares, and set a property of the destination view controller.

Related

How to store tableview cell selected index data into Userdefault and use it into previous page tableview cell using Swift?

My scenario, I am having two viewcontroller VC1 and VC2. Inside VC1 I am having one tableview with two cell for From and To translation language UI.
Here From cell or To cell click to showing common VC2 language list in a tableview. Based on language selection I need to store the selection data to VC1 and need to assign VC1 tableview cell label (immediate updating).
Below code I am using, need to simplified also how to use stored values in VC1 within tableview cell.
Here, My Code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if language_segment.selectedSegmentIndex == 0 { //from
selectedFromLanguage = filteredLanguages[indexPath.row]
fromLanguageID = selectedFromLanguage?.icon
fromLanguageName = selectedFromLanguage?.languageName
fromLanguageCode = selectedFromLanguage?.languageCode
} else { //to
selectedToLanguage = filteredLanguages[indexPath.row]
toLanguageName = selectedToLanguage?.icon
toLanguageName = selectedToLanguage?.languageName
toLanguageCode = selectedToLanguage?.languageCode
}
tableView.reloadData()
}
#IBAction func close_Click(_ sender: Any) {
// From Language Responses
UserDefaults.standard.set(fromLanguageID ?? "",, forKey: "icon")
UserDefaults.standard.set(fromLanguageName ?? "", forKey: "fromLanguageName")
}
You should use a delegate model to inform the first VC that the language has changed.
Here's an example. First define a delegate interface, e.g.:
protocol LanguageSelectionDelegate
{
fun languageSelector(didSetFromLanguage: Int)
fun languageSelector(didSetToLanguage: Int)
}
You can either define a single method that passes both (when OK is clicked on the second VC) or use two methods as here, that are called as soon as the user clicks an option (which you use could depend on whether there is a 'Cancel' option which closes the second VC without applying changes).
Make your first VC conform to this protocol, and add an implementation. Within that implementation you may only need to reload the table view data - without the code I don't know. E.g. (only showing the 'from' language change, but 'to' is identical almost):
class FirstVC: UITableViewController, LanguageSelectionDelegate
{
private var fromLanguageId = UserDefaults.standard.integer(forKey: "fromLanguage")
fun languageSelector(didSetFromLanguage languageId: Int)
{
UserDefaults.standard.set(languageId, forKey: "fromLanguage")
fromLanguageId = languageId
tableView.reloadData()
}
}
This assumes that your cellForRowAt implementation will populate the cell according to the languageId. You need to store a delegate reference in the second VC, e.g.:
public val delegate: LanguageSelectionDelegate!
And make sure you initialise it in your first VC prepareFor(segue) function, e.g.:
let vc2 = segue.destination as! SecondVC
vc2.delegate = self
Your didSelectRowAt for the second VC would then become:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath, animated: true)
if language_segment.selectedSegmentIndex == 0
{ //from
selectedFromLanguage = filteredLanguages[indexPath.row]
delegate.languageSelector(didSetFromLanguage: selectedFromLanguage.id)
}
else
{ //to
selectedToLanguage = filteredLanguages[indexPath.row]
delegate.languageSelector(didSetToLanguage: selectedToLanguage.id)
}
}
There's no need to call reloadData() from this function. And note that you only really need to deal in a unique ID for the language (I've assumed an int here), rather than saving all details about it.

data is not transferring at the right time in my prepare for segue function

here is my code.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "toMonthVC") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! monthCellViewController
// your new view controller should have property that will store passed value
viewController.dataFromHoursVC = valueToPass
}
}
So basically I am trying to pass a value from one VC to another. the didSelectRow is working perfectly how expected. However, the prepare function is running late. For example, the first time the code is run, the second vc sees the passed value as nil. But when i go back and then do it again, it says the passed value, but the value is the one that was done before. So simply put it is acting like the prepare function is behind or being called late.
How did you set up your segue? It sounds like the value isn't being set before the segue is performed.
Try doing it this way:
After you give your segue an identification name set up your code like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
// invoke the segue manually after value is set
self.performSegue(withIdentifier: "SEGUEID", sender: self)
}
Quick Warning
You really should be careful using force unwraps optionalVar! and force downcasts thing = otherThing as! Type. It's far better to always use if-let and guard-let statements or fail-able down casts as?. Using these will decrease the chances of developing a hard to find nil value bug.
This could happen if you set your tableview's data source and/or delegate to you view controller in interface builder. Depending on the rest of your code, initialization of the view controller may need to access the tableview's delegate or dataSource during initialization (i.e. before the controller is passed to the prepareForSegue function). When it is set in IB, the delegate (or dataSource) allow the functions to be called.

Navigate to a new view controller for every cell in table view

I am a beginner coder in swift. I am trying to create a tabbed application. For one of my tabs, I am creating a table view which has multiple rows each which have a different task (A good way to think of this is the facebook app, where each option in the more screen will take you to a separate view)
Now, my table is populated with an array:
let array = ["one", "two", "three]
I want to ask, that everytime that I tap on one of these rows, I would like to go a new view controller. How is this possible?
What I tried was performSegue with an identifier which I give in the storyboard, but then there would be an x amount of segues connecting to the table view? So I don't think this is right? :/
I know the contents of the array prior to generating the table, so If I know the array value, and the row being tapped, how can I navigate to a new view controller?
Edit:
When performing the segue between the controllers, I am using:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showView", sender: self)
}
This of course will only connect to the segue showView however, how can i add multiple view controllers?
You need to simply compare which row is selected in tableView and then perform segue according to it.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let segueIdentifier: String
switch indexPath.row {
case 0: //For "one"
segueIdentifier = "showView1"
case 1: //For "two"
segueIdentifier = "showView2"
default: //For "three"
segueIdentifier = "showView3"
}
self.performSegue(withIdentifier: segueIdentifier, sender: self)
}
Add the following function to your controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
navigationController?.pushViewController(NewController(), animated: true)
}
Using Static Cells in a UITableViewController
Summary
An alternative is to use a UITableViewController with static cells. If you know already know the menu then you can just create static cells for each item.
In the storyboard you can connect a segue from each cell to their respective view controllers.
Example

Errors when changing from UITableViewController to UITableView

I'm trying to embed a Table View in a regular View Controller because I want some other Views in the View Controller besides just the Table View. I had a Table View Controller with some methods that I was overriding. I copied the code for the Table View Controller, pasted it into a Table View .swift file, and removed the "override" before each method as the methods weren't inherent to the Table View class. Unfortunately I'm still getting errors in a few lines of the code:
super.viewDidLoad()
"Value of type 'UITableView' has no member 'viewDidLoad'"
tableView.rowHeight = UITableViewAutomaticDimension
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
tableView.estimatedRowHeight = 60.0
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
super.didReceiveMemoryWarning()
"Value of type 'UITableView' has no member 'didReceiveMemoryWarning'"
tableView.insertRows(at: [indexPath], with: .automatic)
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
What do I need to do to those lines of code to make my program run? It fails to build if I leave the code as it is, and if I put two slashes in front of those lines of code, it runs but just creates a black screen. Any help would be greatly appreciated! Thanks!
import UIKit
class TaskListTableView: UITableView {
var tasks:[Task] = taskData
func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60.0
}
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
#IBAction func cancelToLoLFirstTableViewController(_ segue:UIStoryboardSegue) {
}
#IBAction func saveAddTask(_ segue:UIStoryboardSegue) {
if let AddTaskTableViewController = segue.source as? AddTaskTableViewController {
if let task = AddTaskTableViewController.task {
tasks.append(task)
let indexPath = IndexPath(row: tasks.count-1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
as! TaskCell
let task = tasks[indexPath.row] as Task
cell.task = task
return cell
}
}
I think you are confused about what the UITableView and UITableViewController classes do.
A UITableView class is the class that draws the table view component you see on your screen, updates it when necessary and interprets your taps and scrolls into something that makes sense in terms of a table. UITableView extends UIView.
The UITableViewController class is designed to manage a UITableView instance, providing data to display and responding to the actions that it generates. It is an extension of the UIViewController class.
UIView and UIViewController are completely different things and perform completely different functions. You cannot cut and paste code from one to the other because they won't understand it.
So my first recommendation is to hit the books. Read up on what UIView and UIViewController's do to get an understanding of their place in the iOS universe and how they relate to each other. Then look at UITableView and UITableViewController.
Secondly, as to your problem of wanting to have a screen with a table view as well as other components. There are multiple ways to do this and the best solution will vary depending on the complexity of the UI you are trying to build and the data and code behind it.
Once you've got your head around how views and controllers work. I would start by building a simple screen with several simple components on it and a single controller behind it. ie. A class you have written that extends UIViewController.
When your happy with this, you have two choices that I typically see:
You can add a UITableView UI component and set your UITableViewController as it's dataSource <UITableViewDataSource> and delegate <UITableViewDelegate>. Then add in the various methods from these protocols to define the data to display and how your controller will respond to your actions on the table view.
A more complex choice you should look at if #1 gets too messy. This choice can result in cleaner code. Instead of adding the UITableView UI component, you add a UIContainerView UI component. UIContainerViews are designed to link to a seperate controller and view. Effectively this means you have two controllers. One for the general UI components and one for the table view.
This is all going to depend on exactly what you are trying to do. But first you need to do some reading.

Separate UIViewController in same Storyboard

Issue
I have a single page app with a single view controller. On the screen there is a button that slides out/in a (smaller) UIView with a TableView (functions correctly). My goal is to simplify my view controller, hence my idea was to split off the UIView with the TableView into its own view controller. Therefore I've created a second view controller in the Storyboard and created a class HintsViewTableViewController, that contains the TableView datasource and delegate methods.
Main View controller
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var HintsViewTableVC = HintsViewTableViewController()
HintsViewTableViewController
class HintsViewTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var HintsViewTableView: UITableView!
#IBOutlet var hintsLabel: UILabel!
override func viewDidLoad() {
hintsLabel.text = "HINT" <---fatal error: unexpectedly found nil while unwrapping an Optional value
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 4
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return CGFloat (40)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
}
Problem
If the storyboard entry point is the Main View Controller, the compiler gives an error stating that my property hintsLabel! is nil and crashes.
If I move the storyboard entry point directly to the HintsViewTableViewController, then the app runs and shows the correct view on screen.
Question
Apparently, the procedure to initialize a view controller directly (using the storyboard entry point) is different from assigning the view controller to a variable (as I do in the first case). I've searched high and low for init methods, but have come up blank.
Another solution I've tried: making a separate XIB file and linking this to my HintsViewTableViewController, however TableViews in XIB files can't have prototype cells.
What am I missing here, or stated differently: what's the correct procedure to separate a UIView into a separate view controller (in the same Storyboard)?
The problem is that when you instantiate your HintsViewTableViewController like this:
var HintsViewTableVC = HintsViewTableViewController()
you are creating an instance of the class, BUT that class knows nothing about your Storyboard, so all of the #IBOutlets will be nil because they aren't wired to anything.
Instead, you need to ask the Storyboard to create the ViewController:
self.storyboard?.instantiateViewControllerWithIdentifier("hintsController") as! HintsViewTableViewController
where hintsController is the Storyboard ID you have set for that ViewController in the Identity Inspector.
Note: You will need to make this call to the Storyboard in a method (such as viewDidLoad where self will refer to an instance of your ViewController class.
If you want to declare it as a property like you were doing before, making it a lazy property will allow it to be created when first accessed (and self will be available then):
lazy var hintsViewTableVC: HintsViewTableViewController = { self.storyboard?.instantiateViewControllerWithIdentifier("hintsController") as! HintsViewTableViewController }()