I want to do a batch insertion into UITableview.
MyArray.all() do |datas|
self.tableView.beginUpdates
self.tableView.insertRowsAtIndexPaths(datas, withRowAnimation: UITableViewRowAnimationFade)
self.tableView.endUpdates
self.myarrayvar += datas
end
'myarrayvar' is an array of MyArray. I tried to refer to
apple documentation
and stackoverflow which is similar to my question.
But I really no idea how to convert my datas, to fit into the parameter of insertRowsAtIndexPaths function.
Using rubymotion
Here's one way that I'm currently using:
def insertCellsAtBottom(newData)
# Get count for the existing data you have.
currentCount = #data.count
# Build the indexPaths for the new data, adding to the current count each time.
# This will make sure you are creating the correct indexes.
indexPaths = newData.map.with_index do |obj, index|
NSIndexPath.indexPathForRow(currentCount + index, inSection: 0)
end
# Update your datasource and insert the rows.
self.tableView.beginUpdates
#data.addObjectsFromArray(newData)
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimationFade)
self.tableView.endUpdates
end
Your MyArray contains an array of objects. The method insertRowsAtIndexPaths:withRowAnimation is for telling the tableView that you have added items to your datasource and that it should go and ask for that data at the indexPaths you supply.
The steps you take are:
Update your data model
Tell the tableView what changes have been made
So for example if you start with an array and add one object your would need to do
# Start with empty data model
my_array = []
# Update the model to have one object
my_array << my_new_object
# Tell the table that you have updated your model by inserting one object at the beginning of the first section
inserted_index_paths = [ NSIndexPath.indexPathForRow(0, inSection: 0]) ]
self.tableView.insertRowsAtIndexPaths(inserted_index_paths, withRowAnimation: UITableViewRowAnimationFade)
If you then inserted an object at the end again it would look very similar except that the newly inserted index path would be NSIndexPath.indexPathForRow(1, inSection: 0) as you would now be telling the tableView that you inserted a second object into the first section
Old Answer
You should do something like
index_paths = MyArray.map { |index| NSIndexPath.indexPathForRow(index, inSection: 0) }
self.tableView.beginUpdates
self.tableView.insertRowsAtIndexPaths(index_paths, withRowAnimation: UITableViewRowAnimationFade)
self.tableView.endUpdates
in obj-c this is the way to do it...
if you get your datas from an NSMutableArray, just make your operation in it.
for example, i got
array = #[#"text1", #"text2", #"text4"];
for my cell, i'm calling in cellForRowAtIndexPath method:
[cell setText:array[indexPath.row]];
in my array i'm making :
array[2] = #"text3";
and then :
[self.tableView reloadData];
I didn't test this but it's the good way to insert a new row.
Related
I am writing a function to loop through all of the cells in a UITableViewController. Here is what I have so far:
var i = 0
while (i < tableView.numberOfRows(inSection: 0)) {
i += 1
let cell = tableView.cellForRow(at: i-1)
}
Everything loops correctly until I try to get the cell. It expects an input of type IndexPath, however I am passing in an Int. Whenever I force it as an IndexPath like so:
let cell = tableView.cellForRow(at: i-1 as! IndexPath)
I get a warning saying that it will always fail/ always return nil. Is there a better way to do this, or am I just missing a crucial step. Any help is greatly appreciated. Thanks in advance!
EDIT (A LITTLE MORE EXPLANATION): All the cells are custom classed cells, with a specific variable. I want to loop through all of the cells and get that value.
let visibleCells = tableView.visibleCells
for aCell in visibleCells {
print(aCell.question?.text) <------- this is the value I want
}
Most of the information below has been said by others in the other answers and comments, but I wanted to put it all in one place:
We need to step back from the specifics of the question and ask what you are actually trying to do.
The UITableView method cellForRow(at:) will only return cells that are actually on-screen. If there is only room for 5 cells and you have to scroll to expose the rest, that method will return nil for all but the cells that are visible.
As others have suggested, if your goal is to loop through the cells that are on-screen the property tableView.visibleCells would be a better choice.
If your goal is to loop through all cells that exist in your data then you need to explain what you are trying to do.
As for your specific question, the cellForRow(at:) wants a parameter of type IndexPath. You can't simply cast an Int to IndexPath. That will fail. Instead, as #TaylorM said in their answer, you need to create an IndexPath. If your table view only has a single section then you can simply use
let indexPath = IndexPath(row: i, section: 0)
(Assuming you fix your loop code so your indexes start at 0.)
It also does not make sense to use a while loop like that.
Instead of all of this, I suggest using:
let visibleCells = tableView.visibleCells
for aCell in visibleCells {
//Do something with the cell
}
You should create an IndexPath via code, like this:
let ndx = IndexPath(row:i, section: 0)
Or, to modify your code:
var i = 0
while (i < tableView.numberOfRows(inSection: 0)) {
i += 1
let ndx = IndexPath(row:i-1, section: 0)
let cell = tableView.cellForRow(at:ndx)
}
Based on the later edit where you mention that you want the value of a text string on each row, I would suggest that the above is probably not the best way to approach this :) (I know others have already said this, but I didn't want to make assumptions about what you wanted to do unless you specifically stated what you wanted ...)
You are probably better off taking the same approach you take to populate the data for the table view cells via cellForRowAt: to get the question text than to loop through all the table rows, which would result in some issues for non-visible rows as others have indicated already.
If you have any issues with getting the data provided for cellForRowAt:, do share the code for the cellForRowAt: with us and I'm sure one of us can help you figure things out :)
Try
let indexPath = IndexPath(row: i, section: 0)
You may need to adjust the section to fit your needs.
Check out the documentation for IndexPath. Like most classes and structures, IndexPath has initializers. If you need to create a new instance of any object, you'll most like use an initializer.
*also see the comment from #rmaddy:
"Unrelated to your question but I can almost guarantee that your attempt to loop through all of the cells is the wrong thing to do. What is your actual goal with that loop?"
I believe I have figured it out using all of the help from you wonderful people.
In my case, I have a custom class with a variable that I want access to. I can do what Taylor M, Fahim, and Duncan C said: let indexPath = IndexPath(row: i, section: 0)
Then I can add as! NumberTableViewCell to the end, which allows me access to the values defined there. In the end, it looked like this:
var i = 0
while (i < tableView.numberOfRows(inSection: 0)) {
i += 1
var cell = tableView.cellForRow(at: IndexPath(row: i-1, section: 0)) as! NumberTableViewCell
print(cell.question.text!)
}
Thank you all for your help!
I have a comments array declared as: var comments: [String] which I populate it with some Strings and I also have a UICollectionView within which I present the comments. My code is the following when I try to delete the selected cells from the UICollectionView:
if let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems {
for indexPath in indexPathsForSelectedItems {
comments.remove(at: indexPath.item) //I have only one section
}
collectionView.deleteItems(at: indexPathsForSelectedItems)
}
The issue is that sometimes when I delete the selected items, it creates an out of bounds exception on the comments array.
However when I use the following approach (create a copy array and replace the original one with its copy) no problem occurs:
var indexes: [Int] = []
for indexPath in indexPathsForSelectedItems {
indexes.append(indexPath.item)
}
var newComments: [String] = []
for (index, comment) in comments.enumerated() {
if !indexes.contains(index) {
newComments.append(comment)
}
}
comments = newComments
Why is this happening?
I am using Swift 3 and XCode 8.2.1
Sorting
If you're not sure that indexPathsForSelectedItems are sorted in descending order, and hence always deletes the highest index first, you will eventually run into an out of bounds. Deleting an item will change the indices for all array elements with higher indices.
You probably want to use indexPathsForSelectedItems.sorted(by: >).
Everybody knows about the indexPathsForSelectedRows - but here come the time that i need to know the rows that are not selected
I would like to make an extension of it.
Is there any chance somebody already have done it or have an idea how this can be done?
If there is only one section in the table view you could simply
Map all row indexes from indexPathsForSelectedRowsto an Int array.
let rowIndexes = indexPathsForSelectedRows.map { $0.row }
Create an Int array from the indexes in the dataSourceArray
let allIndexes = 0..<dataSourceArray.count
Filter the indexes which are not in allIndexes and create new index paths
let indexPathsForDeselectedRows = allIndexes.filter {!rowIndexes.contains($0) }.map {NSIndexPath(forRow: $0, inSection: 0)}
If there are multiple sections it's a bit more complex.
Im creating a new table, and within it, Im inserting a new row with fake text, but I get the crash report
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'attempt to insert row 5
into section 0, but there are only 5 rows in section 0 after the
update
after some fiddling around, I changed my code from:
#IBAction func add() {
let newRow = ChecklistItems(text: "Im the new Row", checked: false)
items.append(newRow)
let index = items.count
let indexPath = NSIndexPath(forRow: index, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
}
TO THIS:
#IBAction func add() {
let index = items.count
let newRow = ChecklistItems(text: "Im the new Row", checked: false)
items.append(newRow)
let indexPath = NSIndexPath(forRow: index, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
}
can someone explain to me the actual details why this switch made the code work?
thank you so much~
Welcome to SO.
In your first version, you add an item to your items array, then create an indexPath which points to the count of the array (So if the array contains 5 items, you create an indexPath value of row:5, section:0)
However, arrays are zero-based. If an array contains 5 elements, valid array indexes are 0 - 4. The same goes for indexPath values. The highest valid row value is totalRows - 1. If the array contains 5 elements, 5 is not a valid array index.
In the second version of your code, index = the item count BEFORE adding an item. So if the row contains 4 items, count = 4, you add an item, and now items contains 5 items and 4 is a valid index into the array, so the insert does not refer to an item that's beyond the end of your items array.
Because the indexPath is beginning with 0, so it's max value is the items.count - 1
Because count is starting from 1, but NSIndexPath and also array index starting from 0.
so lets say you have an array of ("a", "b", "c"), array count is 3. but the index of it is 0, 1, 2.
so with your first code, you add new item and choose the index based on its count which is beyond its array index. for example you add new item "d" to example array above, the count will be changed to 4. But the max index is 3, which is why it throws error.
with your second code, you get the index based on count first before adding. So you set index for item d with 3, because the array only contain a, b, c and then add d to array.
it is working for you right now, but it will potentially create a mismatch between your array index and your tableView index.
A good way to retrieve object index is using array function indexOf.
My goal is to insert a section upon a click and insert 4 rows into this section.
I'm already familiar with how to use InsertRowsAtIndexPaths and inserting rows into one section makes no problem.But when it comes to inserting new sections, it's tricky and the apple documentation doesn't explain it fully.
Here is the code i use for inserting the rows
self.tableView!.beginUpdates()
var insertedIndexPaths: NSMutableArray = []
for var i = 0; i < newObjects.count + 1; ++i {
insertedIndexPaths.addObject(NSIndexPath(forRow: initialCount + i, inSection: sectionsnumbers)) }
self.tableView?.insertRowsAtIndexPaths(insertedIndexPaths as [AnyObject], withRowAnimation: .Fade)
self.tableView!.endUpdates()
Any examples, insights are very appreciated.
Thank you.
When you insert a section, its data is reloaded. You don't need to tell the tableview about all the new rows individually; it will just ask the data source for the number of rows in the new section.
See this document for more information.
use var sectionNumbers:NSMutableArray = ({"object":"object"}) //etc whatever object you want
Now use this function
func numberOfSectionsInTableView(tableView: UITableView) -> Int{
return sectionNumbers.count}
and add object in sectionNumbers and callThis method method where you want to add section
tableView.reloadData()
It will help
Inserting sections is different than inserting rows. If you want to insert rows you need to call tableView.beginUpdates() first and table.endUpdates() after done, otherwise it will throw some data inconsistency exception. On the other hand, when inserting sections you don't have to do anything, just reflect the change in your datasource like numberOfSectionsInTableView.