Issues with tap to expand on UITableView cell - ios

I have a feature in my app that allows users to tap on a table view cell and it will expand to show more information. This works great but there are a few issues:
1) When I segue away from the table view, the top cell becomes expanded.
2) When I tap to expand a cell and then tap again to shrink the cell, it loses the dividing line above it until I tap a different cell.
3) When I slide to delete, the content in the expanded version of the cell overlaps the cell below it like this:
Here is my code for expanding the cell:
var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if tableView != self.searchDisplayController?.searchResultsTableView {
if indexPath.row == selectedRowIndex.row {
if cellTapped == false {
cellTapped = true
return 141
} else {
cellTapped = false
return 68
}
}
}
return 68
}

Related

Swift indexPathsForSelectedRows returns nil

I am working on a tableView, goal is to implement a expandible/collapsible tableView cells. The number of cells is completely dynamic.
I have things working, but with a small problem. I am using didSelectRowAtIndexPath to toggle expand/collapse cell. So what happens now is the toggle executes by tapping anywhere on the cell. :-(
Inside the cell, I have two UIViews (Header and Detail precisely). I need the toggle expand/collapse to work only when I tap on Header and not the whole cell.
For that, I am fetching the indexPath like here but that doesn't work since indexPathsForSelectedRows returns nil.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myVouchers.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndex == indexPath.row {
return 150.0
}
else {
return 40.0
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCellWithIdentifier("CollapsedCell", forIndexPath: indexPath) as! CollapsedCell
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(VoucherDetailViewController.expandCell(_:)))
(cell as! CollapsedCell).headerView.addGestureRecognizer(tapGestureRecognizer)
return cell
}
func getIndexPathForSelectedCell() -> NSIndexPath? {
var indexPath: NSIndexPath?
if tableView.indexPathsForSelectedRows?.count > 0 { //**nil is returned here**
indexPath = (tableView.indexPathsForSelectedRows?[0])! as NSIndexPath
}
return indexPath
}
func expandCell(sender: UITapGestureRecognizer) {
if let myIndexPath = getIndexPathForSelectedCell() {
if selectedIndex == myIndexPath.row {
selectedIndex = -1
}
else {
selectedIndex = myIndexPath.row
}
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([myIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
}
}
Could someone give me a clue why the indexPathsForSelectedRows returns nil?
Many Thanks!
UPDATE 1:
Current Working demo
When I add my expand cell logic in didSelectRowAtIndexPath and remove my tapGestureRecognizer for the view inside the cell - it looks like this -
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedIndex == indexPath.row {
selectedIndex = -1
}
else {
selectedIndex = indexPath.row
}
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
}
You got this bug because
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
reloads and deselect your rows.
You just select row, after that, you reload row (this action will deselect this row) and later you want to get a selected rows using
tableView.indexPathsForSelectedRows
but table view does not have any selected rows at this moment.
As a solution, you can store selected indexPath in new variable, and use this variable instead of
tableView.indexPathsForSelectedRows
From the indexPathsForSelectedRows documentation:
The value of this property is nil if there are no selected rows.
Therefore:
guard let selectedRows = tableView.indexPathsForSelectedRows else {
return nil
}
return selectedRows[0]
(no check for count is needed, the method never returns an empty array).
By the way, you could simplify the condition using:
return tableView.indexPathsForSelectedRows?.first
or, if your table does not support multiple selection, directly call:
return tableView.indexPathForSelectedRow
which is already there.
As Sulthan said try below code in place of reload
tableView.beginUpdates()
tableView.endUpdates()

Changing UITableViewCell height using tableView.beginUpdates()

SO I want to change the height of my UITableViewCell by doing something like this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedIndexPath = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath == selectedIndexPath {
return 140
} else {
return 90
}
}
However, the problem is that when I do this, the cell "expands" slower than the content of expanded portion shows, so whatever is in the expanded portion shows before the cell fully expands and it is kind of ugly. This problem can be fixed if I use tableView.reloadRowsAtIndexPaths(), but I don't want to reload the cell because I have a timer displayed in the expanded portion of the cell.

Hide UITableview cell

i'm trying to Hide a cell from a UITableView. just like the delete action do but i just want to hide it to later show it in the same position.
i know that UITableViewCell has a property called "Hidden" but when i hide the Cell using this property it hide but no animated and they leave a blank space
Example:
first cell
second cell
third cell
it's possible that when i hide the second cell, that third cell change position to 2 ?
thanks
One way to effectively "hide" a row with animation and without actually removing it is to set it's height to zero. You'd do so by overriding -tableView:heightForRowAtIndexPath:.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat height = 0.0;
if (isRowHidden) {
height = 0.0;
} else {
height = 44.0;
}
return height;
}
(You'd only want to return 0.0 for the specific row or rows you want to hide of course, not unconditionally).
Simply changing the return value of this method doesn't make the table view automatically adjust the row's height, to make it do so you make the following calls.
isRowHidden = YES;
[tableView beginUpdates];
[tableView endUpdates];
If you do so you'll see an animated appearance/disappearance transition between the two.
In SWIFT you need to do two things,
HIDE your cell. (because reusable cell may conflict)
Set Height of cell to ZERO.
Look at here,
HIDE cell
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if indexPath.row == 1 {
cell?.hidden = true
} else {
cell?.hidden = false
}
return cell
}
Set Height of cell to ZERO.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var rowHeight:CGFloat = 0.0
if(indexPath.row == 1){
rowHeight = 0.0
} else {
rowHeight = 55.0 //or whatever you like
}
return rowHeight
}
Using this you can remove reusable cell conflict issues.
You can do the same for cell?.tag also to hide specific cell by tag.
Ref: https://stackoverflow.com/a/28020367/3411787
You can't simply hide a UITableViewCell. You have to remove that cell from the table view then insert it again when you would like it to reappear in the table.
The methods you're looking for are deleteRowsAtIndexPaths:withRowAnimation: and insertRowsAtIndexPaths:withRowAnimation:. These are well documented in the UITableView documentation here. You're going to have to remember where you removed the cell from then insert it at the same position later.
Keep in mind that if you add cells to your table with a lesser row index than the row index of the deleted row, you will have to add on to the index to maintain it's relative position.
Hope this helps.
Same as Zaid Pathan but for Swift 4:
//HIDE you cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! UITableViewCell
//hide second cell
indexPath.row == 1 ? (cell.isHidden = true): (cell.isHidden = false)
return myCell
}
//Set Height of cell to ZERO.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var rowHeight:CGFloat = 0.0
indexPath.row == 1 ? (rowHeight = 0.0): (rowHeight = 49.0)
return rowHeight
}
If you want the other cells to have a dynamic height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 1 { // Or any other row
return 0
}
return -1.0
}
I am using Hidden in Attributes Inspector of TableViewCell -> ContentView and then implement method:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if shouldHideSection(indexPath.section) {
return 0.0
} else if let isHidden = tableView.cellForRow(at: indexPath)?.contentView.isHidden, isHidden {
return 0.0
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
You should delete row and reload table view
[tbv reloadData];
cell display, depending on the data source, so you need to deal with the data source.
if dataArr is the table view datasource,first of all, you should delete the data source where the cell,then
[dataArr removeObjectAtIndex:indexpath.row];
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[indexpath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
at last ,you can insert data source when you should add a cell
[dataArr insertObjectAtIndex:indexpath.row];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexpath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Best way to set rowHeight value in condition check
Dataarray where value stored .
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//set up here
let cell = myTableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! myCustomCell
if (dataArray?[indexPath.row].somevalue == "true")
{
cell.isHidden = true
tableview.rowHeight = 0.0
}
else{
// show cell values
tableview.rowHeight = 130
}
`
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let dicttemp : Dictionary = arrAllCoursesList[indexPath.row] as! Dictionary<String,Any>
var rowHeight:CGFloat = 0.0
if let getStatus = dicttemp["status"] as? String
{
if getStatus == "1"
{
rowHeight = 240.0
}
else
{
rowHeight = 0.0
}
}
return rowHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CompetitionTableViewCell") as! CompetitionTableViewCell
let dicttemp : Dictionary = arrAllCoursesList[indexPath.row] as! Dictionary<String,Any>
if let getStatus = dicttemp["status"] as? String
{
if getStatus == "1"
{
cell.isHidden = false
}
else
{
cell.isHidden = true
}
}
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}

Height broken for UIDatePicker in UITableViewCell in static cell

I'm trying to put a UIDatePicker into a UITableViewCell which is opened/closed by clicking on the cell above it. The picker seems to be rendering all wrong. Firstly, here is the related code from the UITableView class:
// Determines if the date picker should be shown
var editingDate: Bool = false
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == 1 && indexPath.row == 1 { // Picker cell
if !self.editingDate {
return 0
}
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 1 && indexPath.row == 0 { // Date cell
self.editingDate = !self.editingDate
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 1)], withRowAnimation: .Fade)
}
}
The table in the storyboard:
I have set the Table Cell to a custom height of 219 in the storyboard. Below is an animated GIF of what happens when I currently click the date field. Sorry for the terrible quality.
I noticed that we can see from the animated gif above that the cell background becomes transparent (not white) which means the Content View of the cell is not stretching to the full height. I wonder why that could be?
Edit: Here is the constraints used for the DatePicker:
If you use static cells, you can specify the height of the cell holding the picker in the storyboard editor and your code could look like this:
if (indexPath.section == 1 && indexPath.row == 1 && !editingDate) { // Picker cell
return 0
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
Update
Replace:
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 1)], withRowAnimation: .Fade)
With:
tableView.beginUpdates()
tableView.endUpdates()
Are you using AutoLayout?
If so try pinning your DatePicker by the cell's edges in IB by CTRL-dragging from it to the cell. After all 4 edges have been pinned give it another try.

Expandable content of UITableViewCells "shines" trough

I just made a UITableView in swift with expandable cells when clicking them. Upon being expanded, the cells show a UIDatePicker.
The expanding itself works fine, but the cell-content that should only visible when the cell is expanded kind of shines through the cells. Here is a screenshot of what I am talking about:
Cell 1 is expanded and the other cells are not. As you can see, their expandable content is visible while it actually shouldn't be.
Here is the code I use:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellIdentifier = "Cell";
var cell: NewExpenseTableCell;
var array: NSArray = NSBundle.mainBundle().loadNibNamed("Cell", owner: self, options: nil);
cell = array.objectAtIndex(0) as NewExpenseTableCell;
cell.backgroundColor = UIColor.whiteColor();
cell.descriptionLabel?.text = descriptionLabels[indexPath.item];
return cell;
}
var selectedRowIndex = -1;
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedRowIndex == indexPath.row {
selectedRowIndex = -1;
} else {
self.selectedRowIndex = indexPath.row;
}
newExpenseTable.beginUpdates();
newExpenseTable.endUpdates();
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex {
return 206;
}
return 55;
}
Could anyone tell me how I can make sure the UIDatePicker is only visible when the cell is expanded?
Here's what I would recommend to get the cell reuse working correctly.
Open the .xib file for your NewExpenseTableCell. Go to Attributes Inspector, enter "NewExpenseTableCell" as the Identifier.
In the viewDidLoad method of your view controller, register the nib like so:
self.tableView.registerNib(UINib(nibName: "NewExpenseTableCell", bundle: nil), forCellReuseIdentifier: "NewExpenseTableCell")
In the cellForRowAtIndexPath method, replace the first four lines you have with this:
var cell = tableView.dequeueReusableCellWithIdentifier("NewExpenseTableCell") as NewExpenseTableCell

Resources