I have an app that has a table view. Every minute or so I want the first row of the UITableView to disappear. It would be nice to have it animate off. I am assuming I need to use something like this:
let myNSIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
mainTableView.deleteRowsAtIndexPaths(myNSIndexPath, withRowAnimation:UITableViewRowAnimation.Fade)
This doesn't work. I'm not sure exactly how to use IndexPath correctly. My table only has one section and I always want the cell at Index 0 to be removed. What must to do to get this working correctly?
One way
Use removeAtIndex
yourarray.removeAtIndex(indexPath.row)
And don't forget to reload the table view
tableView.reloadData()
Second Way
yourIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
yourtable.beginUpdates() //if you are performing more than one operation use this
yourarray.removeObjectAtIndex(myNSIndexPath.row)
yourtable.deleteRowsAtIndexPaths(NSArray(object: yourIndexPath), withRowAnimation: UITableViewRowAnimation.Left)
yourtable.endUpdates() //if you are performing more than one operation use this
Took help from
#Rmaddy
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/
You can delete the first cell like this.
One way
let myNSIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
self.tableView.beginUpdates()
self.arrayData.removeObjectAtIndex(myNSIndexPath.row) // also remove an array object if exists.
self.tableView.deleteRowsAtIndexPaths(NSArray(object: myNSIndexPath), withRowAnimation: UITableViewRowAnimation.Left)
self.tableView.endUpdates()
Second way
yourArr.removeAtIndex(0)
self.tableView.reloadData()
in SWIFT 4
You can delete the row like this.
let indexPathRow=sender.tag
let indexPath = IndexPath(row: indexPathRow, section: 0)
arrayList.remove(at: indexPathRow)
mainTableView.deleteRows(at: [indexPath], with: .none)
Related
I have a tableView where the user can tap on a button inside the cell to delete it. That button is connected with this delegate-function:
extension WishlistViewController: DeleteWishDelegate {
func deleteWish(_ idx: IndexPath){
// remove the wish from the user's currently selected wishlist
wishList.wishes.remove(at: idx.row)
// set the updated data as the data for the table view
theTableView.wishData.remove(at: idx.row)
self.theTableView.tableView.deleteRows(at: [idx], with: .right)
print("deleted")
}
}
Here is how I call the callback (after an animation is finished):
#objc func checkButtonTapped(){
self.successAnimation.isHidden = false
self.successAnimation.play { (completion) in
if completion {
self.deleteWishCallback?()
}
}
}
And this callback is handled in cellForRowAt and passes the indexPath:
cell.deleteWishCallback = {
self.deleteWishDelegate?.deleteWish(indexPath)
}
It works fine until the user clicks multiple buttons right after another as I get a IndexOutOfBounds-Error. What I was thinking of is to store all the incoming indexes in some sort of list and delete them one after another but each index changes as soon as another cell below itself is deleted. What is the best way to get this done?
How are you sending the indexPath to delete in the delegate, can you show code?
You might have retain cycle on your deleted cell, and the index is not valid.
Edit: Solution
You must pass the cell in your closure self.deleteWishCallback?(cell) and then get the actual index path like this
cell.deleteWishCallback = { deletedCell in
let indexPath = tableView.indexPath(for: deletedCell)
wishList.wishes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .right)
self.deleteWishDelegate?.deleteWish(indexPath)
}
Instead of self.theTableView.tableView.deleteRows(at: [idx], with: .right) just simply reloadData. (self.theTableView.reloadData())
If you set the new datasource for your tableView, you should reload cells.
Hi and hope you'll help.
In every cell I have a text field which responds directly from table view. I save data via closure in cell.
cell.tableViewClosure = {[unowned self] in
self.tableView.beginUpdates()
// Singleton
Strings.shared.strings[indexPath.row] = cell.textField.text!
self.tableView.reloadRows(at: [indexPath], with: .automatic)
self.tableView.endUpdates()
}
By instance i delete first cell, data array count and number of rows are equal after in log, but if I try to edit text field in last cell and tap return, app crashes with index out of range.
If log during deletion - it is 5 strings in array, but indexPath.row for this proper cell is 5.
But if I reload data in deletion - everything fine with edit last cell but UI is not smooth.
Your problem is that indexPath.row is captured when you assign the closure, for your last cell, it will have the value "4". If you subsequently delete an earlier cell, the last element in your array is now "3", but the captured value doesn't change; it is still 4.
When you then edit the text field in the last cell, the captured value 4 is used to access your model and you get an array bounds exception.
You can use indexPath(for:) to determine the appropriate index path when the closure executes:
cell.tableViewClosure = {[unowned self] in
if let indexPath = self.tableView.indexPath(for: cell) {
self.tableView.beginUpdates()
Strings.shared.strings[indexPath.row] = cell.textField.text!
self.tableView.reloadRows(at: [indexPath], with: .automatic)
self.tableView.endUpdates()
}
}
indexPath.row starts with 0. So the range will be 0-4. If you're using 5 strings in an array and using a .count - that will return a 5.
Just do indexPath.row - 1 to adjust for the off by 1 (index out of range).
Use tableView.indexPath(for: cell) to get the current index
I'm trying to track down a difficult crash in an app.
I have some code which effectively does this:
if let indexPath = self.tableView.indexPath(for: myTableViewCell) {
// .. update some state to show a different view in the cell ..
self.tableView.reloadData()
// show nice fade out of the cell
self.friendRequests.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
The concern is that calling reloadData() somehow makes the indexPath I just retrieved invalid so the app crashes when it tries to delete the cell at that indexPath. Is that possible?
Edit:
The user interaction is this:
User taps a button [Add Friend] inside of table view cell <-- indexPath retrieved here
Change the button to [Added] to show the tap was received. <-- reloadData called here
Fade the cell out after a short delay (0.5s). <-- delete called here with indexPath from #1
I can change my code to not call reloadData and instead just update the view of the cell. Is that advisable? What could happen if I don't do that?
Personally, I'd just reload the button in question with reloadRows(at:with:), rather than the whole table view. Not only is this more efficient, but it will avoid jarring scrolling of the list if you're not already scrolled to the top of the list.
I'd then defer the deleteRows(at:with:) animation by some small fraction of time. I personally think 0.5 seconds is too long because a user may proceed to tap on another row and they can easily get the a row other than what they intended if they're unlucky enough to tap during the wrong time during the animation. You want the delay just long enough so they get positive confirmation on what they tapped on, but not long enough to yield a confusing UX.
Anyway, you end up with something like:
func didTapAddButton(in cell: FriendCell) {
guard let indexPath = tableView.indexPath(for: cell), friendsToAdd[indexPath.row].state == .notAdded else {
return
}
// change the button
friendsToAdd[indexPath.row].state = .added
tableView.reloadRows(at: [indexPath], with: .none)
// save reference to friend that you added
let addedFriend = friendsToAdd[indexPath.row]
// later, animate the removal of that row
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
if let row = self.friendsToAdd.index(where: { $0 === addedFriend }) {
self.friendsToAdd.remove(at: row)
self.tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .fade)
}
}
}
(Note, I used === because I was using a reference type. I'd use == with a value type that conforms to Equatable if dealing with value types. But those are implementation details not relevant to your larger question.)
That yields:
Yes, probably what's happening is the table view is invalidating stored index path.
To test whether or not it is the issue try to change data that is represented in the table right before reloadData() is called.
If it is a problem, then you'll need to use an identifier of an object represented by the table cell instead of index path. Modified code will look like this:
func objectIdentifer(at: IndexPath) -> Identifier? {
...
}
func indexPathForObject(_ identifier: Identifier) -> IndexPath? {
...
}
if
let path = self.tableView.indexPath(for: myTableViewCell)
let identifier = objectIdentifier(at: path) {
...
self.tableView.reloadData()
...
if let newPath = indexPathForObject(identifier) {
self.friendRequests.removeObject(identifier)
self.tableView.deleteRows(at: [newPath], with: .fade)
}
}
I have a table view which received data from a real-time database. These data are added from the bottom of the table view and so on this table view has to scroll down itself to show new data.
I've found a method to do it however I'm not satisfied because the scroll always start from the top of the list. Not very beautiful.
Here is the code of this method :
func tableViewScrollToBottom(animated: Bool) {
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
let numberOfSections = self.clientTable.numberOfSections
let numberOfRows = self.clientTable.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
self.clientTable.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}
})
}`
Is there a way to modify this method in order to scroll only from the previous position ?
The issue is probably how the rows were inserted into the table. For example, if you add rows to the end using something like this, you get a very smooth UI:
#IBAction func didTapAddButton(sender: AnyObject) {
let count = objects.count
var indexPaths = [NSIndexPath]()
// add two rows to my model that `UITableViewDataSource` methods reference;
// also build array of new `NSIndexPath` references
for row in count ..< count + 2 {
objects.append("New row \(row)")
indexPaths.append(NSIndexPath(forRow: row, inSection: 0))
}
// now insert and scroll
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None)
tableView.scrollToRowAtIndexPath(indexPaths.last!, atScrollPosition: .Bottom, animated: true)
}
Note, I don't reload the table, but rather call insertRowsAtIndexPaths. And I turned off the animation because I know they're off screen, and I'll then scroll to that row.
I am trying to make an iOS very similar to the game Lifeline
The game looks as though it's just a table view that has a row inserted after a delay.
I have managed to insert rows like this
for obj in array {
self.dialogue.append(obj)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}}
*Note the self.dialogue is my data source for my table view.
However I do not know how to add a delay in between adding a new row, for example
-> Insert row -> Wait 3 seconds-> Insert another row
How can I do this like it's done in the game?
you may implement separate method or block for inserting the rows and call it from inside your for loop with delay. e.g.
var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
for obj in array {
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.dialogue.append(obj)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
})
}}