How to properly move array item when `UICollectionViewCell` position is changed - ios

I have a UICollectionView, on which user can change position of the cells, after looking at every Swift Drag and Drop page. I settled on a method to be able to update the array of strings the keep track of the collection view in this function:
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let positionPreviouslyHoldingTheSelectedIndex = list[sourceIndexPath.item]
// put the select index in the intended destination index
list[sourceIndexPath.item] = list[destinationIndexPath.item]
// put the cell/index of the destination index in the selected positions old index
list[destinationIndexPath.item] = positionPreviouslyHoldingTheSelectedIndex
}
But above code update the array as expected and it gets weird, how I would update the array/list once the moveItemAt is called.

There are 2 IndexPaths, you have to get rid of the array element from sourceIndexPath and add it to destinationIndexPath, as such:
let item = list.remove(at: sourceIndexPath.item)
list.insert(item, at: destinationIndexPath.item)

Related

Selected cell is deselected when switch to other page

I make a custom calendar using UICollectionView. From the calendar when I selected some dates and move forward to next month, then back again to the previous month, the selected item is deselected. Maybe it happens for reusable cell. How can I solve this problem.
For better understand what I want:
From September I select 4,5 then move to August/July/November (In this month maybe select some other dates or not)
Then return to September. In September I want to showed 4,5 as selected
I tried this using didSelectItemAt indexPath, but when return back to the September the selected item is deselected
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateRangePickerCell {
if cell.isSelected == true {
cell.backgroundColor = .blue
cell.label.textColor = .white
cell.isUserInteractionEnabled = true
}
selectedDate = cell.date!
}
}
First create an array of selected Cells. If you are using a model to set data to cell, you can create an array of selected models. or you can create an array of selected rows.
Let's say you are using a model.
var selectedDates: [DateModel] = []
Then
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateRangePickerCell {
selectedDate = cell.date!
if !selectedDates.contains(dataSourceModel[indexPath.row]) {
selectedDates.append(dataSourceModel[indexPath.row])
}
}
}
then in your cellForItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if selectedDates.contains(dataSourceModel[indexPath.row]) {
cell.isSelected = true
}
}
Also make sure you remove you model when unSelected
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if selectedDates.contains(dataSourceModel[indexPath.row]) {
selectedDates.remove(dataSourceModel[indexPath.row])
}
}
*may contain some syntax error, but you can follow this path to get where you want.
Cells in UITableView and UICollectionView are reused when you scroll, that is why you should store which days selected in another place. Then, in cellForItem you should set isSelected.
Have you tried setting clearsSelectionOnViewWillAppear to false in viewDidLoad()?
A Boolean value indicating if the controller clears the selection when the collection view appears.
The default value of this property is true. When true, the collection view controller clears the collection view’s current selection when it receives a viewWillAppear(_:) message. Setting this property to false preserves the selection.
Source: Apple Developer Documentation

Why is array in collection view getting multiplied by amount of sections?

When I want to see an array in a collection view in swift, it gets multiplied by the amount of sections in the collection view. Why is this and how do I stop it?
let numbers = ["one", "two", "three"]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3;
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let theCell = collectionView.dequeueReusableCell(withReuseIdentifier: "theCell", for: indexPath) as! cell
for number in numbers {
print("\(number)")
}
return theCell
}
I hoped that it would return:
one
two
three
but instead it returned:
one
two
three
one
two
three
one
two
three
Since you have three cells per section, and assuming you have only one section, collectionView(_:cellForItemAt:) gets called three times. Each time you are printing all the elements of the numbers array. To only print the element corresponding to the current index, replace the for loop by:
print(numbers[indexPath.row])

Swift - separate views have subviews that are sharing the same memory?

new to swift. I have a nested CollectionView from one viewcontroller. The main viewcontroller has 7 collectionviewcell ("Level1Cell" in the code below). Each time I click a button or trigger an event, I want the collectionView to reload with the new data.
func eventHandler() {
// updates data
myCollectionView.reloadData()
}
Then, after it calls reload, it will call the reload again on each of the the nested CollectionViewCell.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Level1Cell", for: indexPath) as! Level1Cell
cell.appsCollectionView.reloadData()
return cell
}
The problem is, let say I want to, for the first cell, set a particular row some text.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(self.index == 0 && indexPath.row == 30){
rightCell.textLabel.text = "asdasd"
}
The fourth "Level1Cell" cell somehow has its label set also at the 30th row, but not the second and third. After stepping through the debugger, I realize that the cells, after reloading, the fourth cell "Level1Cell" is set to have the the same memory address as the first cell ( why does reload do this - shouldn't it allocate a new memory for each "Level1Cell"? - how can I get around this). Also, should I not use reload to update the data in the view and nested view of those from the view controller?
Thanks!
UIcollectionview will reuse cells. You must provide needed data for row in method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
Or just clear previous data at cell.

indexPath.row always returns zero

I have two collection view and one displays names and the other one displays age of the corresponding person. This data is stored inside array of dictionary in a form of "[["Name","Age"],["Name": "Daniel", "Age" : "20"],["Name":"Jake","Age":"20"]]. This data comes from CSV file, so the first element is a header. Inside collectionView cellForItemAtIndexPath, I'm checking collection view and provide data base on row number like cell[indexPath.row]["Name"] and cell2[indexPath.row]["Age"]. However, indexPath.row always returns zero, so I'm getting just headers -
How do fix this issue? This is my code -
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.nameCollectionView {
let nameCell = collectionView.dequeueReusableCellWithReuseIdentifier("NameCell", forIndexPath: indexPath) as! NameCell
nameCell.data.text = self.data?[indexPath.row]["Name"]
println(indexPath.row)
return nameCell
}
else{
let ageCell = collectionView.dequeueReusableCellWithReuseIdentifier("AgeCell", forIndexPath: indexPath) as! AgeCell
ageCell.data.text = self.data?[indexPath.row]["Age"]
return ageCell
}
}
As par your code you are setting numberOfItemsInSection only 1 then you always get 0th index. make there is dynamic value for example return Array.count.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.data.count // here you need to set dynamic count of array
}
UPDATE:
If you followed numberOfSectionsInCollectionView then make your code like following of cellForRow:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.nameCollectionView {
let nameCell = collectionView.dequeueReusableCellWithReuseIdentifier("NameCell", forIndexPath: indexPath) as! NameCell
nameCell.data.text = self.data?[indexPath.section]["Name"]
println(indexPath.section)
return nameCell
}
else{
let ageCell = collectionView.dequeueReusableCellWithReuseIdentifier("AgeCell", forIndexPath: indexPath) as! AgeCell
ageCell.data.text = self.data?[indexPath.section]["Age"]
return ageCell
}
}
IndexPath is a property which has the following structure
Indexpath {Section, Row}.
So if you want your data in two different section with a single row in them then indexpath.row for each of them is going to return 0 as because
For section index 0 - Indexpath[0,0] meaning indexpath of section index 0 and row index 0
For section index 1 - Indexpath[1,0] meaning indexpath of section index 1 and row index 0
Hope could make you understand.
As others have pointed out, you are telling your collection views that you always have 2 sections and 1 item in each section. Thus the collection view will only ever ask for 1 item in each section. Thus there will only ever BE one item in each section (index 0).
You say "This data is stored inside dictionary in a form..."
Is it a dictionary or an array of dictionaries? A dictionary is an unordered collection, so it is not appropriate for storing an ordered set of items for feeding to a collection view or table view. An array of dictionaries is appropriate. From the data you show, and your code, it looks like you have an array of dictionaries. You should edit your question to make that clear.
Your code doesn't really make sense. You have 2 different collection views, and you have each display different data. You tell your collection views that you have 2 sections but ignore the section number and create the same data for both sections. Something is wrong there.

correct way of deleting items in collection view

I have a collection view of 20 items stored in pictures array. I am deleting selected items with this code:
self.collectionView.performBatchUpdates({
let selectedItems = self.collectionView.indexPathsForSelectedItems()!
self.picturesObject.deleteItemsAtIndexPaths(selectedItems)
self.collectionView.deleteItemsAtIndexPaths(selectedItems)
}, completion: {_ in})
this is the implementation of picturesObject's deleteItemsAtIndexPaths:
func deleteItemsAtIndexPaths(indexPaths:[NSIndexPath]){
for indexPath in indexPaths{
pictures.removeAtIndex(indexPath.item)
}
}
The problem I'm facing is the following. Every time a selected item in collection view is deleted, the pictures array gets smaller. If I delete the first item, than pictures array is only 19 items big. If I than try to delete item number 20 in collection view, the array index gets out of bounds because pictures array is now only 19 object big, but I wanted to delete the 20iest object stored in indexpath.item.
What is the correct way of deleting items in collection view?
EDIT:/// collection view methods
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
collectionView.allowsMultipleSelection = true
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picturesObject.pictures.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! ShopCell
cell.imageView.image = picturesObject.pictures[indexPath.item]
return cell
}
Before looping through your array in deleteItemsAtIndexPaths, sort your indexPaths in descending order. Then, if you delete item number 19, the next to delete is guaranteed to be less than 19.

Resources