func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let cell = tableView.cellForRow(at: indexPath) as? ExpandablePlaneTableViewCell {
return 400.0
}
return 75.0
}
I want to change size of my cell but inside of heightForRowAt it cannot see my cell and crashed. When I put there if let check it does not enter inside of the block and just takes 75.
Can anyone tell me what the problem is? It's too strange for me!
I already set delegate to self. So it call the function but cannot detect my cell there.
UPDATE
In my ExpandablePlaneTableViewCell I have a variable:
var exapanded = false
Later in my ViewController: On click on the button in the cell I run my delegate method:
func expandViewButtonTapped(_ sender: ExpandablePlaneTableViewCell, for indexPath: IndexPath) {
sender.exapanded = true
tableView.reloadRows(at: [indexPath], with: .automatic)
}
and after I want to expand it and reload the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "expandableCell", for: indexPath) as! ExpandablePlaneTableViewCell
cell.delegate = self
cell.indexPath = indexPath
return cell
}
Do not attempt to get a cell in heightForRowAt. And there certainly is no reason to do so in your case.
You seem to want the height to be one value for certain types of cells and another height for other types.
Simply use the same basic logic you have in cellForRowAt, based on the indexPath, to determine which height to return. In other words, base the decision on your data model, not on the cell.
Related
I am using a UITableView and what I am doing is I am changing the color of the cell when I tap on the cell using didSelectRow function of UITableView at cellForRowAt. The thing which is bothering me is when I scroll down or scroll up, those cells whom I changed the color before were changed to other cells. Here is my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "TasksTableViewCell") as! TasksTableViewCell
cell.backView.backgroundColor = .white
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = myTableView.cellForRow(at: indexPath) as! TasksTableViewCell
cell.backView.backgroundColor = UIColor(named: "primaryViewColor")
}
Does anyone knows why this happens? Does anyone has a solution that when only those cells changes color whom I tap on, and when I scroll down or move up only those cells have the other color?
cellForRowAt will be called every time that cell is displayed.
you need selected list to save selected index.
var listSelected: [Int] = []
and
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TasksTableViewCell") as! TasksTableViewCell
cell.backView.backgroundColor = listSelected.contains(indexPath.row) ? UIColor(named: "primaryViewColor") : .white
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if listSelected.contains(indexPath.row) {
listSelected = listSelected.filter{$0 != indexPath.row}
} else {
listSelected.append(indexPath.row)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
I encountered do you see the problem many times. Even if using and iVar can solve the problem, You are mixing "Controller" logic and "Model" logic.
I usually prefer to move "selection" state inside the model.
Suppose You have a class "Contact" you use to fill cell data (usual MVC pattern)
I add:
class contact{
..
var selected = false
}
AND in TV delegation method I use to apply selection, OR better I use a custom selection method in a custom cell (for example to see a √ element in cell)
As a bonus multiple selection come for free, and you can also save current selections for next run :)
So as I understand you select a cell and after that other cells look like they are selected?
If so I think this is happening because you change the background color of the cell and tableViews and collectionViews are reusing the cells, basically keeping the background you changed behind.
TableViewCells are reused as soon as they leave the visible area.
This means that a cell whose background you have colored will be deleted from the view hierarchy as soon as it is scrolled up or down. If the corresponding row is scrolled in again, the function cellForRowAt is called again for this IndexPath and the cell gets a white background.
The easiest is to save the IndexPaths of the selected cells and check in the cellForRowAt function if the current cell has to be selected.
Add the following var to the viewController class:
var selectedIndexPaths = Set<IndexPath>()
and modify the tableView delegate methods:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = myTableView.dequeueReusableCell(withIdentifier: "TasksTableViewCell") as! TasksTableViewCell
cell.backView.backgroundColor = (selectedIndexPaths.contains(indexPath) ? UIColor(named: "primaryViewColor") : .white)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if selectedIndexPaths.contains(indexPath)
{
selectedIndexPaths.remove(indexPath)
}
else
{
selectedIndexPaths.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .none)
}
You can use
step 1: create model
class DemoModel {
var isSelected: Bool = false
var color: UIColor = .While
}
step 2: and in tableview
var listDemo: [DemoModel] = [DemoModel(),...]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier:
"TasksTableViewCell") as! TasksTableViewCell
var obj = listDemo[indexPath.row]
cell.backView.backgroundColor = obj.color
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var obj = listDemo[indexPath.row]
obj.color = UIColor(named: "primaryViewColor")
tableView.reloadRows(at: [indexPath], with: .automatic)
}
My table have the functionality to delete a row, but when add a new row is writting two labels in the same place, a label above of the other label
a label belong to the deleted row and the other label belong to the new label when add a new row
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
tableView.register(newCell.self, forCellReuseIdentifier:"newCell");
cell = tableView.dequeueReusableCell(withIdentifier: "newCell", for: indexPath) as! newCell
cell.textLabel.text= value
return cell
}
}
First of all, register your cell outside the cellForRowAt indexPath function, preferably where you are adding your tableView.
For this instance, register it in viewDidLoad
So
tableView.register(newCell.self, forCellReuseIdentifier:"newCell")
goes into viewDidLoad
for the function, try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "newCell", for: indexPath) as! newCell
cell.textLabel.text= value
return cell
}
Hope this helps. Also, provide more context to what is going wrong and what is the intended behaviour.
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 have a UITableView, which as an example contains dynamic cells I create based on the content of an array.
I can populate these using the count of the array and indexPath to render a cell per item. I am happy with this and it works well.
I would like to try now and create static cells programmatically.
Immediately however I am stumped, how do I create this? I'm currently overriding numberOfRowsInSection and cellForRowAt indexPath as follows:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! ProfileCell
cell.rowContent.text = items[indexPath.row]
return cell
}
I suspect my first mistake is dequeueReusableCell and would really appreciate any help.
If I understood your question, you want to add a static cell in a tableView that contains dynamic cells.
If that is the case, you could hardcode this, increasing the return value here:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count+1
}
In this case, you want to add just one static cell.
In cellForRowAtIndexPath, you should define where you want to add this static cell. In the example below, it would be the first cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//should change StaticCell to the static cell class you want to use.
let cell = tableView.dequeueReusableCell(withIdentifier: "staticCell", for: indexPath) as! StaticCell
//hardcore attributes here, like cell.rowContent.text = "A text"
return cell
}
let row = indexPath.row-1
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! ProfileCell
cell.rowContent.text = items[row]
return cell
}
It's basically about shifting the items[row] according to the number of static cells you want to use.
I'm not sure if that will work, but would be my first guess (according to my experience about that). Try this and tell me if it worked :)
I want to have ActivityIndicatorView inside each UITableViewCell and start animation after tap (to show that action is in progress). But I cannot start the animation effect.
I added ActivityIndicatorView to my cell prototype and connected it via #IBOutlet. This is my code to start animation after user selects table row:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedAction = actions[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionTableViewCell
cell.activityInProgressIndicator.startAnimating()
}
I also tried adding new instance of ActivityIndicatorView as cell.accessoryView. Without any effect.
I also tried to update the cell either via tableView.reloadData() (which I would like to avoid) and tableView.beginUpdates() and tableView.endUpdates()
Ideally this spinning indicator should be hidden but setting .isHidden = false in didSelectRowAt also does not work.
Just replace
let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell",
for: indexPath) as! ActionTableViewCell with below line
let cell = tableView.cellForRow(at: indexPath) as! ActionTableViewCell.
Hope it will work!!
The problem is that you're creating new instance of ActionTableViewCell when you use tableView.dequeueReusableCell inside didSelectRowAt, which is wrong.
You need to use something like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ActionTableViewCell else { return }
cell.activityInProgressIndicator.startAnimating()
}
This will fix your issue.