How to use single array in multiple collectionView sections? - ios

I have single array which contain game`s informations. My Json has 12 items in a page. I did created 4 sections which has 3 rows. It is repeating first 3 items of array in every sections.
Screenshot from app
I want to use like that;
Total Items = 12
Section = 1 2 3
Section = 4 5 6
Section = 7 8 9
Section = 10 11 12
How can I do that ? Thanks in advance :)
func numberOfSections(in collectionView: UICollectionView) -> Int {
return id.count / 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "lastAddedCell", for: indexPath) as! lastAddedCell
cell.gameName.text = name[indexPath.row]
cell.gameImage.sd_setImage(with: URL(string:resimUrl[indexPath.row]))
return cell
}

I don't think it's such a good idea. I would rather create the section separately by making a section manager than making them from the same array. But, if you want to do it the way you are doing it right now. Here is an easy fix:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return id.count / 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "lastAddedCell", for: indexPath) as! lastAddedCell
let index = indexPath.row + (indexPath.section * 3) // The index is then based on the section which is being presented
cell.gameName.text = name[index]
cell.gameImage.sd_setImage(with: URL(string:resimUrl[indexPath.row]))
return cell
}

Consider this case below and implement it,
var array = [1,2,3,4,5,6,7,8,9,10,11,12]
//Statcially you can slice them like this
var arr2 = array[0...2] {
didSet {
//reload your collection view
}
}
var arr3 = array[3...5]
var arr4 = array[6...8]
var arr5 = array[9...array.count - 1]
Above you manually sliced the dataSource for each UICollectionView but the problem is this is really risky and eventually can lead to an Index Out of Range crash, so we dynamically slice the array thru looping in it using the index of each element in range of +3 indexes to append to the new UICollectionView data source.
// loop thru the main array and slice it based on indexes
for(index, number) in array.enumerated() {
if 0...2 ~= index { // if in range
arr2.append(number)
} else
if index <= 5 {
arr3.append(number)
} else
if index <= 8 {
arr4.append(number)
} else
if index <= 11 {
arr5.append(number)
}
}
Finally : in your numberOfItemsInSection check the UICollectionView and set return its data source like,
if collectionView = myMainCollectionView {
return arr3.count
}
And goes the same for the cellForItemAt
Heads Up : make sure your dataSource arrays are empty initially,
let arr2: [Int] = [] {
didSet{
//reload your collectionView
}
}

Related

How to insert multiple sections in collectionview at once?

Actually i know how to insert one section at a time but i don't know
how to insert multiple section at once?.
collectionView.insertSections(IndexSet(integer: array.count + 1))
how to insert multiple section at once?.
ex
var array = [3,4,2,1,6] // one section for one element
func numberOfSections(in collectionView: UICollectionView) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array[section]
}
//now i want to add three new sections [8,5,9]
Api doc:
Use this method to insert one or more sections into the collection
view. This method adds the sections,
An IndexSet can contain multiple indexes
collectionView.insertSections(IndexSet([2, 4, 7]))
Edit:
To insert 3 new sections at end of
var array = [3,4,2,1,6]
use
let startIndex = array.count
array.append(contentsOf: [8,5,9])
let endIndex = array.count
collectionView.insertSections(IndexSet(startIndex..<endIndex))

Stored selected indexPath of UICollectionView inside UITableView

I'm currently making a screen that has an UITableView with many sections that have the content of cells is UICollectionView. Now I'm saving the selected indexPath of the collection into an array then save to UserDefaults (because the requirement is showing all cells has selected before when reopening view controller).
But I have the issues is when I reopen view controller all items in all sections with the same selected indexPath show the same state.
I know it occurs because I just save the only indexPath of the selected item without the section of UITableview which is holding the collection view. But I don't know how to check the sections. Can someone please help me to solve this problem? Thank in advance.
I'm following this solution How do I got Multiple Selections in UICollection View using Swift 4
And here is what I do in my code:
var usrDefault = UserDefaults.standard
var encodedData: Data?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
if let act = usrDefault.data(forKey: "selected") {
let outData = NSKeyedUnarchiver.unarchiveObject(with: act)
arrSelectedIndex = outData as! [IndexPath]
}else {
arrSelectedData = []
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let optionItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "optionCell", for: indexPath) as! SDFilterCollectionCell
let title = itemFilter[indexPath.section].value[indexPath.item].option_name
if arrSelectedIndex.contains(indexPath) {
optionItemCell.filterSelectionComponent?.bind(title: title!, style: .select)
optionItemCell.backgroundColor = UIColor(hexaString: SDDSColor.color_red_50.rawValue)
optionItemCell.layer.borderColor = UIColor(hexaString: SDDSColor.color_red_300.rawValue).cgColor
}else {
optionItemCell.backgroundColor = UIColor(hexaString: SDDSColor.color_white.rawValue)
optionItemCell.layer.borderColor = UIColor(hexaString: SDDSColor.color_grey_100.rawValue).cgColor
optionItemCell.filterSelectionComponent?.bind(title: title!, style: .unselect)
}
optionItemCell.layoutSubviews()
return optionItemCell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let strData = itemFilter[indexPath.section].value[indexPath.item]
let cell = collectionView.cellForItem(at: indexPath) as? SDFilterCollectionCell
cell?.filterSelectionComponent?.bind(title: strData.option_name!, style: .select)
cell?.backgroundColor = UIColor(hexaString: SDDSColor.color_red_50.rawValue)
cell?.layer.borderColor = UIColor(hexaString: SDDSColor.color_red_300.rawValue).cgColor
if arrSelectedIndex.contains(indexPath) {
arrSelectedIndex = arrSelectedIndex.filter{($0 != indexPath)}
arrSelectedData = arrSelectedData.filter{($0 != strData)}
}else {
arrSelectedIndex.append(indexPath)
arrSelectedData.append(strData)
encodedData = NSKeyedArchiver.archivedData(withRootObject: arrSelectedIndex)
usrDefault.set(encodedData, forKey: "selected")
}
if let delegate = delegate {
if itemFilter[indexPath.section].search_key.count > 0 {
if (strData.option_id != "") {
input.add(strData.option_id!)
let output = input.componentsJoined(by: ",")
data["search_key"] = itemFilter[indexPath.section].search_key.count > 0 ? itemFilter[indexPath.section].search_key : strData.search_key;
data["option_id"] = output
}
}else {
data["search_key"] = itemFilter[indexPath.section].search_key.count > 0 ? itemFilter[indexPath.section].search_key : strData.search_key;
data["option_id"] = strData.option_id
}
delegate.filterTableCellDidSelectItem(item: data, indexPath: indexPath)
}
}
This will only work based on the assumption that both your parent table view and child collection views both are not using multiple sections with multiple rows and you only need to store one value for each to represent where an item is located in each respective view.
If I am understanding correctly, you have a collection view for each table view cell. You are storing the selection of each collection view, but you need to also know the position of the collection view in the parent table? A way to do this would be to add a property to your UICollectionView class or use the tag property and set it corresponding section it is positioned in the parent table. Then when you save the selected IndexPath, you can set the section to be that collection view's property you created(or tag in the example) so that each selected indexPath.section represents the table view section, and the indexPath.row represents the collection view's row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
let collectionView = UICollectionView()
collectionView.tag = indexPath.section
//...
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
indexPath.section = collectionView.tag
let strData = itemFilter[indexPath.section].value[indexPath.item]
//...
}
Basically each selected index path you save will correspond to the following:
indexPath.section = table view section
indexPath.row = collection view row
IndexPath(row: 5, section: 9) would correlate to:
--table view cell at IndexPath(row: 0, section: 9) .
----collection view cell at IndexPath(row: 5, section: 0)
Edit: This is how you can use the saved index paths in your current code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//...
let tempIndexPath = IndexPath(row: indexPath.row, section: collectionView.tag)
if arrSelectedIndex.contains(tempIndexPath) {
//...
} else {
//...
}
//...
}
Your statement arrSelectedIndex.contains(indexPath) in the cellForItemAt method is not correct.
Each time a UICollectionView in a UITableView's section is loaded, this will called the cellForItemAt for ALL cells.
Here is the error :
In your GIF example the first cell is selected in the first collectionView, you will store (0, 0) in the array.
But when the second collectionView will loads its cells, it will check if the indexPath (0, 0) is contained into your array. It is the case, so the backgroundColor will be selected.
This error will be reproduced on every collectionView stored in your tableView sections.
You should probably also store the sectionIndex of your UITableView into your array of IndexPath.

uicollectionview inside uitableviewcell scroll will crash due to Index out of range

I learned the UICollectionView inside UITableViewCell from this tutorial.
It works perfectly only when all of the collection views have the same numberOfItemsInSection, so it can scroll and won't cause Index out of range error, but if the collection view cells numberOfItemsInSection are different, when I scroll the collection view it crashes due to Index out of range.
I found the reason that when I scroll the tableview, the collection view cell index path updated according to the bottom one, but the collection view I scrolled is top one so it does't remember the numberOfItemsInSection, so it will crash due to Index out of range.
ps: it is scrollable, but only when that table cell at the bottom, because its numberOfItemsInSection is correspond to that cell and won't cause Index out of range.
This is the numberOfItemsInSection of My CollectionView, i actually have two collection view but i think this is not the problem
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var numberOfItemsInSection: Int = 0
if collectionView == self.collectionview_categorymenu {
let categories = self.json_menucategory["menu_categories"].arrayValue
numberOfItemsInSection = categories.count
} else {
let menuitems = self.json_menucategory["menu_items"].arrayValue
let item_data = menuitems[self.itemimages_index].dictionaryValue
let menu_item_images = item_data["menu_item_images"]?.arrayValue
numberOfItemsInSection = (menu_item_images?.count)!
print(numberOfItemsInSection)
}
return numberOfItemsInSection
}
This is the cellForItemAt indexPath of my CollectionView:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionview_categorymenu {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryMenuCell", for: indexPath) as! CategoryMenuCell
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ItemImagesCell", for: indexPath) as! ItemImagesCell
let menuitems = self.json_menucategory["menu_items"].arrayValue
let item_data = menuitems[self.itemimages_index].dictionaryValue
let menu_item_images = item_data["menu_item_images"]?.arrayValue
print(menu_item_images!)
let itemimage_data = menu_item_images?[indexPath.item].dictionaryValue
print("===\(indexPath.item)===")
let itemimage_path = itemimage_data?["image"]?.stringValue
let itemimage_detail = itemimage_data?["detail"]?.stringValue
if let itemimage = cell.imageview_itemimage {
let image_url = itemimage_path?.decodeUrl()
URLSession.shared.dataTask(with: NSURL(string: image_url!) as! URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print("error")
return
}
DispatchQueue.main.async(execute: { () -> Void
in
itemimage.image = UIImage(data: data!)!
})
}).resume()
}
if let description = cell.textview_description {
description.text = itemimage_detail
}
if let view = cell.view_description {
view.isHidden = true
}
return cell
}
}
The problem is probaby that you are returning the same number every time for numberOfItemsInSection what you want to do is return the number of items in each array. From the link you sent they do this:
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return model[collectionView.tag].count
}
The other possibility is that you are returning the wrong array when cellForItemAtIndexPath please check that you are using the tag attribute correctly
So they get the count of each array within the model. Without seeing any of your code its very difficult to actually see where your error is coming from.

UICollectionView attempts to dequeue cells beyond data source bounds while reordering is in progress

I have implemented collection view with cell reordering
class SecondViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private var numbers: [[Int]] = [[], [], []]
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
for j in 0...2 {
for i in 0...5 {
numbers[j].append(i)
}
}
longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
self.collectionView.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .Began:
guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case .Changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case .Ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
extension SecondViewController: UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return numbers.count
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let number = numbers[section].count
print("numberOfItemsInSection \(section): \(number)")
return number
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
print("cellForItemAtIndexPath: {\(indexPath.section)-\(indexPath.item)}")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! TextCollectionViewCell
cell.textLabel.text = "\(numbers[indexPath.section][indexPath.item])"
return cell
}
func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let temp = numbers[sourceIndexPath.section].removeAtIndex(sourceIndexPath.item)
numbers[destinationIndexPath.section].insert(temp, atIndex: destinationIndexPath.item)
}
}
It works fine until I try to drag an item from section 0 to section 2 (which is off screen). When I drag the item to the bottom of collection view, it slowly starts scrolling down. At some point (when it scrolls past section 1) application crashes with fatal error: Index out of range because it attempts to request cell at index 6 in section 1 while there are only 6 items in this section. If I try to drag an item from section 1 to section 2, everything works fine.
Here's an example project reproducing this problem (credit to NSHint):
https://github.com/deville/uicollectionview-reordering
Is this a bug in framework or am I missing something? If it is the former, what would be the workaround?
I actually ran into this issue today. Ultimately the problem was in itemAtIndexPath - I was referencing the datasource to grab some properties for my cell : thus the source of the out of bounds crash. The fix for me was to keep a reference to the currently dragging cell and in itemAtIndexPath; check the section's datasource length versus the passed NSIndexPath and if out of bounds; refer to the dragging cell's property.
Likesuchas (in Obj-C ... haven't moved to Swift yet) :
NSArray* sectionData = [collectionViewData objectAtIndex:indexPath.section];
MyObject* object;
if (indexPath.row < [sectionData count]) {
object = [dataObject objectAtIndex:indexPath.row];
} else {
object = draggingCell.object;
}
[cell configureCell:object];
Not sure if this is similar to your exact issue; but it was mine as everything is working as expected now. :)

Set numberOfItemsInSection at Runtime - UICollectionView

I would like to set the numberOfItemsInSection of my collectionView at runtime. I will be changing the value programmatically at runtime quite often and would like to know how.
I have an array of images to display in my UICollectionView (1 image per UICollectionViewCell), and the user can change the category of the images to display, which will also change the number of images to display. When the view loads, the numberOfItemsInSection is set to the count of the allClothingStickers array. But number this is too high. The array that does get displayed is the clothingStickersToDisplay array which is a subset of the allClothingStickers array.
There is this error after some scrolling:
fatal error: Array index out of range
This is because the number of items has become smaller, but the UICollectionView numberOfItemsInSectionproperty has not changed to be a smaller number.
I have this function that sets the number of cells in the UICollectionView before runtime.
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems
}
This function to set the stickersToDisplay array (and I want to update the numberOfItemsInSection property here):
func setStickersToDisplay(category: String) {
clothingStickersToDisplay.removeAll()
for item in self.allClothingStickers {
let itemCategory = item.object["category"] as! String
if itemCategory == category {
clothingStickersToDisplay.append(item)
}
}
self.numberOfItems = clothingStickersToDisplay.count
}
This is the function that returns the cell to display:
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let sticker = clothingStickersToDisplay[indexPath.row]
let name = sticker.object["category"] as! String
var imageView: MMImageView =
createIconImageView(sticker.image, name: name)
cell.setImageV(imageView)
return cell
}
EDIT: Oh yeah, and I need to reload the UICollectionView with the new clothingStickersToDisplay at the same place that I update it's numberOfItemsInSection
I think what you should do is to clothingStickersToDisplay a global array declaration.
Instead of using that self.numberOfItems = clothingStickersToDisplay.count
Change this function to
func setStickersToDisplay(category: String) {
clothingStickersToDisplay.removeAll()
for item in self.allClothingStickers {
let itemCategory = item.object["category"] as! String
if itemCategory == category {
clothingStickersToDisplay.append(item) //<---- this is good you have the data into the data Structure
// ----> just reload the collectionView
}
}
}
In the numberOfItemsInSection()
return self.clothingStickersToDisplay.count

Resources