I am trying to load a cell into a certain section of my tableView with an animation, but It is crashing giving me this error 'NSInternalInconsistencyException', reason: 'attempt to delete row 1 from section 0 which only contains 1 rows before the update'
from this code:
let indexPath = IndexPath(item: session[section].count - 1, section: section)
tableView.reloadRows(at: [indexPath], with: .automatic)
this code is triggered after a user clicks a button to add another cell to the end of that section. All I want to do is add that cell to the end of the section with an animation, how can I do that?
#objc func addSetPressed(_ sender: UIButton){
let section = sender.tag
session[section].append(ExerciseSet(setID: "", sessionID: "", exerciseID: "", setIndex: section + 1, weight: session[section][0].weight, reps: session[section][0].reps))
//tableView.reloadData()
let indexPath = IndexPath(item: session[section].count - 1, section: section)
tableView.reloadRows(at: [indexPath], with: .automatic)
setTableViewHeight()
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
}
Related
I am working on quite complex updates on a UITableView. Multiple rows can be moved, deleted, inserted and changed at the same time. What is the correct way to handle these changes within the same tableView.beginUpdates()/ tableView.endUpdates()block?
Example:
Data before update ==> Data after update
================== =================
0: zero 0: zero
1: one 1: 555 (updated element five)
2: two 2: three
3: three 3: 222 (updated element two)
4: four
5: five
==> Elements 1 and 4 are deleted
==> Elements 2 and 5 are updated (content changes) and they switch their position
This can be done using a simple ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var button: UIButton!
var rowTitles = ["zero", "one", "two", "three", "four", "five"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowTitles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = rowTitles[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
#IBAction func click() {
tableView.beginUpdates()
// "zero", "one", "555", "three", "four", "222"
rowTitles[2] = "555"
rowTitles[5] = "222"
// "zero", "555", "three", "four", "222"
rowTitles.remove(at: 1)
// "zero", "555", "three", "222"
rowTitles.remove(at: 3)
// Delete "one" (original index = 1)
tableView.deleteRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
// Delete "four" (original index = 4)
tableView.deleteRows(at: [IndexPath(row: 4, section: 0)], with: .automatic)
// Move "two" and "five", use new old index for source and new index for destination
tableView.moveRow(at: IndexPath(row: 2, section: 0), to: IndexPath(row: 3, section: 0))
tableView.moveRow(at: IndexPath(row: 5, section: 0), to: IndexPath(row: 1, section: 0))
// CRASH
//tableView.reloadRows(at: [IndexPath(row: 5, section: 0)], with: .automatic)
tableView.endUpdates()
}
}
Everything works fine, except moving and reloading the rows 2 and 5 at the same time. I reloadRows(...) is not used, the code runs but results in a list zero, five, three, two. So, the elements in the correct order, but the rows have not been updated to 555 and 222.
When using reloadRows(...) with the original indexes 2 and 5 the app crashes because move+delete have been called for the same indexes (it seems that reload is internally handled as delete+add).
Using reloadRows(...) AFTER endUpdates() delivers the correct result. However, I wonder if this is the correct way to go. Regarding the Apple Docs reloadRows(...) should be called within beginUpdates ... endUpdates.
So, what is the correct way to solve this?
Not entirely sure what visual effect you're going for, but...
You probably want to reload the table view and then remove / rearrange the rows.
Try it like this:
#IBAction func click() {
rowTitles[5] = "555"
rowTitles[2] = "222"
let p1 = IndexPath(row: 2, section: 0)
let p2 = IndexPath(row: 5, section: 0)
self.tableView.reloadRows(at: [p1, p2], with: .automatic)
rowTitles.remove(at: 1)
rowTitles.remove(at: 3)
// need to update order in array
rowTitles[1] = "555"
rowTitles[3] = "222"
tableView.performBatchUpdates({
// Delete "one" (original index = 1)
tableView.deleteRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
// Delete "four" (original index = 4)
tableView.deleteRows(at: [IndexPath(row: 4, section: 0)], with: .automatic)
// Move "two" and "five", use new old index for source and new index for destination
tableView.moveRow(at: IndexPath(row: 2, section: 0), to: IndexPath(row: 3, section: 0))
tableView.moveRow(at: IndexPath(row: 5, section: 0), to: IndexPath(row: 1, section: 0))
}, completion: { _ in
// if you want to do something on completion...
})
}
End result order is:
zero
555
three
222
I was wondering if it is posible to reload a tableView without reloading the first cell/row in a simple way? If so, how would you do it?
let itemArray:[Int] = []
for i in 1..<itemArray.count {
let indexPath = IndexPath(item: i, section: 0)
tableView.reloadRows(at: [indexPath], with: .fade)
}
If your table is not grouped currently ,
The best way to do this is add your 1st cell to one section And all other cells to another section. Then you can easily reload all cell excluding 1st cell using
//section 0 - your 1st cell
//section 1 - other cells
tableView.reloadSections([1], with: .none)
Just use the reloadRows method to reload your tableView.
var dataSource: [SomeData] = []
func reloadAllButFirstRow() {
guard dataSource.count > 0 else { return }
tableView.reloadRows(
at: (1...dataSource.count).map { IndexPath(row: $0, section: 0) },
with: .fade
)
}
I want to delete a row from a table view, but it isn't working :/
This next code block is triggered from a custom button (not from an internal Apple-Table-Edit).
self.tableView.beginUpdates()
self.sections[indexPath.section].items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
self.tableView.endUpdates()
This is how a section looks like:
class Section: Comparable {
var state: TrackingObject.stateEnum
var items: [TrackingObject]
var expanded: Bool
init(state : TrackingObject.stateEnum, items : [TrackingObject], expanded : Bool){
self.state = state
self.items = items
self.expanded = expanded
}
static func group(trackingObjects: [TrackingObject]) -> [Section] {
let groups = Dictionary(grouping: Model.trackingObjects) { (trackingObject: TrackingObject) -> TrackingObject.stateEnum in
return trackingObject.currentState
}
return groups.map { (state: TrackingObject.stateEnum, trackingObjects: [TrackingObject]) in
return Section(state: state, items: trackingObjects, expanded: state == .active_due ? true : false)
}.sorted()
}
}
Expected: That the row of the given indexPath is removed with an animation as well as removed in the 'data source' (called: self.sections)
Actual Output: Error-Message:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'attempt to insert row 2
into section 0, but there are only 0 rows in section 0 after the
update'
(I was removing the second row in the section 0)
self.sections[indexPath.section].items.remove(at: indexPath.row)
self.tableView.beginUpdates()
DispatchQueue.main.async {
tableView.deleteRows(at: [indexPath], with: .automatic)
self.tableView.endUpdates()
}
Try removing item from your data source before beginUpdates(). You can also just remove item from the data source and reload the table view as you are setting the animation option as automatic, so tableview will perform the preferable animation.
Swift 3
I have a table view with several rows, each row has a button (uses indexPath.row), when the button is clicked it moves the row from Section 1 (testArray) to Section 0 (followedArray). BUT when using the search bar to search through testArray and a row's button is clicked it crashes.
Now when using the search bar, testArray is filtered into a new array called filteredArray. The button is supposed to move the row from filteredArray to followedArray.
Without searching, the button works as intended, it only crashes when using the search bar.
What am I doing wrong?
Here is the ERROR I get:
CustomCellSwift[1473:391841] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3600.5.2/UITableView.m:1315
New Suggestion: Could it have anything to do with it having a conflict with the indexPath.row of testArray and followedArray when filteredArray replaces the table view?
Deleting from UISearchController's filtered search results
The code I'm using:
#IBAction func followButtonClick(_ sender: UIButton!) {
// Adding row to tag
let buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: self.myTableView)
if let indexPath = self.myTableView.indexPathForRow(at: buttonPosition) {
// Change Follow to Following
(sender as UIButton).setImage(UIImage(named: "follow.png")!, for: .normal)
cell.followButton.isHidden = true
cell.followedButton.isHidden = false
// Checking wether to import from testArray or filteredArray to followedArray
if !(searchController.isActive && searchController.searchBar.text != "") {
self.myTableView.beginUpdates()
// ----- Inserting Cell to followedArray -----
followedArray.insert(testArray[indexPath.row], at: 0)
myTableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
// ----- Removing Cell from testArray -----
testArray.remove(at: indexPath.row)
let rowToRemove = indexPath.row
self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 1)], with: .fade)
self.myTableView.endUpdates()
}
else {
self.myTableView.beginUpdates()
// ----- Inserting Cell to followedArray -----
followedArray.insert(filteredArray[indexPath.row], at: 0)
myTableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
// ----- Removing Cell from filteredArray -----
filteredArray.remove(at: indexPath.row)
let rowToRemove = indexPath.row
self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 0)], with: .fade)
self.myTableView.endUpdates()
}
}
}
So I found the answer, I was adding the object to the array using insert at sections method instead of using just insert since I only have 1 section.
let testObject: Test = filteredArray[indexPath.row]
let indexOfObjectInArray = testArray.index(of: testObject)
followedArray.insert(testObject, at: 0)
I'm trying to create a search view controller quite like Tweetbot's, where adding text to the search bar inserts a new section and new rows into the tableView,
like so.
I've tried using the searchBar delegate methods searchBarTextDidBeginEditing and
searchBar(_:, textDidChange:) but my attempts to insert a new section and rows within the method resulted in crashes.
What I tried:
tableView.beginUpdates()
tableView.insertSections(NSIndexSet(index: 0), withRowAnimation: .None)
tableView.insertRowsAtIndexPaths(
[
NSIndexPath(forRow: 0, inSection: 0),
NSIndexPath(forRow: 1, inSection: 0),
NSIndexPath(forRow: 2, inSection: 0)
],
withRowAnimation: .None)
tableView.endUpdates()
The error I got:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Invalid update: invalid number of sections. The number of sections contained in the table
view after the update (3) must be equal to the number of sections contained in the table
view before the update (3), plus or minus the number of sections inserted or deleted
(1 inserted, 0 deleted).'
*** First throw call stack:
(0x181a19900 0x181087f80 0x181a197d0 0x18238c99c 0x18695c724 0x10008b578
0x10008b5e8 0x18696d0a0 0x18679bc48 0x18679bbc4 0x186783880 0x186782bfc
0x18677ef98 0x186875268 0x18696cdfc 0x18696fe60 0x1869c817c 0x18696d02c
0x1867eca24 0x10029004c 0x1867ecea4 0x186873d38 0x186924b84 0x186924038
0x186ce2a18 0x186909158 0x1867967a8 0x186ce4018 0x186755960 0x1867526e4
0x186794618 0x186793c14 0x1867642c4 0x18676258c 0x1819d0efc 0x1819d0990
0x1819ce690 0x1818fd680 0x182e0c088 0x1867cd40c 0x100123594 0x18149e8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
Thanks for your help.
After you call tableView.endUpdates() your table view will call its UITableViewDataSource delegate and it expects that tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int method to return 3. (because you added 3 rows in the first section).
Try something like this:
Declare new property:
var showSearchSelection = false
Search:
tableView.beginUpdates()
tableView.insertSections(NSIndexSet(index: 0), withRowAnimation: .None)
tableView.insertRowsAtIndexPaths(
[
NSIndexPath(forRow: 0, inSection: 0),
NSIndexPath(forRow: 1, inSection: 0),
NSIndexPath(forRow: 2, inSection: 0)
],
withRowAnimation: .None)
showSearchSelection = true
tableView.endUpdates()
UITableViewDataSource delegate:
extension UIViewController : UITableViewDataSource {
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section:
case 0:
return showSearchSelection ? 3 : 0
default:
return 0
}
}