UITableView: How to change the background of overlapped cells when Drag cell? - ios

I have attached a image.
This is the iPhone's default reminder app.
Like this, I want to make a gray background when the drag cell overlap.
Here is what I tried: ;But it looks ugly in the emulator.
var beforeTouch: IndexPath?
func tableView(_ tableView: UITableView,
targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath,
toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if let cell = tableView.cellForRow(at: proposedDestinationIndexPath) {
if (self.beforeTouch != sourceIndexPath)
&& (sourceIndexPath != proposedDestinationIndexPath) {
if self.beforeTouch == nil {
cell.setSelected(true, animated: false)
} else {
cell.setSelected(true, animated: false)
tableView.cellForRow(at: beforeTouch!)?.setSelected(false, animated: false)
}
}
self.beforeTouch = proposedDestinationIndexPath
}
return proposedDestinationIndexPath
}
In this code, I used below method.
func tableView(_:, targetIndexPathForMoveFromRowAt:, toProposedIndexPath:) -> IndexPath
It seems executed every time when drag and can trace the cell by using the IndexPath of the parameter.
Also, I think there will be a performance problem because the 'cell.setSelected()' is used in a tableView function that is continuously called while dragging. I also have the problem of having to store the indexPath of the previous cell in the storage property of the class, so I am looking for another way.
I think there will be a traditional way to this issue.
Can someone help on how I can achieve it?

Related

ScrollToRow does not work when said row is near the bottom of the table view

I have a cell class which implements a textfield delegate. In this delegate I am calling a function to tell the tableview to scroll to a specific row based off an indexPath. This works in most cases but not when the row is at the bottom of the table view. The cell class has a table property which is passed in, in my main controllers cellForRow method. Code below:
extension IR_TextCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
util_textField.addHightlightedBorder(textField)
if let index = table?.indexPath(for: self)
{
scrollDelegate?.scrollToMe(at: index)
}
}
}
func scrollToMe(at index: IndexPath) {
self.tableV.scrollToRow(at: index, at: .middle, animated: false)
}
I have tried wrapping DispatchQueue.main.async around this and adding a deadline but it didn't make a difference.
Do I need to change my tableview's bottom constraint maybe?
My situation is a little different than yours but I had a same issue scrolling to cells that are near the bottom. It might now work for your exact situation but I hope this helps someone who comes across this posting. I suspected that it might be a timing issue so I ended up doing it like below:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == tableView.indexPathsForVisibleRows?.last?.row {
let scrollIndex = 0//set to your predetermined scrolled to index
let cellRect = tableView.rectForRow(at: indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)
if scrollIndex >= indexPath.row && !completelyVisible {
let maxIndex = 10//number of elements in the array - 1
//in case you want a delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self.tableView.scrollToRow(at: IndexPath(row: scrollIndex > maxIndex ? maxIndex : scrollIndex, section: 0), at: .middle, animated: false)
}
}
}
}

UITableView not dequeuing some cells

I've never run into this issue before, but here is what I am currently experiencing:
I have a UITableView in a view controller. Delegate and dataSource are setup properly.
Now I'm running into performance issues after showing/inserting a few cells.
The issue is that the tableview seems to not dequeue some of the cells but rather creates a new one each time (always calls awakeFromNib in the custom class).
The ones with which the dequeuing seems to not work are just simple ones with a label and some with a label, an image and another label.
There is however one case in which dequeuing seems to work. I have a custom cell class that just contains a vertical stack view into which I dynamically add a variable amount of custom buttons.
I have a class that is responsible for setting up the cells. It has a method for each of the cell classes I use.
func initialModeratorCell(at indexPath: IndexPath, with message: Message, in tableView: UITableView) -> OnboardingInitialModeratorCell{
guard let cell = tableView.dequeueReusableCell(withIdentifier: initalModeratorCellIndentifier) as? OnboardingInitialModeratorCell else{
fatalError("No cell with \(initalModeratorCellIndentifier) identifier")
}
if indexPath.row != 0{
cell.moderatorImage.isHidden = true
}
let attributedMessage = attributedString(for: message, with: paragraphStyleFor(message: message))
cell.mesageText.attributedText = attributedMessage
cell.dateLabel.text = message.userType.name()
cell.mesageText.sizeToFit()
cell.dateLabel.sizeToFit()
return cell
}
All those methods look similar to this. This is one that doesn't seem to get reused.
This here is the one that does get reused (awakeFromNib only called once):
func componentCell(from item: ConversationItem, in tableView: UITableView, with owner: OnboardingComponentCellDelegate) -> OnboardingComponentCell{
guard let comp = item as? Component else{
fatalError("Type of item is not Component, but should be!")
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: componentButtonsCellIdentifier) as? OnboardingComponentCell else{
fatalError("No cell with \(componentButtonsCellIdentifier) identifier")
}
cell.componentStack.translatesAutoresizingMaskIntoConstraints = false
setupComponentCell(cell, for: comp, owner: owner)
return cell;
}
This is setupCell():
setupComponentCell(_ cell: OnboardingComponentCell, for comp: Component, owner: OnboardingComponentCellDelegate){
cell.reset()
cell.component = comp
OnboardingComponentManager.createComponent(for: comp, in: cell, delegate: owner)
}
The cell.reset() method looks like this:
func reset(){
component = nil
delegate = nil
componentStack.removeAll() //removeAll is in an extension on UIStackView
}
The call OnboardingComponentManager.createComponent() just populates the stackview with the correct buttons for said component.
The methods above (componentCell(from:) and initialModeratorCell(at:) are called from the public method onboardingCellForItem(at indexPath: IndexPath, ..):
static func onboardingCellForItem(at indexPath: IndexPath, with displayedItems: Items, in tableView: Table, typingDelegate: IndicatorDelegate, buttonOwner: CellDelegate, onboardingType: ConvType) -> UITableViewCell{
let item = displayedItems[indexPath.row]
if item.type == .message{
return messageCell(from: item, at: indexPath, in: tableView, with: displayedItems)
}else if item.type == .component{
return componentCell(from: item, in: tableView, with: buttonOwner)
}else if item.type == .typingIndicator{
return typingIndicatorCell(with: typingDelegate, in: tableView)
}
return UITableViewCell()
}
This method then is called from the dataSource method cellForRowAtIndexPath like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return OnboardingCellManager.onboardingCellForItem(at: indexPath, with: displayedItems, in: tableView, typingDelegate: self, buttonOwner: self, onboardingType: .onboarding)
}
The cells were prototyped in a storyboard in the tableView (as prototype cells).
I'm somewhat hitting a wall here. I've never run into this issue before and I can't seem to find the reason as to why it happens.
This performance degradation is most notable on older devices (iPhone 5, etc).

Why the order of the UITableView Cells changed when I changed the size of the cell and reload?

I got a tableView to display some information like following image.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if cellStates[indexPath.section] == .unfold {
cellStates[indexPath.section] = .fold
} else {
cellStates[indexPath.section] = .unfold
}
tableView.reloadData()
}
The cell would be expanded under fold state, like following image.
If you touch it again, it should be folded again, like image 1.
Then here comes the problem.
As what we can see in image 2, there are three cells out side the screen.
No1, No2, No5 outside. And No3, No4 Inside. No3 is the cell we just touched.
When I touch No3 cell again to fold it, the order changed to No3, No4, No1, No2, No5.
It seems the cells that inside screen have been lifted to the top.
I'm confused in this, what's really going on? How to avoid?
The height of the cell depends on the unchanged data source, so it will mess things up.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "WBCell") as! WBTableViewCell
cell.controller = self
let state = cellStates[indexPath.section]
if state == .initial {
let data = dataSource[index]
cell.initial(data: data)
cellStates[indexPath.section] = .fold
} else if state == .fold {
cell.switchTo(unFolded: false) // Update autolayout constrains via SnapKit
} else {
cell.switchTo(unFolded: true)
}
return cell
}

When calling prepareForReuse() in CustomTableViewCell.swift, UITableViewCells become unresponsive after several deletions from UITableView (Swift 3)

I have an app that pulls objects from Firebase, then displays them in a table. I've noticed that if I delete 5 entries (this is about when I get to the reused cells that were deleted), I can't delete any more (red delete button is unresponsive) & can't even select the cells. This behavior stops when I comment out override func prepareForReuse() in the TableViewCell.swift controller. Why???
The rest of the app functions normally while the cells are just unresponsive. Weirdly, if I hold one finger on a cell and tap the cell with another finger, I can select the cell. Then, if I hold a finger on the cell and tap the delete button, that cell starts acting normally again. What is happening here??? Here is my code for the table & cells:
In CustomTableViewCell.swift >>
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
cellImage.image = nil
}
In ViewController.swift >>
override func viewDidLoad() {
super.viewDidLoad()
loadUserThings()
}
func loadUserThings() {
ref.child("xxx").child(user!.uid).child("yyy").queryOrdered(byChild: "aaa").observe(.value, with: { (snapshot) in
// A CHANGE WAS DETECTED. RELOAD DATA.
self.arr = []
for tempThing in snapshot.children {
let thing = Thing(snapshot: tempThing as! DataSnapshot)
self.arr.append(thing)
}
self.tableView.reloadData()
}) { (error) in
print(error)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellData = arr[indexPath.row]
...
// SET TEXT VALUES OF LABELS IN THE CELL
...
// Setting image to nil in CustomTableViewCell
let imgRef = storageRef.child(cellData.imgPath)
let activityIndicator = MDCActivityIndicator()
// Set up activity indicator
cell.cellImage.sd_setImage(with: imgRef, placeholderImage: nil, completion: { (image, error, cacheType, ref) in
activityIndicator.stopAnimating()
delay(time: 0.2, function: {
UIView.animate(withDuration: 0.3, animations: {
cell.cellImage.alpha = 1
})
})
})
if cell.cellImage.image == nil {
cell.cellImage.alpha = 0
}
// Seems like sd_setImage doesn't always call completion block if the image is loaded quickly, so we need to stop the loader before a bunch of activity indicators build up
delay(time: 0.2) {
if cell.cellImage.image != nil {
activityIndicator.stopAnimating()
cell.cellImage.alpha = 1
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// instantly deselect row to allow normal selection of other rows
tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: false)
selectedObjectIndex = indexPath.row
self.performSegue(withIdentifier: "customSegue", sender: self)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("should delete")
let row = indexPath.row
let objectToDelete = userObjects[row]
userObjects.remove(at: row)
ref.child("users/\(user!.uid)/objects/\(objectToDelete.nickname!)").removeValue()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if (self.tableView.isEditing) {
return UITableViewCellEditingStyle.delete
}
return UITableViewCellEditingStyle.none
}
A few things. For performance reasons, you should only use prepareForReuse to reset attributes that are related to the appearance of the cell and not content (like images and text). Set content like text and images in cellForRowAtIndexPath delegate of your tableView and reset cell appearance attributes like alpha, editing, and selection state in prepareForReuse. I am not sure why it continues to behave badly when you comment out that line and leave prepareForReuse empty because so long as you are using a custom table view cell an empty prepareForReuse should not affect performance. I can only assume it has something to do with you not invoking the superclass implementation of prepareForReuse, which is required by Apple according to the docs:
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
super.prepareForReuse()
}
The prepareForReuse method is only ever intended to do minor cleanup for your custom cell.

swift determine if a certain row disappear from view

I've have been sniffing around to find the solution but up until now its fruitless. What I want to do is to determine if a certain cell disappeared from screen.
Here's the code i have been trying:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let indexPathWatchCell = NSIndexPath(forRow: 4, inSection: 0)
if ((tableView.indexPathsForVisibleRows?.contains(indexPathWatchCell)) == nil){
NSLog("it disappeared!!")
}
}
Unfortunately its not working, any ideas?
EDIT:
for clarity, what I want to accomplish is set the size of the cell to CGFLOAT(44) when it disappeared in view
contains method return Boolean value if the object contains other wise return false, so you need to check for that not for nil so change your code like this.
if (tableView.indexPathsForVisibleRows?.contains(indexPathWatchCell)){
NSLog("it not disappeared!!")
}
else {
NSLog("it's disappeared!!")
}
Or if you just want to know for "disappeared" than you can use !(not) with if
if (!tableView.indexPathsForVisibleRows?.contains(indexPathWatchCell)){
NSLog("it disappeared!!")
}

Resources