Wait for UITableView to finish insert - swift

I'm trying to insert a new row, and immediately make it the first responder.
Using .insertRows() seems to execute asynchronously, even with .none. for the animation. That means that when I call cellForRow(at:), it returns nil.
What would be the best way to wait for the insert to finish, before calling becomeFirstResponder?
This does not work:
self.tableView.insertRows(at: [newIndex], with: .none)
self.tableView.cellForRow(at: newIndex)?.becomeFirstResponder() //Returns nil

Put the insertRows(at:with:) call between a beginUpdates() and endUpdates() calls:
self.tableView.beginUpdates()
self.tableView.insertRows(at: [newIndex], with: .none)
self.tableView.endUpdates()
self.tableView.cellForRow(at: newIndex)?.becomeFirstResponder()
Anyway I'm not so fond of letting as first responder a cell in this way: what if it's not visible?

Why do you have to use .insertRaws()? I mean, why don't you just call reloadData() after adding new item to array?
Note: I updated the code and tested in Xcode. Thanks vadian and Will.
array.append(newItem)
tableView.reloadData() // It is required.
let newIndex = IndexPath(row: array.count - 1, section: 0)
let cell = tableView.cellForRow(at: newIndex)!
cell.becomeFirstResponder()
// If animation is needed
let transition = CATransition()
transition.type = CATransitionType.reveal
cell.layer.add(transition, forKey: nil)

Related

TableView cell: Constraint change conflicts tableView.endUpdates()

I have a messaging application written in Swift.
It does have message bubbles: if the message is longer than 200 chars it is being shortened.
Whenever the user clicks on a message it gets selected:
If the message was shortened, I replace the text with the original long text: Therefore I need to call tableView.beginUpdates() and tableView.endUpdates()
Plus I have to change the timeLabel's height constraint with UIView.animate()
But the two seems to conflict each other, and makes a weird animation: (watch the end)
https://youtu.be/QLGtUg1AmFw
Code:
func selectWorldMessage(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? WorldMessageCell {
cell.messageLabel.text = data[indexPath.row].originalMessage
self.lastContentOffsetY = self.tableView.contentOffset.y
self.tableView.beginUpdates()
UIView.animate(withDuration: duration) {
cell.timeLabelHeightConstraint.constant = 18
cell.layoutIfNeeded()
}
self.tableView.endUpdates()
self.lastContentOffsetY = nil
cell.layoutIfNeeded()
WorldMessageIdsStore.shared.nearby.saveCellHeight(indexPath: indexPath, height: cell.frame.size.height, expanded : true, depending: indexPath.section == 1 ? true : false )
}
}
#objc func deselectSelectedWorldMessage(){
if (currentSelectedIndexPath == nil){
return
}
let indexPath = currentSelectedIndexPath!
tableView.deselectRow(at: indexPath, animated: false)
if let cell = tableView.cellForRow(at: indexPath) as? WorldMessageCell {
cell.messageLabel.text = data[indexPath.row].shortenedMessage
self.lastContentOffsetY = self.tableView.contentOffset.y
self.tableView.beginUpdates()
UIView.animate(withDuration: duration) {
cell.timeLabelHeightConstraint.constant = 0
cell.layoutIfNeeded()
}
self.tableView.endUpdates()
self.lastContentOffsetY = nil
cell.layoutIfNeeded()
}
currentSelectedIndexPath = nil
}
Is there a way to animate cell height change & constraint change in the same time?
I can not place self.tableView.beginUpdates() and self.tableView.endUpdates() in the UIView.animate(){ ... } part, because it would cause the new appearing rows to flicker.
.
.
.
UPDATE 1
So If I palce the self.tableView.beginUpdates() and self.tableView.endUpdates() inside the UIView.animate(){ ... }, then the animation works fine. But as I mentioned it causes flicker when new rows are appearing.
Video:
https://youtu.be/8Sex3DoESkQ
UPDATE 2 !!
So If I set the UIview.animate's duration to 0.3 everything works fine. I don't really understand why.
This animation can be sticky.
I think your main problem here is bubble shrinking more then it should. If you run your animation in slow animation mode (By the way, the simulator has this option, very useful for debugging) you will notice:
To sort this out, you can make your label vertical content compression resistants
higher, and check label has top and bottom constraints with Hi priority too, so it would not get cut this way.
https://medium.com/#abhimuralidharan/ios-content-hugging-and-content-compression-resistance-priorities-476fb5828ef
Try using the following code to overcome flicking problem
tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.middle, animated: true)
Alternative solution
try using this handsome method to update your cell with animation instead of beginUpdates() and endUpdates() methods
tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.bottom )

scroll back to the previous collectionview cell

I am using the code below to scroll to the next cell which works perfectly but how do I scroll back to the previous cell?
let cellItems = CollectionView.indexPathsForVisibleItems
CollectionView.scrollToItem(at: cellItems.max()!, at: .centeredVertically, animated: true)
First of all, the indexPathsForVisibleItems method does not guarantee order. You need to sort it firstly:
let sortedIndexes = collectionView.indexPathsForVisibleItems.sorted(<)
If you want to scroll to the previous cell, you need to store previous cell IndexPath somewhere in your class:
var previousCellIndexPath: IndexPath?
And than you can scroll to this cell:
func scrollToPreviousCell() {
guard let previousCellIndexPath = self.previousCellIndexPath else { return }
collectionView.scrollToItem(at: previousIndexPath, at: centeredVertically, animated: true)
}

UITableView - Deleting rows with animation

I'm trying to delete row from tableview using animation however in most cases it gets stuck. 1 in 15 tries will result in this animation being played. This is what my delete action looks like:
func contextualDeleteAction(forRowAtIndexPath indexPath: IndexPath) -> UIContextualAction {
let action = UIContextualAction(style: .destructive,
title: "Delete") { (contextAction: UIContextualAction, sourceView: UIView, completionHandler: (Bool) -> Void) in
Model().context.delete(notesArray[indexPath.row])
notesArray.remove(at: indexPath.row)
self.myTableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.bottom)
Model().saveItems()
}
action.title = "Delete"
return action
}
This is what it looks like when it gets stucked when swiping.
When pressing delete button instead.
I've also tried to use tableView.beginUpdate() and tableView.endUpdate() but didn't get different result.
The problem is that you have forgotten your primary duty in this method: you must call the completion handler!
Model().context.delete(notesArray[indexPath.row])
notesArray.remove(at: indexPath.row)
self.myTableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.bottom)
Model().saveItems()
completionHandler(true) // <-- look!
In Swift 4, you can perform a delete animation just like the iPhone Messages animation.
Use this:
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)

Swift Error convertPoint(CGPoint, to: UITableView)

So I have a custom tableview and in that tableview I have a button, an image, and 2 labels. Each of these items gets filled with json data from a mysql server using php. I converted my project from Objective-C to Swift and in doing so I got this error. This is one of the most important codes in my project because since the user clicks the follow button it moves the array to other arrays, and in doing that it gives a special row number to the cell so all the images, labels, and the button knows which is which to display.
I tried switching it so .convert() but just errors so I left it how it was orginally.
The code for the button
// Follow Button
#IBAction func followButtonClick(_ sender: Any) {
// Adding row to tag
var buttonPosition = (sender as AnyObject).convertPoint(CGPoint.zero, to: self.myTableView)
var indexPath = self.myTableView.indexPathForRow(at: buttonPosition)!
// Creating an action per tag
if indexPath != nil {
// Showing Status Labels
var cell = self.myTableView.cellForRow(atIndexPath: indexPath)!
cell.firstStatusLabel.isHidden = false
cell.secondStatusLabel.isHidden = false
// Change Follow to Following
(sender as AnyObject).setImage(UIImage(named: "follow.png")!, for: .normal)
cell.followButton.isHidden = true
cell.followedButton.isHidden = false
self.myTableView.beginUpdates()
// ----- Inserting Cell to Section 0 -----
followedArray.insert(testArray[indexPath.row], at: 0)
myTableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
// ----- Removing Cell from Section 1 -----
testArray.remove(at: indexPath.row)
var rowToRemove = indexPath.row
self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 1)], with: true)
self.myTableView.endUpdates()
}
}
The Error
Errors with new code
Pictures so its easier to read
convertPoint is changed in Swift 3 like convert(_:to:).
var buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: self.myTableView)
Check Apple Documentation for more details.
Edit:
For your first warning instead of usinf indexPath != nil you need to use if let and for that label error you need to cast your cell to customCell that you are using.
var buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: self.myTableView)
if let indexPath = self.tblSubscriber.indexPathForRow(at: buttonPosition) {
//Cast your `UITableViewCell` to CustomCell that you have used
var cell = self.myTableView.cellForRow(atIndexPath: indexPath) as! CustomCell
}
For your deleteRows error you need to specify the UITableViewRowAnimation not true or false.
self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 1)], with: .automatic)
For more detail about UITableViewRowAnimation check documentation.
If you are trying this code in swift3 then the above method is updated to
func convert(_ point: CGPoint,
from view: UIView?) -> CGPoint you can use it like this
var buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: self.myTableView)
I have checked it in Playground and it is working.

cannot convert value of type 'nsindexpath?' to expected argument type '[nsindexpath]'

I want to delete a row from my tableView using deleteRowsAtIndexPaths, the codes I use to obtain the NSIndexPath of the specific row are as follow:
let pointInTable: CGPoint = sender.convertPoint(sender.bounds.origin, toView: self.tableView)
let cellIndexPath = self.tableView.indexPathForRowAtPoint(pointInTable)
But then I got the error as suggested in the title when I put down cellIndexPath for deleteRowsAtIndexPaths. I get a sense that it might be about an array type of NSIndexPath but I'm not sure exactly how I should construct my codes. Please help thanks!
Ran into the same problem in this instance:
tableView.reloadRows(at: indexPath, with: .fade)
resolved it like this:
tableView.reloadRows(at: [indexPath], with: .fade)