Populate cellForRowAtIndexPath for grouped tableview - ios

I have a uitableview (NOT a UITableViewController) with 2 sections inside the uitableview. For the other methods I was able to rely on a switch case to figure out what section should be populated with the correct data.
switch(section) {
case 0:
return postitiveArray.count
case 1:
return negativeArray.count
default :return 0
}
But cellForRowAtIndexPath doesn't seem to have that option. So how can I populate one section with postitiveArray and the other section with negativeArray?

The tableView(_:cellForRowAtIndexPath:) method supplies you with an NSIndexPath, which has row and section properties. So you just need to get the section property from it to determine which section you're in.

Related

Clickable UITableView section

I have a sectioned UITableView. Rather than have each row of the section individually selectable, how to I make it so the entire section (rows plus header view) respond the same if there is a tap anywhere in the section. Suggestions?
This kind of behavior can be supported in different ways. The simplest way to make an entire section have the same behavior is as follows:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0:
doBehavior0()
break
case 1:
doBehavior1()
break
// etc...
}
}
Keep in mind that this will not work for headers. You'll have to create a gesture recognizer and call doBehaviorX() from that. More details here. This should be simple enough to implement if you have a fixed number of sections (which I will assume since your question didn't specify).

Does indexpath.row take care of iterating an array?

I'm working through a UITableView tutorial and I've become curious about array iteration as I implement the UITableViewDataSource methods. When we call indexPath.row, is this calling a for loop for us behind the scenes? I'm asking because months back when I was learning to use data from a webservice (I've forgotten the exact steps of how I did it precisely) but I believe I needed to iterate over the array in order to present the information in the console.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an instance of UITableViewCell, with default appearance
let cell = UITableViewCell.init(style: .value1, reuseIdentifier: "UITableViewCell")
// Set the text on the cell with the description of the item instance that is at the nth index of the allItems array, where n = row this cell will appear in on the tableview
let item = itemStore.allItems[indexPath.row]
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = "$\(item.valueInDollars)"
return cell
}
Am I correct in thinking that the indexPath.row method is iterating over the array for us behind the scenes?
let item = itemStore.allItems[indexPath.row]
No, calling indexPath.row does not iterate through all rows for you. It's just a variable sent to cellForRowAt that you can access and use to create your cell. It contains information about which row in which section the function cellForRowAt was called for.
However, cellForRowAt is actually called every time a cell is going to be visible on your tableVIew. Imagine you have a dataSource with 100 items and it is possible to only see 10 cells at a time. When the tableView gets initially loaded, cellForRowAt will get called 10 times. Then, if you scroll your tableView to show 3 more cells, cellForRowAt will get called 3 more times.
First of all the tutorial seems to be pretty bad. Table view cells are supposed to be reused
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
The workflow to display table view cells is:
The framework calls numberOfRowsInSection (and also numberOfSections)
For each section/row pair in the range of the visible cells it calls cellForRowAt and passes the index path in the second parameter.
No. The indexPath is just a struct with a section and a row. You use those to directly look up the information from your array. No iteration is happening.
cellForRowAt is only called for the cells that are on screen, so the order they are called depends on which direction you are scrolling.
In fact, the UITableView doesn't even know if you are using an array, or generating the information on the fly. What you do with the indexPath row and section is up to you.
Check the documentation of UITableView and cellForRowAtIndexpath functions.
An index path locating a row in tableView.
IndexPath will be a structure, which helps you to get the specific location from a nested array. In your case of the table view, rows inside the section.
cellForRow will return a cell for particular index path(in the specified section and row)
So indexPath.section will give the section number for which the cell is going to be modified and returned to the data source. And Inside the section, and indexPath.row will be the corresponding row inside that section.
index path documentation
tableView-CellForRowAt: IndexPath
UITableView DataSource Documentation
[update]
If you want to add multiple sections, add another dataSource numberOfSections, and return the number of sections count from it.
I can link to a create a table view tutorial from Apple, but it's a long document, you can skip the starting and read the To display a section in your table view.
If you want, you can use a 2D array for keeping sections and rows. And in numberOfSections method return array.count, and in numberOfRows:inSection, return array[section].count
More examples,
example
example
thanks,
J

Switch calls multiple cases one after another when checking table view tag

I have a collection view with three different cells. Each of the cells contains a table view. So, there are three table views. I've set tags for each of them (from 1 to 3).
Now, on my view controller (I set it as the table view's data source when I dequeue collection view's cells) I call table view's data source method for the number of rows. To distinguish table views I check each one's tag. Here is the code for that:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch tableView.tag {
case 1:
if unitItems1 != nil {
return unitItems1!.count
} else {
return 0
}
case 2:
if unitItems2 != nil {
return unitItems2!.count
} else {
return 4
}
case 3:
if unitItems3 != nil {
return unitItems3!.count
} else {
return 4
}
default:
return 0
}
}
The problem is, when the first cell of the collection view is shown (with the first table view) it works fine. But when I scroll to the second cell, BOTH case 2 and case 3 get executed. After that, the second table view shows data as expected, but when I scroll to the third one, the method doesn't get called.
I can't figure out why two case statements get called one after another, while everything works fine for the first cell. If you have any ideas why this happens (or maybe, you could suggested a better way of checking table view's), I would appreciate your help.
Actually, the solution is quite simple. The reason of the problem was that collectionView's data source method was dequeueing all the cells one after another, even when they weren't on the screen. Consequently, tableView's inside of each cell were getting set, too. So, their data source method was getting called, hence the problem.
UICollectionView has a property called isPrefetchingEnabled. According to the documentation it denotes whether cells and data prefetching is enabled.
The documentation says:
When true, the collection view requests cells in advance of when they will be displayed, spreading the rendering over multiple layout passes. When false, the cells are requested as they are needed for display, often with multiple cells being requested in the same render loop. Setting this property to false also disables data prefetching. The default value of this property is true.
So, to solve the problem, described in the question, I set it to false as soon as my collectionView gets set.

Swift: Add empty element in table view (Core Data)

I want to add a 'new element' cell to my UITableView so that after text typing and tapping 'Enter' the new entry must be created. Something like here but only in the end of the list. How can I do this? In my UITableViewController I use NSFetchedResultsController that handles operations with Core Data.
You will need to amend the tableView delegate and datasource methods to include the extra row:
Add another prototype cell to your storyboard to represent your "extra" row. Design it accordingly (subclassing if necessary) and assign it a different cell identifier.
Amend numberOfRowsInSection to return the FRC sectionInfo count plus one
Amend cellForRowAtIndexPath to dequeue with the different cell identifier if indexPath.row is equal to the FRC sectionInfo count (ie. the last row). Configure the cell accordingly.
Amend didSelectRowAtIndexPath to trigger the code to add the new entry.
Check/amend all other delegate/datasource methods (eg. editingStyleForRowAtIndexPath) to ensure they check the indexPath.row and handle the extra row appropriately.

Using indexPath vs. indexPath.row in tableView.deselectRowAtIndexPath

I noticed in that to disable the highlight feature of a table cell you would yse the following method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
...
tableView.deselectRowAtIndexPath(indexPath, animated: false)
}
What I don't understand is what's going on in the first argument indexPath. In the instructional book I've been reading just about every other method has been using indexPath.row for selecting individual cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as CustomTableViewCell
cell.nameLabel.text = restaurantNames[indexPath.row]
cell.typeLabel.text = restaurantTypes[indexPath.row]
cell.locationLabel.text = restaurantLocations[indexPath.row]
}
Just out of curiosity I tried passing indexPath.row as an argument but got the error, 'NSNumber' is not a subtype of 'NSIndexPath'. In this particular case what is the main difference between using indexPath and indexPath.row.
From the NSIndexPath class reference:
The NSIndexPath class represents the path to a specific node in a tree
of nested array collections. This path is known as an index path.
Table views (and collection views) use NSIndexPath to index their items
because they are nested structures (sections and rows). The first index is the section of the tableview and the second index
the row within a section.
indexPath.section and indexPath.row are just a "convenience" properties, you always have
indexPath.section == indexPath.indexAtPosition(0)
indexPath.row == indexPath.indexAtPosition(1)
They are documented in NSIndexPath UIKit Additions.
So NSIndexPath is an Objective-C class and is used by all table view
functions. The section and row property are NSInteger numbers.
(Therefore you cannot pass indexPath.row if an NSIndexPath is expected.)
For a table view without sections (UITableViewStyle.Plain) the
section is always zero, and indexPath.row is the row number of
an item (in the one and only section). In that case you can use
indexPath.row to index an array acting as table view data source:
cell.nameLabel.text = restaurantNames[indexPath.row]
For a table view with sections (UITableViewStyle.Grouped) you have
to use both indexPath.section and indexPath.row (as in this
example: What would be a good data structure for UITableView in grouped mode).
A better way to disable the selection of particular table view rows is
to override the willSelectRowAtIndexPath delegate method:
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
// return `indexPath` if selecting the row is permitted
// return `nil` otherwise
}
In addition, to disable the appearance of the cell highlight on touch-down, you can also set
cell.selectionStyle = .None
when creating the cell. All this is (for example) discussed in
UITableview: How to Disable Selection for Some Rows but Not Others.
Your question and confusion stems from the fact that Apple decided to use somewhat imprecise method name/signature
tableView.deselectRowAtIndexPath(indexPath, animated: false)
The "Row" word in the method signature implies that row is going to be deselected, therefore one would expect a row as a method parameter. But a table view can have more than one section. But, in case it doesn't have more sections, the whole table view is considered one section.
So to deselect a row (a cell really) in table view, we need to tell both which section and which row (even in case of having only one section). And this is bundled together in the indexPath object you pass as a parameter.
So the before-mentioned method might instead have a signature
tableView.deselectCellAtIndexPath(indexPath, animated: false)
which would be more consistent, or another option, albeit a bit less semantically solid (in terms of parameters matching the signature)
tableView.deselectRowInSectionAtIndexPath(indexPath, animated: false)
Now the other case - when you implement the delegate method
tableview:cellForRowAtIndexPath
here again (Apple, Apple...) the signature is not completely precise. They could have chose a more consistent method signature, for example
tableview:cellForIndexPath:
but Apple decided to stress the fact that we are dealing with rows. This is an 8 year old API that was never the most shining work of Apple in UIKit, so it's understandable. I don't have statistical data, but I believe having a single section (the whole tableview is one section) is the dominant approach in apps that use a table view, so you don't deal with the sections explicitly, and work only with the row value inside the delegate method. The row often serves as index to retrieve some model object or other data to be put into cell.
Nevertheless the section value is still there. You can check this anytime, the passed indexPath will show the section value for you when you inspect it.
NSIndexPath is an object comprised of two NSIntegers. It's original use was for paths to nodes in a tree, where the two integers represented indexes and length. But a different (and perhaps more frequent) use for this object is to refer to elements of a UITable. In that situation, there are two NSIntegers in each NSIndexPath: indexPath.row and indexPath.section. These conveniently identify the section of a UITableView and it's row. (Absent from the original definition of NSIndexPath, row and section are category extensions to it).
Check out tables with multiple sections and you'll see why an NSIndexPath (with two dimensions) is a more general way to reference a UITableView element than just a one-dimensional row number.
i see the perfect answer is from apple doc
https://developer.apple.com/documentation/uikit/uitableview/1614881-indexpath
if you print indexpath you may get [0,0] which is first item in the first section
I was confused between indexPath and indexPath.row until i understand that the concept is the same for the TableView and the CollectionView.
indexPath : [0, 2]
indexPath.section : 0
indexPath.row : 2
//
indexPath : the whole object (array)
indexPath.section : refers to the section (first item in the array)
indexPath.row : refers to the selected item in the section (second item in the array)
Sometimes, you may need to pass the whole object (indexPath).

Resources