How to insert multiple sections in collectionview at once? - ios

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

Related

How to use single array in multiple collectionView sections?

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
}
}

Invalid UICollectionView Update

I have a sectioned uicollectionview with nested cells that can be deleted on tap.
My goal is to display cells from the datasource if available, and if not, to show a "placeholder" cell stating no data is currently available.
My issue emerges on delete of a last remaining cell under a given section. My numberOfItemsInSection is 1 specifically for the "placeholder" cell but should be 0 to align to the datasource of 0 where no more data is available.
Any thoughts on workarounds?
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if sectionItems[section].count == 0 {
return 1
}
else {
return sectionItems[section].count
}
}
func onTap() {
self.sectionItems[indexPath.section]?.remove(at: indexPath.item)
self.exampleCollectionView.deleteItems(at: [indexPath])
}
When you delete your last item, you need to instead reload for the "No Items" cell. Something like this should work:
func onTap() {
sectionItems[indexPath.section]?.remove(at: indexPath.item)
let isEmpty = sectionItems[indexPath.section]?.count ?? 0 == 0
exampleCollectionView.performBatchUpdates({
if isEmpty {
self.exampleCollectionView.reloadItems(at: [indexPath])
} else {
self.exampleCollectionView.deleteItems(at: [indexPath])
}
}, completion: nil})
}
Essentially you need to tell the collectionView that there will still be one cell there after the update when the last item is removed.

iOS: Invalid update: invalid number of rows in section n

I am building an iOS app, basically the user create an item by pressing the "+" button and then the app should put the new item in according section of the table based on the location of the item. However I got the error: Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update. Here is my code, thank you
let sections = ["Bathroom", "Bedroom", "Dining Room", "Garage", "Kitchen", "Living Room"]
#IBAction func addNewItem(_ sender: UIBarButtonItem) {
/*
//Make a new index path for the 0th section, last row
let lastRow = tableView.numberOfRows(inSection: 0)
let indexPath = IndexPath(row: lastRow, section: 0)
//insert this new row into the table
tableView.insertRows(at: [indexPath], with: .automatic)*/
//Create a new item and add it to the store
let newItem = itemStore.createItem()
var Num: Int = 0
if let index = itemStore.allItems.index(of: newItem) {
switch newItem.room {
case "Bathroom":
Num = 0
print("I am in here")
case "Bedroom":
Num = 1
case "Dining Room":
Num = 2
case "Garage":
Num = 3
case "Kitchen":
Num = 4
case "Living Room":
Num = 5
default:
Num = 0
print("I am in Default")
}
let indexPath = IndexPath(row: index, section: Num)
tableView.insertRows(at: [indexPath], with: .automatic)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create an instance of UITableViewCell, with default appearance
//let cell = UITableViewCell(style: .value, reuseIdentifier: "UITableViewCell")
//get a new or recycled cell
//if indexPath.row < itemStore.allItems.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
//Set the text on the cell with the decription of the item
//that is at the nth index of items, where n = row this cell
//will appear in on the table view
let item = itemStore.allItems[indexPath.row]
if item.name == "" {
cell.nameLabel.text = "Name"
} else {
cell.nameLabel.text = item.name
}
if item.serialNumber == nil {
cell.serialNumberLabel.text = "Serial Number"
} else {
cell.serialNumberLabel.text = item.serialNumber
}
cell.valueLabel.text = "$\(item.valueInDollars)"
cell.roomLabel.text = item.room
if item.valueInDollars < 50 {
cell.valueLabel.textColor = UIColor.green
}else if item.valueInDollars >= 50 {
cell.valueLabel.textColor = UIColor.red
}
cell.backgroundColor = UIColor.clear
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let LocationName = sections[section]
return LocationName
}
thank you so much for your time!
and this is how I create item
#discardableResult func createItem() -> Item {
let newItem = Item(random: false)
allItems.append(newItem)
return newItem
}
The problem is that you should insert the item in the array first:
itemStore.allItems.append(newItem)
Also, there is a difference between sections and rows in numberOfRowsInSection(return number of rows for every section) you have a switch that returns the same number, it should be
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
Edit :
The problem is when the table loads there is 0 rows for all the sections ( itemStore.allItems.count is zero ), when you try to insert a row say at section 0 , row 0 -- the dataSource must be updated only for that section , which is not happen in your case as it's the same array that is returned for all sections , so you must either have an array of array where inner array represent number of rows so addition/deletion from it doesn't affect other ones ,,,,, or lock the insert to say section 0 like this
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0 ) {
return itemStore.allItems.count
}
return 0
}
in this edit i inserted in 2 sections 0 and 2 with no crash because i handled numberOfRowsInSection to return old numbers for old section that why to be able to insert in all sections you must have a different data source array or manage from numberOfRowsInSection , see edited demo here Homepwner
Instead of setting footer in viewDidLoad implement this method
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if(section == 5 ) {
let textLabel = UILabel()
textLabel.text = "No more Item"
return textLabel
}
return nil
}
There is something terribly wrong with your data source. Your numberOfRow for all sections is the same. i.e. itemStore.allItems.count. Which means you are setting the same number of rows in each section.
The main issue is, while adding a new item, you are inserting a new row in the specific section which means
new number of rows for section = previous number of rows in section +
1
However, there is no addition in the data source.
So according to the code, you have inserted a new row, but your data source has one record less since you didn't add the item there.
So I would like you do the following in order of these steps :
Add item in required data source.
inserRowInSection
Update : Remove the condition in cellForRowAtIndexPath :
if indexPath.section <= 1 and it's else block. You don't need that. If number of sections is less than 1, it won't be called. Moreover it's the else block which is getting called all the time because you already have sections in your code. So if case will never be called.
So I got the issue. In itemStore.createItem() the value for room is always Room. In the switch-case, you never have Room case. So it always falls in default case. You need to fix that.
#Jacky.S I downloads your code and try to debug it.Fist look our error properly.
reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0)
So It says before update, your sections has 0 rows. Now you try to update you tableview using this piece of code.
print(indexPath)
tableView.insertRows(at: [indexPath], with: .automatic)
I just added the print statement for debugging purpose and it prints [0, 0] .So you say your tableview to insert 0th row on section 0.
Now when you insert any row into tableview it calls the delegate method.Now look at the below delegate method.
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
if section == 0{
print("0",itemStore.allItems.count)
}
if section == 1{
print("1",itemStore.allItems.count)
}
// print(itemStore.allItems)
return itemStore.allItems.count
}
In your code you just return itemStore.allItems.count.I added the above code for debugging.Now look that itemStore.allItems.count always return 1.So for first section it works perfectly because few times ago you insert a row in first section whose index is [0 = row, 0 = section].So you previously returned rows for section 0 is equal to the recently return rows for section 0.But When it comes for section 1 you previously return no row that means your section 1 has no rows in previous but in your above delegate method you also return 1 row for section 1.Which is conflicting because in previous you never update or return any rows for section 1.That's why you code crash and and say that
your number of rows contained in an existing section after the update is 1 which must be equal to the number of rows contained in that section before the update. Which is 0.
So this is the explanation for you crash.
Now, to recover from crash your allItems should be a dictionary or an array of array.
var allItems = [[Item]]()
or
var allItems = ["String":[Item]]()
guess allItems element is something like this
allItems = ["bathroom":[item1, item2, item3],"bedroom":[item1, item2, item3, item4]]
so here you have 2 section one is bathroom and another is bedroom.First section have 3 row and second one have 4 row and in you tableview delegate method you have to return 3 for section 0 and 4 for section 1

UITableView with different optional sections?

I am looking for a "good" way to solve some special requirements:
I have an UITableView with different sections, for example:
Base Data
About me
Interests
Images
Base Data contains always values (but there is still an variable row count) - and all other "Categories" could contain rows, or still could be empty. If there is no data, the category should be not shown.
No my first idea to solve that is:
Create all possible categories (but that could be 20 or more) - and do something like that:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count:Int = 0
switch (section) {
case 0:
count = baseHeaders.count
case 1:
if(mapItem.courses?.count > 0) {
count = mapItem.courses!.count
}
break;
default:
count = 0
}
return count
}
And ill check also with: titleForHeaderInSection if the count is null, and return then no header for the section.
BUT: is that a good way? My concern is about creating 20 sections and just 2 are used. Is there another, better way? Can i create sections manually? If yes, should i? So that only the base category is visible, and append everything else if there is data available.
This looks like my way of approaching such problems. I'm using enums (Obj-C & especially Swift) to handle and identify my Sections and I always return the full amount of potential sections:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return FormSection.count // enum function
}
In func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int, however, I switch the unused sections off by returning 0 rows.
The benefit I saw after struggling with your type of dynamic tables was that all sections are always at the same index which made cell management relatively easy:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let section:FormSection = FormSection(rawValue:indexPath.section)!
switch section {
case .Header:
//…
default:
//…
}
}
The same goes for section headers/footers:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
switch section {
case FormSection.Header.rawValue:
return nil
case FormSection.RoomSetup.rawValue where foo == false:
return nil
default:
// return header with title = FormSection(rawValue: section)?.headerTitle()
// Swift enums ftw ;)
}
And the number of rows is calculated/fetched at runtime:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section:FormSection = FormSection(rawValue:section)!
switch section {
case .Section1:
return fooExpanded ? (numberOfFoo) : 0
case .Section2:
return model.barCount()
default:
return 1
}
}

How can I update the number of rows in a TableView in Swift?

Suppose I initially set up my number of rows as such:
func tableView(tableView:UITableView!, numberOfRowsInSection section: Int) -> Int {
return 100
}
If I add something to the table and call
self.tableView.reloadData()
How do I get the number of rows to update to 101? Can I somehow call that function again but return 101?
First I suggest you to learn MVC design pattern,this is helpful when you learn ios coding.
Then,when you use dynamic tableview, I think you should keep data source of tableveiw.
For example
In your tableviewcontroller
keep an Array to be your data source:
var Array = [something you want]
Then in
func tableView(tableView:UITableView!, numberOfRowsInSection section: Int) -> Int {
return array.count
}
Then if you want to change UI,just change this array,and reload or insert/delete rows

Resources