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

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

Related

Remove a particular value from array instead of remove using index path

I am working in array handling , the overall working of the application is the user can select the number of number of songs that has to be added to a particular playlist. I have used Tableview to list the songs when the user click the tableview cell have added .checkmark when already checkmark found I will remove .checkmark, similarly when the user select the song I have kept a array to save the track id of that particular song .
"SongIDArray" is the array used to save. When the user deselect the song I have to delete the particular songs "track id". but in array it has remove(at: index) I cannot delete it through index. please help me
for example :** my array is**
songIDArray = [25,45,69,78,63]
I need to remove 78 from the index without knowing the index path.
You can use filter to remove values from an array:
let newArray = [25, 45, 69, 78, 63].filter { $0 != 78 }
removeAll is the command for conditionally removing.
var array = [25,45,69,78,63]
array.removeAll{$0 == 78}
print(array)
//[25, 45, 69, 63]
You have a few options here, whichever is best suited to your exact use-case!
Remove by filtering
The filter function passes each element in the array to the block of code you provide and removes all items you return false for. This is useful for performing a simple filter, but if you also need the index of the removed item (For example to reload a table view index path) it's not very useful.
songIDArray = songIDArray.filter({ id in
return id != 78
})
Remove by index
First get the index to remove at, you can do this in various ways depending on the type of items you have in your array. If they conform to Equatable (Integers do) you can use
if let removalIndex = songIDArray.firstIndex(of: 78) {
songIDArray.remove(at: removalIndex)
}
If you don't have an array of Equatable elements, then you can instead use index(where:) which returns the first index for which you return true from the provided closure:
if let removalIndex = songIDArray.index(where: { id in
return id == 78
}) {
songIDArray.remove(at: removalIndex)
}
This method is useful if you also need to do something else with the index, like reload a UITableViewCell at a specific index path to reflect your change e.t.c.
Remove All
In swift 4.2 there is also a removeAll(where:) function which works similar to filter but doesn't return a new array:
songIDArray.removeAll(where: { id in
return id == 78
})

TableView Extension indexPathsFor `Un` SelectedRows

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.

How can I access an item from an array dynamically with Swift

I've got an Array that I'm using to populating the rows of a UITableView.
After a row is selected I need to retrieve information from the Array based on the row selected to populate some outlets (labels, textfields, etc.)
For example:
I create an itemSelected variable in the didSelectRowAtIndexPath in my ViewController for the TableView which I set to indexPath.row
itemSelected = indexPath.row
Then in my viewDidLoad for my otherViewController I need to retrieve the info by
array[itemSelected]
But, I get a compiler error that says: "Expression resolves to unused i-value"
In here you simply accessing the array but not calling any value. As a example if you have a key call "Name" in your array and you want to set it to a UILabel just do it as this.
self.Name.text = array[itemSelected].valueForKey("Name") as! String
if not just do something with it.
self.Name.text = array[itemSelected] as! String
OR
print(array[itemSelected])

Swift 2D Array Initialisation with Objects

I'm Trying to initialise a 2D array with objects in every cell.
I started with a class,
class cell {
var up = 1
var down = 1
var right = 1
var left = 1
}
And then Initialise my Array as such:
let row = Array<cell!>(count: 10, repeatedValue: cell())
let myArray = Array(count: 10, repeatedValue: row)
Now that works fine... Until I change the property of one of the objects.
myArray[0][0].left = 0
Then ALL objects in the Array have their "left" property set to 0.
How can I create objects that are independent of each other in the Array? (without using for-loops to append each item individually)
This won't work with Cell being a class because that is a reference type, so every cell in your array will be a reference to the same cell. If you can, change Cell to be a struct instead of a class. As a value type, every cell will be a unique copy.
If Cell must be a class, you could create myArray using nested maps, (which technically is still loops):
let myArray:[[Cell!]] = (1...10).map { _ in (1...10).map { _ in Cell() } }
Note, you should use names starting with an uppercase letter for class and struct names, which is why I change your cell to Cell.
It's because Cell is a class and classes are reference types. So the first line creates a row with 10 references to the same cell. The second line creates 10 unique rows (arrays are structs therefore value types) but all the unique rows have 10 references to the same cell.
There are several ways to resolve this. You can make sure you create 100 unique cells:
var array = [[Cell]]()
for _ in 0 ..< 10
{
var row = [Cell]()
for _ in 0 ..< 10
{
row.append(Cell())
}
array.append(row)
}
Or, you might consider making Cell a struct which is probably the best idea if a Cell is as simple as your example and you don't need inheritance.
struct Cell
{
var up = 1
var down = 1
var right = 1
var left = 1
}
In which case you can initialise the array the way you expect.
var array = [[Cell]](count: 10, repeatedValue: [Cell](count: 10, repeatedValue: Cell()))
Your class cell works is a reference type. What does that mean ?
Well, Apple's explanation about the topic is quite sufficient :
Types in Swift fall into one of two categories: first, “value types”,
where each instance keeps a unique copy of its data, usually defined
as a struct, enum, or tuple. The second, “reference types”, where
instances share a single copy of the data, and the type is usually
defined as a class. In this post we explore the merits of value and
reference types, and how to choose between them.
By declaring cell as a class, this is a reference type.
Refence type
// let's create an object
let cell1 = Cell()
// now we add a reference to this object
let cell2 = cell1
// now we change the top of cell1
cell1.up = 10
NSLog("\(cell2.up)")
// -> outputs : 10
// Since cell1 and cell2 represents the same object,
// you can't modify cell1 without modifying ce22.
What happen if you declares cell as a structinstead of a class? It becomes a value type :
Value type
// let's create an object
let cell1 = Cell()
// now we create a new object, cell2, with the value initialized
// with cell1's value. It's a copie, not a reference
let cell2 = cell1
// now we change the top of cell1
cell1.up = 10
NSLog("\(cell2.up)")
// -> outputs : 0
// Here cell1 and cell2 are totally different objects
What you are doing won't work. If you try to create an array and populate it repeatedValue and the repeated value is an object, the array contains multiple references to the same object. (Objects are a "reference type". When added to a container what gets added is a reference to the object, not a copy.)
You need to create a new object for each entry in your outer array:
(Edited to create an array of String objects since I don't have access to the OP's cell class)
typealias rowArray = [String!]
var myArray = [rowArray]()
for i in 1...10
{
myArray.append(rowArray(count: 10, repeatedValue: "Foo"))
}
Note that you're going to face the same problem with your inner array. You don't have an array of unique cell objects - you have an array that contains multiple references to a single cell object.

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