Drag and drop in tableView cell - uitableview

I have implemented drag and drop functionality in tableView. The problem is when I drag and drop the cell, index at dropped place remain same as of picked position index. How can I keep the drop position index unchanged.
For Example: I have two cell cell-0 and cell-1 both having UILable. After dragging cell-1 to 0th index, my table shows:
cell-1
cell-0
and if I amend any changes in cell-0 label, it makes changes in label of cell-1.
any solution to keep the cell index as it is after drag and drop. Or any other solution will be appreciated.
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.tableStoryArray[sourceIndexPath.row]
tableStoryArray.remove(at: sourceIndexPath.row)
tableStoryArray.insert(movedObject, at: destinationIndexPath.row)
debugPrint("\(sourceIndexPath.row) => \(destinationIndexPath.row)")
}

Related

How to reset indexPath.row value to zero when reloading data?

I have a UITableView that has about 30 items. Here's an overview of how my tableview works: In order to enable infinite scrolling and a small memory size, I calculate the scrolling direction, speed, and I remove the top 10 items and add 10 items to the bottom (the same goes for scrolling up the other way). If I were to scroll to an item, the indexPath.row stays at that same item when I attempt to reloadData() with a new set of 30 items.
I have tried creating my own index to use, but it has a lot facets to it that would make it pretty complicated. I'm looking to see if I can just reset indexPath.row.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
item = items[indexPath.row]
}
Sometimes, I would like to jump to another set of items that is a few pages down. So, I then replace the current array of items with a new 30 items. However, when I have already scrolled down a little (let's say I'm on the 15th cell), and I then jump to a new set of 30 items, the very first cell to show in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell is the 15th cell. indexPath.row = 15... Why is indexPath.row not updating to 0? Is there a way to reset indexPath.row to 0 when I fill my array with a new set of items and call reloadData()?
Apparently, manually setting the indexPath is not possible. Instead, I just scroll the tableView to the top to ensure that it starts at the very first element.
tableView.scroll(to: .top, animated: false)

How to prevent cells from indenting when using custom swipe actions in edit mode?

I'm trying to implement two cells, one that can be deleted by swiping, and one that can't be deleted, but has swipe actions the other way.
This is what it looks like now:
I don't want the middle cell to be indented when clicking Edit.
Without any custom code, all cells would show this red circle and get a delete-button, but I have added a few lines of code to prevent that.
This code will set all cells without a leading or trailing swipe action to not become editable, which explains the bottom cell in the gif. I have to include the leading swipe actions to this, because if the cell isn't editable, then I can't swipe at all.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
let vm = sections[indexPath.section].viewModels[indexPath.row]
return vm.leadingSwipeActions != nil || vm.trailingSwipeActions != nil
}
The following code will prevent the middle cell from showing the red circle.
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return sections[indexPath.section].viewModels[indexPath.row].trailingSwipeActions == nil ? .none : .delete
}
If I don't pass .none for the cell with leading actions, then it would show the red circle AND it would even show a default deletion-swipe. I don't want the ability to delete this row!
How can I prevent the middle cell from indenting, while still retaining the ability to swipe it?
This method seems to work! tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool
https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614873-tableview?language=objc

UITableView jumps up when I insert rows above the indexPaths that is on the screen

I am trying to add some rows in my table view. when inserting rows are above the rows which are on the screen, the table view jumps up. I want my table view to stay in the position it is already in when I insert rows above. Keep in mind: tableView jump to indexPath that it was showing but after adding rows above, bottom rows indexPaths changes and the new n'th indexPath is something else.
This is unfortunately not as easy task as one would think. Table view jumps when you add a cell on top because the offset is persisted and cells updated. So in a sense it is not the table view that jumps, cells jump since you added a new one on top which makes sense. What you want to do is for your table view to jump with the added cell.
I hope you have fixed or computed row heights because with automatic dimensions things can complicate quite a bit. It is important to have the same estimated height as actual height for row. In my case I just used:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72.0
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 72.0
}
Then for testing purposes I add a new cell on top whenever any of the cells is pressed:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var offset = tableView.contentOffset.y
cellCount += 1
tableView.reloadData()
let paths = [IndexPath(row: 0, section: 0)]
paths.forEach { path in
offset += self.tableView(tableView, heightForRowAt: path)
}
DispatchQueue.main.async {
tableView.setContentOffset(CGPoint(x: 0.0, y: offset), animated: false)
}
}
So I save what the current offset of the table view is. Then I modify the data source (My data source is just showing number of cells). Then simply reload the table view.
I grab all the index paths that have been added and I modify the offset by adding the expected height of every added cell.
At the end I apply the new content offset. And it is important to do that in the next run loop which is easies done by dispatching it asynchronously on main queue.
As for automatic dimensions.
I would not go there but it should be important to have size cache.
private var sizeCache: [IndexPath: CGFloat] = [IndexPath: CGFloat]()
Then you need to fill the size cache when cell disappears:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
sizeCache[indexPath] = cell.frame.size.height
}
And change the estimated height:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return sizeCache[indexPath] ?? 50.0
}
Also when modifying your offset you need to use estimated height:
paths.forEach { path in
offset += self.tableView(tableView, estimatedHeightForRowAt: path)
}
This worked for my case but automatic dimensions are sometimes tricky so good luck with them.

How can I add a label to a UITableViewCell on touch?

I have a UITableView and I'm displaying UITableViewCells.
When I click on a cell, I want to add a label to that cell, on a new row within the cell.
I'm updating my data structure correctly, I just don't know how to add the label to the subview.
This is where I add the row to my data structure:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
itemdata[indexPath.section].items[indexPath.row - 1].rows.append(row(size: 0))
// need to call below method somehow to update views
}
This is where I think I need to add the label to the cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemcell") as! itemcell
cell.lblItemName?.text = itemdata[indexPath.section].items[indexPath.row - 1].name
if(itemdata[indexPath.section].items[indexPath.row - 1].rows.count > 0){
// dynamically add new rows to the cell in a loop
// each row is a label on a new line
}
return cell
}
Here is what the cell looks like before click:
After touching, it should look like this:
After touching again, it should look something like this:
Dynamically adding new rows is what I don't know how to do.
If possible, I'd like to be able to visually design a row (with a label, buttons, etc) in a similar way that cells can be prototyped, and then add such a row to the cell. Perhaps by having an empty array of such rows within the cell which can then by increased on touch.
I don't want to do it by putting another UITableView inside the cell with more UITableViewCells inside that because I don't need the view to be scrollable and I don't want the lines between cells.

Tableview is bouncing when reloadRows(at: [indexPath], with: .fade) is called from did select row at index path

I am having this weird issue. when I scroll tableview and update some cell it bounce equivalent to the amount first cell scrolled.
I am having one view controller with tableview in it and based on user select some rows i'm updating only that row using below code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = indexPath.row
print(index)
let service = services[indexPath.row]
if service.isEnabled {
service.isEnabled = false
}
else {
service.isEnabled = true
}
let indexPath = IndexPath(item: index, section: 0)
serviceTableView.reloadRows(at: [indexPath], with: .fade)
}
this is working fine if I don't scroll the table view but if i scroll table view and select some cell its bounce the entire tableview
Things already tried
Removing all constraints of table view, it still bounce
Removing all config methods from cellForRowAt indexPath
Different simulators
also I am not doing anything in section view.
PS: What I observe is that it only happens when first cell is partially visible/hidden.

Resources