my data set is like
var data = [["1"],["2","3"],["4"]]
and I want to show it in table view
which I am able to using
cell.textlabel?.text = data[indexPath.section][indexPath.row]
I have a button in navigation bar which when I click should increment all the sections row by 1 without changing anything in data
and all the newly created cell textfield should say "add here"
my cell for rowat
let cell = table.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textlabel?.text = data[indexPath.section][indexPath.row]
return cell
how should I change my cell for row at so that I don't get fatal error index out of range
Adding to #iOSArchitect.com 's answer,
You should check the index in cellForRowAt.
The reason you are getting index out of range exception is because your numberOfCell count increases but the cell's data source remains the same.
So,
let cell = table.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.row < data[indexPath.section].count{
cell.textlabel?.text = data[indexPath.section][indexPath.row]
}else{
cell.textlabel?.text = "add here"
}
return cell
This may not be the precise code but I hope you get the logical answer out of this.
Declare a variable named extraRows and assign its initial value as 0 and add a property observer to it so that whenever it changes, it reloads the tableView
var extraRow : Int = 0 {
didSet {
self.tableView.reloadData()
}
}
After the click of the add button, update extraRow to + 1
#IBAction func addBtnClicked(_ sender : Any) {
extraRow = extraRow + 1
}
Also, update your number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count + extraRows
}
Along with this, you also need to update your tableview cellForRowAtIndexPath and place a check for if indexPath.row > data.count , then show your empty celll
Related
I created a score board using table view and I want to highlight the first row of the table. When I run the app, the first row is highlighted as expected but when I scroll the table, several cell is highlighted instead of first cell.
Here is my code;
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "score", for: indexPath) as! ScoreTableViewCell
if indexPath.row == 0 {
cell.backgroundImageView.image = UIImage(named: "firstClass")
}
cell.nicknameLabel.text = self.sortedResults[indexPath.row]["nickname"] as? String
cell.scoreLabel.text = " \(indexPath.row + 1)"
return cell
}
Cells are dequeued meaning when your cellForRow is asked for cell ( that's not the first one ) the returned cell may dequeue that first cell so make sure to set nil when it's not index zero by replacing
if indexPath.row == 0 {
cell.backgroundImageView.image = UIImage(named: "firstClass")
}
with
cell.backgroundImageView.image = indexPath.row == 0 ? UIImage(named: "firstClass") : nil
Essentially I have a tableview with two types of cells. One is a cell that contains a number that is being pulled from an array. The second cell is just supposed to act as a spacing cell but can contain information. However, I've only seen solutions that have the second cell come after the first if it its row is equal to 0 or if it is sectioned off.
The array of numbers is [1,2,3,4,5,6,7] and the first cell type pulls from it to display each number
I'm trying to get something that looks like this:
celltypeone: 1
celltypetwo
celltypeone: 2
celltypetwo
celltypeone: 3
celltypetwo
celltypeone: 4
celltypetwo
... and so on.
I thought about trying to get the space cell to come after every next cell but I didn't think that made sense.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if index >= 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}
}
This prints all the numbers from the array and displays them but the second cell does not show at all.
I'm unsure of what to do or how to get it to work.
if (index % 2) == 0 | try if index number is double put the first tableCell else put the other one
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if (index % 2) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}
If you notice your product cell needs to be shown at even position and the spacing cell at odd position.
`index >= 0 ` needs to replaced with `index % 2 == 0 `
Moreover in order to fetch data from product array you will be needing to divide the index by 2.
let products = [1,2,3,4,5,6,7]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
if index % 2 == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsblock", for: indexPath ) as! DetailTBCell
// Fetching data from array products defined above
let data = products[index / 2]
return cell
}
else {
let spacecell = tableView.dequeueReusableCell(withIdentifier: "spacingcell", for: indexPath ) as! DetailSpaceTBCell
return spacecell
}
This question follows up from this: Use UICollectionViews to create dynamic and multiple features.
I am able to create a static cell which displays the name and image of the recipe similar like this app:
Where I am stuck is creating a dynamic row which changes based on the amount of data inside i.e. utensils or nutritional values like the image below:
I know how to display rows of data on tableView normally. But not sure how to embed it into a section inside a tableView. I attempted to add multiple prototype cells and assign them to a subclass of UITableViewCell's. Then I try to use if statements in my cellForRow but this isn't soling my issue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
//set the data here
cell.recipeTitle.text = recipe.name
cell.imageView.image = UIImage(named: "url")
return cell
}
else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! SecondCell
//set the data here
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath) as! ThirdCell
//set the data here
return cell
}
}
I have also looked at this demonstration: https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429, which is near to what I want to achieve but I have found it quite difficult to adapt to it.
If someone could direct me on how best to approach this or a good example then I would really appreciate it.
First you can't have static cells and dynamic cells in the same tableView. So how do you work around that? Define each of the static cells in the sections they belong in as well as the dynamic cells in the sections they belong to. That, however doesn't look like what you are trying to do. You just want multiple sections in the same tableView, each section with a different list of data
To do this you will need the number of sections so use the tableView(_:numberOfSections:) function.
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
You can then(and probably should) give each of those sections a title by initializing an array with the titles in your tableViewController(assuming thats what you are using. It could also just be a tableView).
let headerTitles = ["Nutritional Values", "Utensils", "Ingredients"]
Then use the tableView(_:titleForHeaderInSection:)
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
Now you can start defining your rows by the sections.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 0 {
//Setup up the first row
if indexPath.row == 0 {
//I'm not sure where or how you defined First/SecondCell but this may not work depending on those two questions.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FirstCell
return cell
} else if indexPath.row == 1 {
let cell = Bundle.main.loadNibNamed("StaticCell", owner: self, options: nil)?.first as! StaticCell
return cell
}
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! SecondCell
//setup cell1 n your storyboard to have a label with a tag # of 12(or whatever number you want to use)
//you also want an array with your utensil data accessible here
let label = cell.viewWithTag(12) as! UILabel
label.text = utensilNames[indexPath.row]
return cell
} else if indexPath.section == 2 {
let cellIngredients = tableView.dequeueReusableCell(withIdentifier: "Ingredients", for: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
return cellIngreidents
}
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
return cell
}
The point here is to use sections then rows to distribute your data.
Just to clarify Section 0 Row 0 - N would be where you're static rows are setup. I found it best to use XIB files subclassing TableViewCell.
Hope this helps.
EDIT So the way I'm looking at the "static" cells is in the first section the xib is the only put exactly where you tell it to be placed. In the example above the first section in the second cell is the
every time I scroll up or down value get changed with wrong value at 1st it shows correct value but after scrolling it get changed every time.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ShowPercentageAttandanceTableViewCell
cell.rollNo.text = String(describing: self.mainArrayRoll[indexPath.row])
var location = Int()
for item in self.rollPercentage {
if item.rollCall == indexPath.row {
location = item.absentCount
cell.percentage.text=String(location)
}
else if cell.percentage.text==("Label")
{
cell.percentage.text=String("0")
}
}
return cell
}
Here is the code.
[![Here in image 2 label is having text =2 and gray color label is having text 0 but it automatically get change after scroll as shown in image 2[![image 2 label changes after scroll but it is showing wrong][2]][2]
You are failing to set the cell.percentage.text to "0" when you didn't find the value you were looking for and you had a reused cell.
Try this:
if let item = self.rollPercentage.first(where: { $0.rollCall == indexPath.row }) {
location = item.absentCount
cell.percentage.text = String(location)
} else {
cell.percentage.text = "0"
}
I have a table with 3 rows each with check button.What I am doing is when I select all the three buttons I want to click my cancel button which is on view not table on same controller to reload all 3 rows the call goes to custom cell class where uncheck is set to true and rows are reloaded.For the first attempt it works fine I can see correct index to be reloaded.On the second time again when I select all 3 check buttons and click cancel again I can see correct index to be reloaded but the call is not going to custom cell class again the check box still remains checked.Any idea why?
I am always getting correct index in my array.
Cancel button code-:
#IBAction func cancelDataItemSelected(_ sender: UIButton) {
for index in selectedButtonIndex{
let indexPath = IndexPath(item: index, section: 0)
print(selectedButtonIndex)
filterTableViewController.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
}
selectedButtonIndex .removeAll()
print(selectedButtonIndex)
}
Table code-:
extension filterControllerViewController:UITableViewDataSource,UITableViewDelegate
{
// NUMBER OF ROWS IN SECTION
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return ControllerData.count
}
// CELL FOR ROW IN INDEX PATH
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let Cell = tableView.dequeueReusableCell(withIdentifier: "filterCell", for: indexPath) as! ControllerCellTableViewCell
Cell.filterTableMenu.text = ControllerData[indexPath.item]
Cell.radioButtonTapAction = {
(cell,checked) in
if let radioButtonTappedIndex = tableView.indexPath(for: cell)?.row{
if checked == true {
self.selectedButtonIndex.append(radioButtonTappedIndex)
}
else{
while self.selectedButtonIndex.contains(radioButtonTappedIndex) {
if let itemToRemoveIndex = self.selectedButtonIndex.index(of: radioButtonTappedIndex) {
self.selectedButtonIndex.remove(at: itemToRemoveIndex)
}
}
}
}
}
return filterCell
}
Custom Class-:
var radioButtonTapAction : ((UITableViewCell,Bool)->Void)?
//MARK-:awakeFromNib()
override func awakeFromNib() {
super.awakeFromNib()
filterTableSelectionStyle()
self.isChecked = false
}
// CHECKED RADIO BUTTON IMAGE
let checkedImage = (UIImage(named: "CheckButton")?.withRenderingMode(UIImageRenderingMode.alwaysOriginal))! as UIImage
// UNCHECKED RADIO BUTTON IMAGE
let uncheckedImage = (UIImage(named: "CheckButton__Deselect")?.withRenderingMode(UIImageRenderingMode.alwaysOriginal))! as UIImage
// Bool STORED property
var isChecked: Bool = false {
didSet{
// IF TRUE SET TO CHECKED IMAGE ELSE UNCHECKED IMAGE
if isChecked == true {
TableRadioButton.setImage(checkedImage, for: UIControlState.normal)
} else {
TableRadioButton.setImage(uncheckedImage, for: UIControlState.normal)
}
}
}
// FILTER CONTROLLER RADIO BUTTON ACTION
#IBAction func RadioButtonTapped(_ sender: Any) {
isChecked = !isChecked
radioButtonTapAction?(self,isChecked)
}
Fundamental misunderstanding of how "reusable" table cells work.
Let's say your table view is tall enough that only 8 cells are ever visible. It seems obvious that 8 cells will need to be created, and they will be reused when you scroll.
What may not be obvious is that the cells also are reused when they are reloaded. In other words, every time .reloadData is called - even if you are only reloading one cell that is currently visible - that cell is reused. It is not re-created.
So, the key takeaway point is: Any initialization tasks happen only when the cell is first created. After that, the cells are reused, and if you want "state" conditions - such as a checked or unchecked button - it is up to you to "reset" the cell to its original state.
As written, your cellForRowAt function only sets the .filterTableMenu.text ... it ignores the .isChecked state.
You can mostly fix things just by setting the cell's .isChecked value, but you're also tracking the on/off states in a much more complicated manner than need be. Instead of using an Array to append / remove row indexes, use an Array of Booleans, and just use array[row] to get / set the values.
Then your cellForRowAt function will look about like this:
// CELL FOR ROW IN INDEX PATH
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let filterCell = tableView.dequeueReusableCell(withIdentifier: "filterCell", for: indexPath) as! ControllerCellTableViewCell
// set the label in filterCell
filterCell.filterTableMenu.text = ControllerData[indexPath.item]
// set current state of checkbox, using Bool value from out "Tracking Array"
filterCell.isChecked = self.selectedButtonIndex[indexPath.row]
// set a "Callback Closure" in filterCell
filterCell.radioButtonTapAction = {
(checked) in
// set the slot in our "Tracking Array" to the new state of the checkbox button in filterCell
self.selectedButtonIndex[indexPath.row] = checked
}
return filterCell
}
You can see a working example here: https://github.com/DonMag/CheckBoxCells
Remember that the cells are reused and that reloadRows just tells the rows to redraw. When a checkbox in a cell is checked by the user, the new checked state should be saved in the underlying data source, and the state marked in the cell in cellForRowAtIndexPath. Otherwise the cell checkbox shows the state for the last time it was set by the user for all indices and not the state for the underlying data source.