I'm having a huge issue with some I code I've converted to Swift. When calling self.tableView.endUpdates() my table view stays empty and I get the following error:
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UITableView.m:1501
Weirdly enough when the same code was written in Objective-C this error is not showing up.
The table view datasource and delegate ar still in Objective-C and used by many other view controllers in the project.
Here is the code in Objective-c:
self.dataSource.itemArray = [self.userList copy];
NSMutableArray *indexPaths = [NSMutableArray new];
for (NSUInteger i = (offset); i < (end); i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[indexPaths addObject:indexPath];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:[indexPaths copy]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
And this is the same code but then in swift:
self.dataSource.itemArray = self.userList.copy() as! [User]
var indexPaths = [NSIndexPath]()
for i in offset...end {
let indexPath = NSIndexPath(forRow: i, inSection: 0)
indexPaths.append(indexPath)
}
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Fade)
self.tableView.endUpdates()
The tableView:numberOfRowsInSection: is called correctly and return the same in both case. Also the cell are correctly registered.
Is there a know issue or is ia just that I can't extent an Objective-C base class in Swift.
In this piece of code:
for i in offset...end {
let indexPath = NSIndexPath(forRow: i, inSection: 0)
indexPaths.append(indexPath)
}
The last indexPath which will be added is NSIndexPath(forRow: end, inSection: 0), that means that you are adding one extra row to your table view which is not expected.
Probably you want to change your loop to:
for i in offset..<end {
let indexPath = NSIndexPath(forRow: i, inSection: 0)
indexPaths.append(indexPath)
}
In my case
myTableView.reloadSections([sectionIndex], with: .none)
did work and removed the error. It may be due to any button or textfield tag getting wrong index after row deletion.
Related
I have looked for ways of getting the last indexPath of a UICollectionView, although below code works for a UITableView (having one section):
[NSIndexPath indexPathForRow:[yourArray count]-1 inSection:0]
but not been able to achieve the same thing for a UICollectionView.
You can find last index of UICollectionView like this.
NSInteger lastSectionIndex = MAX(0, [self.yourCollectionView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.yourCollectionView numberOfItemsInSection:lastSectionIndex] - 1);
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex
inSection:lastSectionIndex];
You can also find last index of UITableView like this.
NSInteger lastSectionIndex = MAX(0, [self.yourTableView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.yourTableView numberOfRowsInSection:lastSectionIndex] - 1);
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex
inSection:lastSectionIndex];
And if you want to detect last index of a specific section, you just need to replace the index of that section with "lastSectionIndex".
Simple way for the server side paging (that I use):
You can do the server side paging in willDisplay cell: delegate method of the collection/table view both.
You'll get the indexPath of the cell that's going to display then make a condition that will check that the showing indexPath.item is equal to the dataArray.count-1 (dataArray is an array from which your collection/table view is loaded)
i think,
you should do it in scrollView's Delegate "scrollViewDidEndDecelerating",
check your currently visible cells by
NSArray<NSIndexPath*>* visibleCells = [self.collection indexPathsForVisibleItems];
create last indexPath by,
NSIndexPath* lastIndexPath = [NSIndexPath indexPathForItem:(datasource.count-1) inSection:0];
check conditions,
if([visibleCells containsObject:lastIndexPath]) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
whole code will be like, Objective C Code,
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSArray<NSIndexPath*>* visibleCells = [collection indexPathsForVisibleItems];
NSIndexPath* lastIndexPath = [NSIndexPath indexPathForItem:(Blogs.count - 1) inSection:0];
if([visibleCells containsObject:lastIndexPath]) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
}
Swift 3.1 Code,
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let visibleCells: [IndexPath] = collection.indexPathsForVisibleItems
let lastIndexPath = IndexPath(item: (Blogs.count - 1), section: 0)
if visibleCells.contains(lastIndexPath) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
}
Swift 5
extension UICollectionView {
func getLastIndexPath() -> IndexPath {
let lastSectionIndex = max(0, self.numberOfSections - 1)
let lastRowIndex = max(0, self.numberOfItems(inSection: lastSectionIndex) - 1)
return IndexPath(row: lastRowIndex, section: lastSectionIndex)
}
}
When I traverse all the cell in a tableView, In Objective-C:
for (int i = 0; i < [_tableView numberOfRowsInSection:0]; i++) {
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
cell.textLabel.text = #"change text";
}
It worked, but in swift I code :
for index in 0...tableView.numberOfRowsInSection(0) {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0))!
cell.textLabel?.text = "\(index)"
}
It crashed, and throws an unwrap nil error. It seems that can only get the visible cells in Swift and can get all cells in Objective-C. How to explain this?
The behaviour is the same. Its just that in Objective C using a nil object does not crash whereas in Swift it crashes.
You can verify this by checking if the cell is nil or not in Objective-C and putting a log. In Swift to avoid the crash use optional binding instead.
E.g. if let cell = tableView.cellFor....
cellForRowAtIndexPath will only return the cell if it's currently visible, that's why your code causes a crash. To loop through all visible cells, just use
for cell in tableView.visibleCells {
// do something
}
I have a UITableView. I want to add a cell to the beginning of the tableView with animation. I tried the following:
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.reloadRowsAtIndexPaths(indexPath, withRowAnimation: UITableViewRowAnimation.Automatic)
But I get the following error:
Cannot invoke 'reloadRowsAtIndexPaths' with an argument list of type
'(NSIndexPath!, withRowAnimation: UITableViewRowAnimation)'
(When I just do tableView.reloadData() it works fine.)
What am I doing wrong, and how can I fix it?
tableView.reloadRowsAtIndexPaths expects an [NSIndexPath] instead of an NSIndexPath! as first argument.
so fix it by calling
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
let indexPaths = [indexPath]
tableView.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)
You get the error, because reloadRowsAtIndexPaths expects an array of index paths, not a single index path. However reloadRowsAtIndexPaths wouldn't add a cell, but just reload the cells at the specified index paths.
Instead you should use insertRowsAtIndexPaths to add your cell.
I'm trying to only display items that meet the criteria ( if location <= searchedDistance) but it seems like this is a very inefficient way to display this data. Does this function reload the entire tableview after each location is checked? If so, isn't this a horrible use of memory/time?
What's a better alternative?
var locations = [Int: Int]()
func searchSuccessful() {
var row: Int = 0
var section: Int = 0
for (index, location) in locations {
if location <= searchedDistance {
self.theTableView.beginUpdates()
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.theTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.theTableView.endUpdates()
}
}
}
Put the begin/end update outside of the loop.
self.theTableView.beginUpdates()
// Do all the changes in a single place, surrounded by begin/end updates
for (index, location) in locations {
if location <= searchedDistance {
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.theTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
self.theTableView.endUpdates()
Swift
self.tableView.beginUpdates()
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tableView.endUpdates()
Obj-C
[self.tableView beginUpdates];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Can any one help me out with UITableView animating issue?
By default we have animation for deleting cell and reordering cells in UITableView.
Can we have animated adding cell, if so how to do it.
I have checked out Three20, did not not get how twitter has done the table expand animation under MyProfile>ReTweets.
Want to try it without Three20 frameowrk, using the existing animation in UITableView.
You can use the following UITableView method:
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Example with self.dataSource being a mutable array and your table only having 1 section:
[self.dataSource addObject:#"New Item"];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.dataSource count]-1 inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
Note: Subtracted 1 from datasource count since NSIndexPath is zero indexed.
Swift
Example array that is the data source for the table view
var myArray = ["Cow", "Camel", "Sheep", "Goat"]
The following will add a row with animation at the top of the table view.
// add item to current array
myArray.insert("Horse", atIndex: 0)
// insert row in table
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
Multiple updates
If you need to do multiple insertions and/or deletions then surround them with beginUpdates() and endUpdates().
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([addIndexPath1, addIndexPath2, ...], withRowAnimation: .Fade)
tableView.deleteRowsAtIndexPaths([deleteIndexPath1, deleteIndexPath2, ...], withRowAnimation: .Fade)
tableView.endUpdates()
Further reading
Documentation
Use reloadRowsAtIndexPaths:indexPath
tableView.beginUpdates()
[tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
tableView.endUpdates()
i hope this will help you..
In swift is also possible using:
func insertRow(entries: [String]) {
tableView.performBatchUpdates({
self.tableView.insertRows(at: [IndexPath(row: entries.count - 1, section: 0)], with: .bottom)
}, completion: nil)
}