I am interested in being able to conditionally execute code based on what row a user selects. Is there a way to associate an identifier with each row (cell) in cellForRowAt to help distinguish which row is selected to be used in DidSelectRowAt delegate?
Yes. You are correct in using the DidSelectRowAt method. If you have a view controller with the table, the view controller will have to adopt the two standard delegates: UITableViewDataSource, UITableViewDelegate.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
let message:[String] = ["Hello", "World", "How", "Are", "You"]
/* Table with five rows */
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
/*Have a simple table with cells being the words in the message */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = message[indexPath.row]
return cell
}
/*Optional method to determine which row was pressed*/
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
print("I'm row number "+String(indexPath.row))
}
/*set the table's delegate and datasource to the view controller*/
override func viewDidLoad() {
super.viewDidLoad()
self.table.delegate = self
self.table.dataSource = self
}
}
This would output:
I'm row number 1
Recall that indexes start at zero.
Related
sorry to bother programmers again,
I'm writing a simple app, and one of the view controllers has a table.
I'm new to all of this so I found some videos and programmed the table. This is the code above classes (honestly I still don't understand what it does):
extension ViewController3: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension ViewController3: UITableViewDataSource
{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
And this is the class:
class ViewController3: UIViewController {
#IBOutlet var tView: UITableView!
let names = [
"Alex",
"Andrew",
"Mary",
"Henry"]
override func viewDidLoad() {
super.viewDidLoad()
tView.delegate = self
tView.dataSource = self
}
}
It works fine but the task is when the orientation of the phone/simulator is horizontal, to SHOW the table not in one row, but 2x2 (like 4 cells - 2 rows, 2 lines). I don't know where to search it, I found some tips about placing elements in this way but it's not for a table. And I don't know whether its possible or not. Thank you all in advance
I have a tableView displaying [Double]. Very Simple. But I also wanna display the average of the onscreen numbers on every row, and the difference between this number and the average.
Because I need to re-calculated the average every time a new row appears, I'm thinking about accessing tableView.visibleCells in cellForRowAt: indexPath method, and then update the average of this row and every other rows on screen, because the average of onscreen rows should be the same for all the onscreen rows.
But then I got this error message [Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?
While this is loud and clear, I'm wondering what is the correct way or workaround for this?
Code is very simple
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
print(tableView.visibleCells.count) // THIS LINE PRODUCE ERROR
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
}
What I have tried:
I've tried didEndDisplaying and willDisplay, when I added print(tableView.visibleCells.count) to either of them, same error message was given back.
Answer:
You can use UITableView's delegate functions to calculate this.
tableView(_:willDisplay:forRowAt:) is called every time before cell becomes visible, so you can recalculate your average value at this moment. Also, there is tableView(_:didEndDisplaying:forRowAt:) which fires when cell goes off display and also can be used to recalculate.
Documentation:
tableView(_:willDisplay:forRowAt:)
tableView(_:didEndDisplaying:forRowAt:)
UPD:
For calculation use tableView.indexPathsForVisibleItems
Example:
import UIKit
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
}
private func calculate() {
let count = tableView.indexPathsForVisibleRows?.count
let sum = tableView.indexPathsForVisibleRows?
.map { data[$0.row] }
.reduce(0) { $0 + $1 }
if let count = count, let sum = sum {
print(sum / Double(count))
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
}
I'm updating an app of mine, and I have used this method in other areas of the app and it works, but for some reason, it's not working on a tableView.
The tableView is inside of a ViewController (CurrencyViewController)
#IBOutlet weak var tableView: UITableView!
tableView.dataSource = self
extension CurrencyViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currencies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCell")! as! CurrencyTableViewCell
cell.name.text = currencies[indexPath.row].currencyName
cell.name.textColor = Styles.whiteColor()
cell.symbol.text = currencies[indexPath.row].currencyCode
cell.symbol.textColor = Styles.whiteColor()
cell.backgroundColor = Styles.mainColor();
return cell
}
}
The tableView itself works, it's just not updating the height of the row.
Did something change with the update of Swift?
You don't seem to have conformed to the UITableViewDelegate ?
extension CurrencyViewController: UITableViewDataSource, UITableViewDelegate
If dataSource (and delegate) is connected in IB delete
tableView.dataSource = self
You must also adopt UITableViewDelegate in the extension, connecting the delegate is not sufficient
extension CurrencyViewController: UITableViewDataSource, UITableViewDelegate {
Question 1)
I've spent a few hours trying to figure out this problem, from what I've read those are the two functions needed to implement the UITableView but it still gives me the error in the title:
import UIKit
class UITableView: UIViewController, UITableViewDataSource,UITableViewDelegate
{
override func viewDidLoad()
{
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
}
}
Question 2)
The TableView is a part of a tabbed view controller that I am implementing for my app. I want my TableView controller to have entries that once pressed will open another ViewController. How can I implement something like that?
Thank you in advance
You have incorrectly set up your ViewController. Either create a UITableViewController which is just a UITableView, or add a UITableView to a UIViewController (Like you have almost done).The didSelectRowAt method will allow you to segue to a new viewController, but in my examples only if it is set up correctly in a storyboard.
UITableViewController option
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
performSegue(withIdentifier: "SegueIdentifier", sender: self)
}
UIViewController option (The UITableView will need to be added through a Storyboard in this example)
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
return cell
}
// MARK: - Table view delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
performSegue(withIdentifier: "SegueIdentifier", sender: self)
}
Here is an example project to work from
You shouldn't be using UITableView to manage the data source. That should be the job of UITableViewController which manages data to be displayed on UITableView. The View in general should only worry about how to display the data, not manage it. See MVC design pattern by Apple.
Apple has a really nice, and complete Swift App tutorial (See Display the Data) that goes through how to set up UITableViewController with a table view you built with storyboard. You can also download and run the source project at the end of the page. I highly recommend it.
You have to to Implement This Mendatory DataSource Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "", for: indexPath)
return cell
}
Hope it Helps.
I've been working with Swift and iOS for a number of months now. I am familiar with many of the ways things are done but I'm not good enough that I can just write things up without looking. I've appreciated Stack Overflow in the past for providing quick answers to get me back on track with topics I've gotten rusty on (for example, AsyncTask Android example).
iOS's UITableView is in this category for me. I've done them a few times, but I forget what the details are. I couldn't find another question on StackOverflow that just asks for a basic example and I'm looking for something shorter than many of the tutorials that are online (although this one is very good).
I am providing an answer below for my future reference and yours.
The example below is an adaptation and simplification of a longer post from We ❤ Swift. This is what it will look like:
Create a New Project
It can be just the usual Single View Application.
Add the Code
Replace the ViewController.swift code with the following:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Data model: These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
// (optional) include this line if you want to remove the extra empty cell divider lines
// self.tableView.tableFooterView = UIView()
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// set the text from the data model
cell.textLabel?.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Read the in-code comments to see what is happening. The highlights are
The view controller adopts the UITableViewDelegate and UITableViewDataSource protocols.
The numberOfRowsInSection method determines how many rows there will be in the table view.
The cellForRowAtIndexPath method sets up each row.
The didSelectRowAtIndexPath method is called every time a row is tapped.
Add a Table View to the Storyboard
Drag a UITableView onto your View Controller. Use auto layout to pin the four sides.
Hook up the Outlets
Control drag from the Table View in IB to the tableView outlet in the code.
Finished
That's all. You should be able run your app now.
This answer was tested with Xcode 9 and Swift 4
Variations
Row Deletion
You only have to add a single method to the basic project above if you want to enable users to delete rows. See this basic example to learn how.
Row Spacing
If you would like to have spacing between your rows, see this supplemental example.
Custom cells
The default layout for the table view cells may not be what you need. Check out this example to help get you started making your own custom cells.
Dynamic Cell Height
Sometimes you don't want every cell to be the same height. Starting with iOS 8 it is easy to automatically set the height depending on the cell content. See this example for everything you need to get you started.
Further Reading
iOS & Swift Tutorial: UITableViewController
iOS Table View Tutorial Using Swift
For completeness sake, and for those that do not wish to use the Interface Builder, here's a way of creating the same table as in Suragch's answer entirely programatically - albeit with a different size and position.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView = UITableView()
let animals = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
let cellReuseIdentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = CGRectMake(0, 50, 320, 200)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
self.view.addSubview(tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as UITableViewCell!
cell.textLabel?.text = animals[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Make sure you have remembered to import UIKit.
In Swift 4.1 and Xcode 9.4.1
Add UITableViewDataSource, UITableViewDelegate delegated to your class.
Create table view variable and array.
In viewDidLoad create table view.
Call table view delegates
Call table view delegate functions based on your requirement.
import UIKit
// 1
class yourViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
// 2
var yourTableView:UITableView = UITableView()
let myArray = ["row 1", "row 2", "row 3", "row 4"]
override func viewDidLoad() {
super.viewDidLoad()
// 3
yourTableView.frame = CGRect(x: 10, y: 10, width: view.frame.width-20, height: view.frame.height-200)
self.view.addSubview(yourTableView)
// 4
yourTableView.dataSource = self
yourTableView.delegate = self
}
// 5
// MARK - UITableView Delegates
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
}
if self. myArray.count > 0 {
cell?.textLabel!.text = self. myArray[indexPath.row]
}
cell?.textLabel?.numberOfLines = 0
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
If you are using storyboard, no need for Step 3.
But you need to create IBOutlet for your table view before Step 4.
SWIFT 5
If you only want a tableView on your screen then you can implement UITableViewController to your ViewController and do like this to show a simple tableViewController with a label in it.
Swift file
class ToDoListViewController: UITableViewController {
let array = ["GAFDGSG","VSBFFSB","BFBFB"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath)
cell.textLabel?.text = array[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath)
}
}
And in storyboard create a UITableViewController with mentioning the identifier like this
MainStoryboard
Result
Here is the Swift 4 version.
import Foundation
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var tableView: UITableView = UITableView()
let animals = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
let cellReuseIdentifier = "cell"
override func viewDidLoad()
{
super.viewDidLoad()
tableView.frame = CGRect(x: 0, y: 50, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return animals.count
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
cell.textLabel?.text = animals[indexPath.row]
return cell
}
private func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath)
{
print("You tapped cell number \(indexPath.row).")
}
}
// UITableViewCell set Identify "Cell"
// UITableView Name is tableReport
UIViewController,UITableViewDelegate,UITableViewDataSource,UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var tableReport: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableReport.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "Report Name"
return cell;
}
}