I've a table view controller in which I want to dynamically create a row (cell). How can I access row by index number in Swift? The following is what I did and now I'm stuck here.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "teamStats", for: indexPath) as! TeamStatsTableViewCell
return cell
}
This is my TableViewController code in which I want to check if the row is at a specific index number.
This method is a delegate method which will be called every time a cell is displayed on your UITableView. So if you want to do something with a cell in a particular index, you do the following. Please go through Apple docs and get a better understanding of what delegate methods are.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "teamStats", for: indexPath) as! TeamStatsTableViewCell
if indexPath.row == requiredIndex {
//Do something.
}
if indexPath.row == lastIndex { // you have the last index with you. replace this variable with that.
cell.firstlabel.text = ""
}
return cell
}
Please try this code:
if indexPath.row == 1{
//Your code
}
You can check the section :
if indexPath.section == 1{
}
It may helps to you.Thank you
Related
I am currently working on a swift based HRM project. where it requires to show a tableview with slightly customized cell. cells it self containing two buttons, under some business logic one button would be hidden. for example ,
if the current user is the employee himself , he can see a list, the cell containing his name can see two buttons,but other cell would show simply one button.
i have tried the followings:
1. if the userId == employeeId (employeeId came from a model) then ,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
also , i have tried
if(self.claimdata[indexPath.section].employeeId != self.empId) {
cell.CancelButton.frame.size.height = 0
}
works fine for the first frame , the problem begins when i begin to scroll. for some unintended cell it also shows two buttons.
Am I missing something?
This issue is due to the cell reusability in UITableView.
Use below code in your cellForRowAtIndexPath method.
cell.CancelButton.isHidden = true
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
As tableView cell is reusableCell
dequeueReusableCell withIdentifier
you just need to give else condition so when it reuse the cell again it knowns what to do with CancelButton.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}else{
cell.CancelButton.isHidden = true
}
}
I have an table view with three cell which contains the label and one image (check box).Now when ever i select any cell.That particular cell image (check box) alone needs to get tick.png. And remaining two cell image should be untick.png.
But now if i select first cell then the first cel image get as tick.png.Then if i select second and third cell.That cell image also getting tick.png
But i need only one image alone needs to tick.png.Which ever table view cell i am selecting that particular cell image alone needs to be tick.png.And remaining two cell image should be untick.png.
My code :
var Data: [String] = ["First","Second","Third"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.Data.count > 0{
return self.Data.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
cell.Lbl.text = self.aData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! suggestionCell
cell.suggestionImg.image = UIImage(named: "tick")
}
If I understand you correctly you only want a single check mark at any given time. If this is true then you would simply setup a property in your view controller like this:
var checkedRow: Int
and set the row index in tableView(_:didSelectRowAt:). By setting it to -1 you would disable all check marks. Then in tableView(_:, cellForRowAt:) you would conditionally enable the check mark for the cell if indexPath.row is equal to checkedRow:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
checkedRow = indexPath.row
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
if indexPath.row == checkedRow {
cell.suggestionImg.image = UIImage(named: "tick.png")
cell. suggestionLbl.text = "<ticked text>"
} else {
cell.suggestionImg.image = UIImage(named "untick.png")
cell. suggestionLbl.text = "<unticked text>"
}
return cell
}
As an add-on to Tom's answer, I suggest storing IndexPath instead of Int adding also a
var lastCheckedRow:IndexPath = IndexPath(row: 0, section: 0)
This allows you to only reload the newly checked row and the previously checked row instead of the whole table view plus it will support multiple sections too. It does not matter much at your current stage where there is only 3 rows but for larger table views this will be more efficient. Also it removes the blinking effect of UITableView.reloadData().
The code is something like:
//0 based on assumption that first cell is checked by default
var checkedRow:IndexPath = IndexPath(row: 0, section: 0)
var lastCheckedRow:IndexPath = IndexPath(row: 0, section: 0)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Update checkedRow for reload but keep track of current tick
lastCheckedRow = checkedRow
checkedRow = indexPath
//Remove previous tick
tableView.reloadRows(at: [lastCheckedRow], with: .automatic)
//Update new tick
tableView.reloadRows(at: [checkedRow], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
if indexPath.row == checkedRow {
cell.suggestionImg.image = UIImage(named: "tick.png")
} else {
cell.suggestionImg.image = UIImage(named "untick.png")
}
return cell
}
You can also play around to create an ideal visual effect when ticking different cell by changing the with:UITableViewRowAnimation parameter which I use .automatic for the example.
allowsMultipleSelection: Is only easiest thing that will help you.
Add following line in your viewDidLoad after setting up the tableView
override func viewDidLoad() {
// ... setup you need
tableView.allowsMultipleSelection = false
}
Hope this helps!
I call tableView.reloadData() inside of didSet{} of the items variable.
I got a crash in the cellForRowAt function only once, never before, never after with no code changes.
override func tableView(_ tableView: UITableView, number ofRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row] //Thread 1: Fatal error: Index out of range
cell.textLabel?.text = item.title
The possible solution will be you need to handle to solve the crash.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row > items.count-1){
return UITableViewCell()
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row] //Thread 1: Fatal error: Index out of range
cell.textLabel?.text = item.title
return cell
}
}
Maybe this will help.
I can see it happening if the table is scrolling quickly and you update the data source: it could hit a race condition where it tries to call cellForRowAt one more time before it realizes you changed the data source. As a precaution, I'd suggest always adding a check that your index is less than the array's count. Maybe it's paranoid, but better than a crash.
e.g:
Array has 100 items.
Swipe hard sending you towards bottom of table.
While scrolling, the array is updated to only have 10 items.
Table scroll asks for cell at row 99 because it didn't get the message yet.
Crash when you ask for item 99 in an array with only 10 items.
This crash appears when your index is more than array's count.
This crash may appear when scrolling the table view and table view get's updated. You are trying to fetch the index more than array count.
You can check the condition of indexpath.row less than array count.
Try using some thing like this
var fileNameArray = [Audio](){
didSet{
if fileNameArray.count > 0{
recordingListTableView.reloadData()
}
}
}
Is It required to do this ?
crash Is index out of range
Are you Re-Updating Array After this didSet Statement ? if Yes, Do not use this DidSet case here , use if Array is not going to be updated again ,
If you still want to use , Also make use of WillSet Case After didSet()
override func tableView(_ tableView: UITableView, number ofRowsInSection section: Int) -> Int {
guard let items.count > 0 else {return 0}
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row]
cell.textLabel?.text = item.title
}
I think items array does not have any value so it does crash. So, try this above code.
I have a VERY annoying problem.
I have a tableView with more or less 50 cells, displaying some options to which I can select the ones I want. I read in Apple's documentation that by default the cells are reused when they are not displayed. With this, if I select the first cell, every 6 cells 1 is marked, that is, if I select the first 6 cells, ALL cells in the table are marked!
My table view allows multiple selections. The selection is being made like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
How do I solve this? I know you have such a "prepareForReuse ()" for the subclass, would that be the solution? If so, can you give me an example of how you would do it?
here is code it may help you
var arr_selectedindePath = NSMutableArray()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if arr_selectedindePath .contains(indexPath) {
arr_selectedindePath .remove(indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
else
{
arr_selectedindePath .add(indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
if arr_selectedindePath .contains(indexPath) {
cell.accessoryType = .checkmark
}
else
{
cell.accessoryType = .none
}
}
You will need to update your data model accordingly so that when cellForRowAtIndexPath is called it displays cells with updated content.
If you don't use a datamodel you will need to store the indexPath's in a mutable array and make a check whether the current indexPath is marked or not.
Hope this helps.
In your custom cell
1.Create a delegate
protocol CellDelegate {
func didTapOnButton(_ cell:Cell)
}
2. Declare delegate
var delegate:CellDelegate?
3.Override this method
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
#IBAction func buttonTapped()
{
self.delegate!.didTapOnButton(self)
}
In your tableview controller
1.Implement delegate method
2.Inside cellForRowAtIndexPath assign tag value to cell.button
3.implement this method
func didTapOnButton(_ cell: Cell) {
print("off button clicked at index \(cell.button.tag)")
}
I Have a UITableView which is controlled by NSFetchedResultsController. I want to add single cell to the first row and make this cell static. In other words, there will be a button which will open another View Controller.
Until now, I was ok with fetched results controller and table. Now I'm a bit confused. How should I do this?
Instead using a header might be ok too, but I don't want this header to be on top all the time. I want this cell to be just like WhatsApp iOS "Create new group" cell on chats panel.
Thank you!
var dataArray = ["A","B","C"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count+1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "CreateNewGroupCell") as! CreateNewGroupCell
return cell
}
else
{
// Get the data from Array
let data = self.dataArray[indexPath.row-1]
// Logic to show other cells
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCell") as! OtherCell
return cell
// ....
}
}
You will need to create tableview with number of rows fetched from NSFetchedResultsController +1. Also in cellForRowIndex method you will need to add a check like indexPath.row == 0 and in there you will make the changes.
Also you will have to add action for that button within that section. You can also set different custom tableview for first row.
It can be similar to following:
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row==0){
let cell = tableView.dequeueReusableCell(withIdentifier: "CellWithButton", for: indexPath) as! CellWithButton
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCells", for: indexPath) as! OtherCells
//here add data for cells from your array
}
return cell
}