I have a UITableView set up that works almost correctly, but if you scroll all the way up or down rather quickly, it shuffles the cells out of order. I set the label to reflect the index path and even when it starts out
[0,0] [0,1] [0,2] [0,3] [0,4] [0,5] [0,6]
After a quick few swipes up and down, the cells look like this
[0,1] [0,6] [0,2] [0,3] [0,4] [0,5] [0,0] [0,1]
My code is similar to the following
import UIKit
class TableViewController : UITableViewController {
var dates: [Date]?
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dates?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCell.storyboardID, for: indexPath) as? tableViewCell else { fatalError() }
let date = dates?[indexPath.row]
cell.date = date
cell.delegate = self
cell.cellIndex = indexPath
return cell
}
}
From Apple docs on UITableView
When the table view asks the data source to configure a cell object for display, the data source can access the queued object by sending a dequeueReusableCellWithIdentifier: message to the table view, passing in a reuse identifier. The data source sets the content of the cell and any special properties before returning it. This reuse of cell objects is a performance enhancement because it eliminates the overhead of cell creation.
So in this case, you are storing the value of indexPath in the cell propeties, "cellIndex", which is reusable by other cells. Therefore, it will randomly show the value based on previously assigned in queue.
In order to encounter this, you can either store the value to array variable in viewController, so every time the datasource want to re-create the cells, it will get from the array consistently.
Secondly, you can directly assign the indexPath to the cell ui element such as UILabel.
cell.titleLabel.text = "\(indexPath)"
Related
After much searching and reading I unfortunately do not come from the following. I want to use static tables to display certain data. (Are there better options?)
In my view I first put an onion picture with a container view underneath. The container view again refers to a Table View Controller.
I made an outlet from the cells and then I thought I could easily adjust the text.
Now I want to change the text of the fields in the table, but unfortunately I do not succeed.
When I start the app then the table is completely empty as seen on the screenshot.
What am I doing wrong ?
class TableViewController: UITableViewController {
var data: [String] = ["Muis", "Aap", "Koe", "Vis"]
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 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let iets = data[indexPath.row]
cell.textLabel?.text = iets
return cell
}
}
If you want to use static cells
Forget dequeueing UITableViewCell instances and all tableview data source and delegate methods.
In Interface Builder select the table view and select Static Cells from the Content popup
Drag the amount of static cells you need into the canvas
In the view controller declare IBOutlets and connect them directly to the UI elements in the cells
You need to change your way of thinking for this one. You do not own the cells, the UITableView does. It will provide cells as it seems fit by using your implementations of UITableViewDataSource:
func numberOfSections(in: UITableView) -> Int
func tableView(UITableView, numberOfRowsInSection: Int) -> Int
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
Normally, the texts (your actual data) would be held in a list available to this data source.
Example:
var data: [String] = []
// Other functions
func numberOfSections(in: UITableView) -> Int {
return 1
}
func tableView(UITableView, numberOfRowsInSection: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER")
cell.text = data[indexPath.row]
return cell
}
Now, if you want to change this cell's text, all you have to do is update your data list and reload the data.
What I have done after a lot of testing and reading. I have create a segue to the statutable class.
if (segue.identifier == "myEmbeddedSegue") {
let childViewController = segue.destination as! hondDetialTableViewController
childViewController.hondId = hondData["hondId"]!
}
In this segue I send only the hondId, everything else i ask entities.
I'm sorry but this is not at all how UITableView works. The UITableViewCell that you define in the Xib/Storyboard within the tableview are just "models" or templates, they don't actually exists until you dequeue them.
You can read how UITableView works here: http://www.thomashanning.com/uitableview-tutorial-for-beginners/
You have to return numberOfSections > 0 if you want anything displayed in your tableview; similarly, that section has to also have numberOfRows > 0 otherwise again, nothing will be displayed (ok, maybe headers and footers if those are properly setup).
At any rate, cells are only accessible after you dequeue them. Creating an outlet in a XIB to a UITableViewCell is useless in most cases.
You can explore other options, such as UIStackView, or maybe what you need is just plain custom UIView with labels that you properly set and layout using NSLayoutConstraints. There are plenty of resources out there, this is just one I quickly Googled for you to get started: https://www.youtube.com/watch?v=de0sthle44I
Good Luck.
I am interested in having a tableview for comments (something similar to instagram comments). So far, I have used a custom cell to set up a textView for comments in my set array, dataName. I was wondering how I could go about setting up a textfield and button on the last row of the tableview that would act as the place to input more comments. Do I need to create another customcell for this and implement this in cellForRowAt indexPath ?
var comments = ["I like this item","Where did you get this?", "I can't believe you found this!", "Hello", "Yay"]
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.commentView.text = dataName[indexPath.row]
cell.commentView.textColor = UIColor.lightGray
cell.commentView.isEditable = false
cell.commentView.isScrollEnabled = false
return cell
}
You can accomplish you desired effect by adding a view that contains a text field and a button as the footer view of the tableview. And when a new comment is added you will proceed to add the comment to the array, and reload the tableview or insertRow with animation.
You already have one cell prototype called "Cell". Just add another cell prototype called "Comment". Now you have two cell prototypes with two different identifiers. If you're on the last row, ask for the "Comment" cell prototype in your dequeue call.
I am using Swift 3.
I've been following this tutorial: https://www.youtube.com/watch?v=VWgr_wNtGPM , supplemented by this answer on StackOverflow.
However, the way that this works is that if I click on a cell, it expands while hiding other cells. How do I make it such that when I expand it, the other already-expanded cells stay expanded?
The best approach I suggest you for achieving this in an elegant way is implementing it through UIStackView elements.
Take a look this post http://www.atomicbird.com/blog/uistackview-table-cells
if you wanna do this yourself, you could try this way.
first step is you should create a model list just like:
var cellsData: [CustomData] = [];
the CustomData seem like:
class CustomData {
var isExpanded: Bool = false;
// whatever other variables
}
then your custom cell should whatever look like but you must do something in the tableView:didSelectItemAt like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row;
self.cellsData[row].isExpanded = !self.cellsData[row].isExpanded;
self.tableView.reloadRows(at: [indexPath], with: .none); // or the other animations
}
then in the "tableView:cellForRowAt" seems like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell;
if(cell.isExpanded){
// do something when the cell is expanded
}else{
// do something when the cell is not expanded
}
}
remember, the cell is reusable, means if you have used the cell more than one time, then the cell will keep the state when it was used the last time.
You can use ExpyTableView, which makes an expandable section from your given header cell. Compatible down to iOS 8.0.
All you have to do is to import ExpyTableView and then:
class ViewController: ExpyTableViewDataSource, ExpyTableViewDelegate {
#IBOutlet weak var expandableTableView: ExpyTableView!
// First, set data source and delegate for your table view.
override func viewDidLoad() {
super.viewDidLoad()
expandableTableView.dataSource = self
expandableTableView.delegate = self
}
// Then return your expandable cell instance from expandingCell data source method.
func expandableCell(forSection section: Int, inTableView tableView: ExpyTableView) -> UITableViewCell {
// this cell will be displayed at IndexPath with section: section and row 0
}
}
You can see your former table view section is now an expandable table view section. You can also download the example project and see more detailed examples.
In my conception every row of tableview is kind of task which you can fill in.And every time you open the app one row should be a priori on a tableView. (As you see in picture )
Also I have button which allows you to add a row.
But I have no idea how can I know the number of rows in section.
Can you please help me with it ?(In my code I've commented the moments I can't understand)
class TaskSecondViewController: UIViewController,UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
self.tableView.separatorColor = UIColor.clear
#IBAction func insert_rows(_ sender: Any) {
let indexPath = IndexPath(row: 1, section: 1) // Don't know what to write in "row"
self.tableView.insertRows(at: [indexPath], with: .top)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 // Here also
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TaskTableViewCell
return cell
}
}
have you tried this :
yourTableView.numberOfRows(inSection: 0)
this returns the no of rows in your table view's section
You can simply have
var numOfRow = 0
every time you hit the button
numberOfRow += 1
tableView.reloadData()
so you can return numberOfRow
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numOfRow
}
But I have no idea how can I know the number of rows in section.
The important thing to understand is that a table knows nothing about the items it displays, or how to display them, or what to do when people interact with them. A table is really just a list of sections, where each section is a list of cells. And it can scroll. And it can ask for help.
The items that are displayed are kept elsewhere. They don't even exist as rows in the table all at the same time. The table only keeps as many rows as it needs to display, and when the user scrolls, it asks some other object for more rows to display and throws out the rows that are no longer displayed. That other object is the data source, and it may be the 'elsewhere' where the items are kept, or it may only be an object that knows where to find (or generate) those items.
In this case, your TaskSecondViewController view controller is the table's data source. You need make sure that that controller somehow has access to the data it needs. Maybe it reads the list from a file. Maybe an array of items is passed in from some other object. There are a million variations on the theme, but it's up to you to know what you want to display in the table and to know where those things are kept. Once you know that, you should be able to figure out how many items are in a given section. You're also going to need to know how many sections there are. It could be that you just have one list of items, and you don't plan to break it up into sections; in that case, you'll just return 1 for the number of sections and the number of items in the whole list for the number of rows in that section.
If you just want to add a row, you don't have to insert it into the table view at an explicit spot. It would be easier to have an array (or dictionary or whatever) and add your items into that object, and then reload your table view when items are added.
var itemsToDisplay = [SomeObject]
override func viewDidLoad() {
// populate items if needed
}
#IBAction func insert_rows(_ sender: Any) {
// get your data to create your object
// add your object to itemsToDisplay
itemsToDisplay.append(myObject)
// for a TableViewController
self.tableView.reloadData()
// if you've included the tableView as an #IBOutlet
myTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemsToDisplay.count // this might be zero, but it doesn't matter
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TaskTableViewCell
var item = itemsToDisplay[indexPath.row]
cell.titleLabel.text = item.whatever
return cell
}
I'm doing a tutorial online right now building out a table of cells and I have a question. In the second function tableView below... what exactly is indexPath?
More importantly, how does the program know to update the value being passed through indexPath by one each time the function is called?
var rowNumber = 0
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let rowCell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
rowCell.textLabel?.text = String(indexPath.row + 1)
return rowCell
}
TableView is divided into sections. Each section contains some number of rows. IndexPath contains information about which row in which section the function is asking about. Base on this numbers you are configuring the cell to display the data for given row. Application is executing this function when it needs to display a row. For example you scroll the tableview down and next cell will appear on the screen. To configure this cell with, for example, correct texts, it's asking you what should be displayed on row which is at this position (section and row).
Also, to improve performance you should create the cell using tableView.dequeueReusableCellWithIdentifier function instead of Cell's init.