I am making a form in a tableview.
Let's say I have 4 different types of cells, each being a question with different kind of answers
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if sortedFixedContentType.count != 0 {
let item = sortedFixedContentType[indexPath.row]
switch item.typeId {
case "1":
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell
return cell;
case "2":
let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! SecondCell
cell.customDelegate = self
return cell;
case "3":
let cell = tableView.dequeueReusableCell(withIdentifier: "ThirdCell", for: indexPath) as! ThirdCell
cell.commentsTextView.delegate = self
return cell;
case "4":
let cell = tableView.dequeueReusableCell(withIdentifier: "FourthCell", for: indexPath) as! FourthCell
return cell;
}
When the tableView is loaded I want to show only first cell, and depending on the answer different cells will be shown.
For example:
FirstCell can be answered with A, B, or C,
If I answer A SecondCell will be shown with answers X and Y.
If X is the answers ThirdCell will be shown (which has no options but a TextField), and when completed FourthCell will be shown
But if in FirstCell the answer is B or C only FourthCell will be directly shown.
At the moment I was doing it by changing the height of the rows in heightForRowAt, although I think there must be an easier way.
However I'm finding a problem:
If I get to the textField in ThirdCell and then I change my first answer, SecondCell is hidden but ThirdCell is not, as the condition to it was the second answer and it's already made, so I thought on setting the height of each row as condition to, but I don't know how to do it.
So I have two main questions:
Is it possible to access to the heightForRowAt to set it as a condition?
Should I make it this way? or maybe there's a better way to get what I need? I read about adding and deleting rows dynamically to tableviews but with the same cell type, this is why I decided to hide them by their height instead.
Thanks in advance!
I think the conventional method is to not modify the height but manipulate the data source (the number of rows in section etc.) to show/hide the appropriate cells.
You should update the data source appropriately after an event and then immediately after you can use func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) and tableView.deleteRowsAt(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) to insert/delete cells in the tableView
This documentation might help: https://developer.apple.com/documentation/uikit/uitableview/1614879-insertrows
What I usually like to do is monitor a variable and when the variable is updated adjust the height of the cell. Make sure that your variable has this didSet code assigned to it so your tableview updates the height when the variable changes.
var selectedRow: Int = 999 {
didSet {
tableView.beginUpdates()
tableView.endUpdates()
}
}
And then, just like you have done I affect the height of the row inside of the heightForRow function.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRow { //assign the selected row when touched
let thisCell = tableView.cellForRow(at: indexPath)
if let thisHeight = thisCell?.bounds.height {
print("Bam we got a HEIGHT!!")
return thisHeight + 50
}
}
return 60 //return a default value in case the cell height is not available
}
Related
So I have a tableview that has a list of items in each cell. Each of these cells contain an image view which, upon being tapped, expands the cell and displays the image for that item. When I scroll down the table view and scroll back up to the cell that was selected, the image is gone. I know this is due to reusing cells but I'm not sure on how to keep the expanded cells image in place while scrolling through other items.
The closest I've come is here:
my table view reuse the selected cells when scroll -- in SWIFT
If someone could lend me a hand that would be awesome. Thanks!
Edit: Adding code snippets - Sorry for the wait.
fileprivate var expandedRowIndex: Int?
// cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// CatalogItem row.
let item = self.items[indexPath.row]
let expanded = indexPath.row == self.expandedItemRowIndex
// Return standard catalog item cell.
let reuseID = expanded
? CatalogItemCell.PROTOCELL_EXPANDED.id
: CatalogItemCell.PROTOCELL.id
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID) as! CatalogItemCell
// Reset thumbnail image back to nil. Needed so that images appear
// only in the cell that they belong in.
if indexPath.row == self.expandedRowIndex{
cell.uiImage_Thumbnail.image = nil
}
return cell
}
// didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
// Expand row - Get the current cell and show image
self.expandedItemRowIndex = indexPath.row
let item = self.items[indexPath.row]
let currentCell = tableView.cellForRow(at: indexPath)
// Pass both the selected cell and item to the ImageManager
ImageManager.startImageRequest(currentCell: currentCell!, item: item)
if self.expandedRowIndex == indexPath.row
{
// Selected row is already expanded.
return
}
var reloadPaths = [IndexPath]()
// Collapse previously expanded row.
if let previousRowIndex = self.expandedRowIndex
{
reloadPaths.append(IndexPath(row: previousRowIndex, section: 0))
}
// Expand the selected row.
self.expandedRowIndex = indexPath.row
let item = self.items[indexPath.row]
debugPrint(item.description)
reloadPaths.append(IndexPath(row: indexPath.row, section: 0))
tableView.reloadRows(at: reloadPaths as [IndexPath], with: .fade)
}
You can maintain a selectedIndex variable.
In your cellForRow you check whether this call is for selectedCell. If yes, then do the customisation that is required for selected cell.
Also you might want to handle heightForRow, there also check whether the call is for selected cell.
You can maintain an indexPath for selected cell. If there are multiple sections.
No need to prevent it from getting reused.
Hope that helps.
My first line of writing
Copy itself to the bottom lines
And when I scroll,
Places of data are changing
Data in UITableView Cell is repeated because Cell is reused so you will need to keep track of data for cell, may be you can add data in array arranged by index.
Yes thats the concept of UITableview dequeCells: as it reuses cells , so if there are say 100 entries only limited number of cells will be created at a time in order to save memory.
Now in order to avoid it, fill your cell views with datasource that has all the values.
If you see that some values are missing from datasource, just use, for example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Tek101TableViewCell
cell.selectionStyle = .none
cell.siraLabel.text = ""
cell.siraLabel.text = String(indexPath.row + 1) + ")"
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Tek101TableViewCell
cell.selectionStyle = .none
cell.siraLabel.text = String(indexPath.row + 1) + ")"
return cell
}
You need to set value in textfield delegate method as below :(As per your need)
If you are use return button in keyboard
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let cell = textField.superview?.superview as! Tek101TableViewCell // track you view hierarchy
cell. siraLabel?.text = textField.text
textField.resignFirstResponder()
return true
}
If you want to track value end editing method
func textFieldDidEndEditing(_ textField: UITextField){
let cell = textField.superview?.superview as! Tek101TableViewCell // track you view hierarchy
cell. siraLabel?.text = textField.text
}
In UITablview cell is reused so this thing will happen. To solve this issue you have to keep all the records inside one global array variable.
You can store all input in array or dictionary while user insertion. and then you have to give this array values to each and every cell from CellForRow.
In short you have to deal with array only.
Hope this will help you.
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
}
I'm trying to implement an expanding cell using this
here is my heightForRowAt indexPath
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0 : return 70
case 1 :
let cell = self.tableView(tableView, cellForRowAt: indexPath)
if let datePickerCell = cell as? MyDatePickerCell {
return datePickerCell.datePickerHeight()
}
return 260
case 2 : return 80
default : return 60
}
}
The app gets stuck at
let cell = self.tableView(tableView, cellForRowAt: indexPath)
the UITableView is embeded in a modal UIViewController
why is this happening?
I think what is happening is that your tableView is looking for the heights of your cells before loading them in the first place. So you are trying to retrieve the cell before it is loaded, causing a crash.
To fix this you could create a dummy cell before loading the tableView and using that cell in heightForCell. That's more applicable for calculating variable heights for each cell though. In this case maybe hardcode the start height and then use the cell for heights after that.
No article explains it clearly regarding my query, I have three cells in a static table and I want to hide second cell when users taps on first cell. Any kind of help is appreciated.
Although you cannot stop the static table from trying to show your cells, you can set their height to zero, making them effectively invisible:
Add this method to your table view controller delegate class:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
return cell == myHiddenCell ? 0 : super.tableView(tableView, heightForRowAtIndexPath:indexPath)
}
In the didSelectCellAtIndexPath method, you can set the height to 0 to hide it :
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
let indexPath = NSIndexPath(forItem: 1, inSection : 0)
let secondCell = tableview.cellForRowAtIndexPath(indexPath)
secondCell.frame.size.height = 0;
self.view.layoutSubviews()
}
}
If you want an animation, just put self.view.layoutSubviews() in an UIView animation method UIView.animateWithDuration... etc
For me, setting the height to 0 for some cells and another height for other cells wasn't an option, as all my cells have different height.
I created another cell in Storyboard, and set row height of 0 (in size inspector). Then in the code, I show the cell with height = 0 if I want to hide it, if not, I show the other cell:
if (hideCell) {
let hiddenCell = tableView.dequeueReusableCell(withIdentifier: "hiddenCell",for: indexPath) as! TheWallTableViewCell
return hiddenCell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as! TheWallTableViewCell
return cell
}