TableView Extension indexPathsFor `Un` SelectedRows - ios

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.

Related

Int vs. IndexPath Troubles

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!

Remove / delete selected cells from UICollectionView causes index out of bounds [sometimes]

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: >).

How do I append to a section of an array? [[]]

I have an array of arrays, theMealIngredients = [[]]
I'm creating a newMeal from a current meal and I'm basically copying all checkmarked ingredients into it from the right section and row from a tableview. However, when I use the append, it obviously doesn't know which section to go in as the array is multidimensional. It keeps telling me to cast it as an NSArray but that isn't what I want to do, I don't think.
The current line I'm using is:
newMeal.theMealIngredients.append((selectedMeal!.theMealIngredients[indexPath.section][indexPath.row])
You should re-model your data to match its meaning, then extract your tableview from that. That way you can work much more easily on the data without having to fuss with the special needs of displaying the data. From your description, you have a type, Meal that has [Ingredient]:
struct Meal {
let name: String
let ingredients: [Ingredient]
}
(None of this has been tested; but it should be pretty close to correct. This is all in Swift 3; Swift 2.2 is quite similar.)
Ingredient has a name and a kind (meat, carbs, etc):
struct Ingredient {
enum Kind: String {
case meat
case carbs
var name: String { return self.rawValue }
}
let kind: Kind
let name: String
}
Now we can think about things in terms of Meals and Ingredients rather than sections and rows. But of course we need sections and rows for table views. No problem. Add them.
extension Meal {
// For your headers (these are sorted by name so they have a consistent order)
var ingredientSectionKinds: [Ingredient.Kind] {
return ingredients.map { $0.kind }.sorted(by: {$0.name < $1.name})
}
// For your rows
var ingredientSections: [[Ingredient]] {
return ingredientSectionKinds.map { sectionKind in
ingredients.filter { $0.kind == sectionKind }
}
}
}
Now we can easily grab an ingredient for any given index path, and we can implement your copying requirement based on index paths:
extension Meal {
init(copyingIngredientsFrom meal: Meal, atIndexPaths indexPaths: [IndexPath]) {
let sections = meal.ingredientSections
self.init(name: meal.name, ingredients: indexPaths.map { sections[$0.section][$0.row] })
}
}
Now we can do everything in one line of calling code in the table view controller:
let newMeal = Meal(copyingIngredientsFrom: selectedMeal,
atIndexPaths: indexPathsForSelectedRows)
We don't have to worry about which section to put each ingredient into for the copy. We just throw all the ingredients into the Meal and let them be sorted out later.
Some of this is code is very inefficient (it recomputes some things many times). That would be a problem if ingredient lists could be long (but they probably aren't), and can be optimized if needed by caching the results or redesigning the internal implementation details of Meal. But starting with a clear data model keeps the code simple and straightforward rather than getting lost in nested arrays in the calling code.
Multi-dimentional arrays are very challenging to use well in Swift because they're not really multi-dimentional. They're just arrays of arrays. That means every row can have a different number of columns, which is a common source of crashing bugs when people run off the ends of a given row.

InsertSections function Swift

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.

How Can I Remove Only Selected Index Paths From An Array In Swift?

I have a mutable array:
var responseArray = ["yes", "no", "no way", "of course", "for sure", "not a chance", "positively"]
The responseArray is the data source for my table view which allows for multiple selections during editing.
I am capturing the selected index paths:
let paths = tableView.indexPathsForSelectedRows()
I can return and verify each selected indexPath of my tableView by running println(paths).
I have read the documentation for the indexPathsForSelectedRows method and understand that it returns an array of index paths which I have sorted by row.
What I cannot understand is how I can use the returned array of index paths to remove the data from the responseArray for each row that is selected for deletion in the table view.
After reading through some documents, is it correct of me to believe that I cannot remove any data from the `responseArray' as I am enumerating over it? For example:
#IBAction func deleteButtonPressed(sender: AnyObject) {
if responseArray.count > 0 {
if let paths = self.tableView.indexPathsForSelectedRows() {
var sortedArray = paths.sorted({$0.row < $1.row})
// Remove index paths from responseArray
for i in sortedArray {
responseArray.removeAtIndex(i.row)
}
self.tableView.reloadData()
}
}
}
I am able to remove each row from the table view one by one, but when I select the first and last rows, all of the rows, or any other combination of rows for deletion, I get fatal error: Array index out of range. However, if I select two adjacent rows for deletion, the desired result is achieved and those two rows are removed from the table view.
I know that there is something that I am missing, but as a new programmer I have been unable to resolve this issue for three days now. What is it that I am not doing correctly?
Here's your array: [ A, B, C, D ]
Let's say you want to delete A and D at index 0 and 3 respectively, one at a time:
deleteAtIndex(0) gives: [ B, C, D ]
deleteAtIndex(3) gives: Out of bounds exception
Edit:
Ok, to avoid complicating things, why not just always delete the highest index first by reversing your sort: {$1.row < $0.row}
For future reference, in addition to the answers given already you could simplify it further by reversing the selected indices:
#IBAction func deleteButtonPressed(sender: AnyObject) {
self.tableView.selectedRowIndexes.reverse().forEach { x in
responseArray.removeAtIndex(x)
}
self.tableView.reloadData()
}
Since your paths array is sorted, you know that every time you delete an element from the array, higher indices will now be one less than they were. You can simply keep an incrementing offset to apply to your deletions -
#IBAction func deleteButtonPressed(sender: AnyObject) {
if responseArray.count > 0 {
if let paths = self.tableView.indexPathsForSelectedRows() {
var sortedArray = paths.sorted({$0.row < $1.row})
var offset=0;
// Remove index paths from responseArray
for i in sortedArray {
responseArray.removeAtIndex(i.row-offset)
offset++
}
self.tableView.reloadData()
}
}
}

Resources