Expand and collapse multilevel sections in uitableview swift4 - ios

I want to expand and collpase the multilevel array in uitableview like the following
Cat1
SubCat1
Info 1
Info 2
SubCat2
Info 1
Info 2
SubCat3
Info 1
Info 2
Cat2
SubCat1
Info 1
Info 2
For that purpose I have done the following code.
struct CellData {
var opened = Bool()
var subCatTitle = String()
var subCatList = [String]()
}
struct MainModel {
var opened = Bool()
var categoryTitle = String()
var categoryList = [CellData]()
}
I have made the list
#IBOutlet var expandableThreeStageTableView: UITableView!
var arrayList = [CellData]()
var expandableList = [MainModel]()
func loadData(){
arrayList.append(CellData(opened: false, subCatTitle: "SubCat1", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat2", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat3", subCatList: ["Info1","Info2"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat4", subCatList: ["Info1"]))
expandableList.append(MainModel(opened: true, categoryTitle: "Cat1", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat2", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat3", categoryList: arrayList))
}
And delegate, datasource methods are given below
extension TextFieldAsSearchVC : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return expandableList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
Int) -> Int {
if expandableList[section].opened{
if expandableList[section].categoryList[section].opened{
return
expandableList[section].categoryList[section].subCatList.count////which extra count should return here
}else{
print("COUNT ",expandableList[section].categoryList.count)
return expandableList[section].categoryList.count +
1///here +1 is for catname + subcatname
}
}else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"TextFieldAsSearchVCCell", for: indexPath) as! TextFieldAsSearchVCCell
cell.lblValue.text =
expandableList[indexPath.section].categoryTitle
return cell
}else if indexPath.row <=
expandableList[indexPath.section].categoryList.count{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row -
1].subCatTitle
return cell
}
else{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row].
subCatList[indexPath.row]//how to access rows in subcategories
return cell
}
}
}
extension TextFieldAsSearchVC : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
IndexPath) {
if indexPath.row == 0{
if expandableList[indexPath.section].opened{
expandableList[indexPath.section].opened = false
//now reload the section
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}else{
expandableList[indexPath.section].opened = true
//now reload sections
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}
}else {
if
expandableList[indexPath.section].categoryList[indexPath.row].opened{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
false
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}else{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
true
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}
}
}
}
From above code I can expand and collapse the Categories but not Subcategories.. When I tried to click on Subcategories it gives me an error
*** Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid index path for use
with UITableView. Index paths passed to table view must contain exactly
two indices specifying the section and row. Please use the category on
NSIndexPath in NSIndexPath+UIKitAdditions.h if possible.'
How to deal with such type of logic?

The specific error you are getting occurs in this line:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
An IndexPath needs both, a row and a section; you're only providing a row. So it should be something like this:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(row: indexPath.row, section: indexPath.section)], with: .automatic)
If you really only need to reload the current indexPath, simply call it like this:
expandableThreeStageTableView.reloadRows(at:
[indexPath], with: .automatic)
This would fix the error you are getting, but I don't know if that solves your problem or not.

Related

Swift TableView insert row below button clicked

I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}

Scroll to the latest inserted row in UITableView using Realm Objects

I have the following code which is working fine, it gets a list of items from a list in Realm called groceryList and displays them on a UITableView in descending order based on the productName. What I would like to be able to do is scroll to the latest inserted row/item in the table, right now when a new item is inserted the user may not see it since the items are alphabetically reordered and the latest item may not be visible on the tableView.
How can I scroll to the latest inserted row/item in a UITableView?
Realm Objects:
class Item:Object{
#objc dynamic var productName:String = ""
#objc dynamic var isItemActive = true
#objc dynamic var createdAt = NSDate()
}
class ItemList: Object {
#objc dynamic var listName = ""
#objc dynamic var createdAt = NSDate()
let items = List<Item>()
}
UITableView:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var allItems : Results<Item>!
var groceryList : ItemList!
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
}
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
}
You can use Realm notifications to know when the data source Results has been modified, then update your table view from there and do the scrolling as well.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var allItems: Results<Item>!
var groceryList: ItemList!
var notificationToken: NotificationToken? = nil
deinit {
notificationToken?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
observeGroceryList
}
func updateResultsList(){
if let list = groceryList {
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
func observeGroceryList() {
notificationToken = allItems.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
self?.tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
self?.tableView.beginUpdates()
self?.tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
self?.tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.endUpdates()
if let lastInsertedRow = insertions.last {
self?.tableView.scrollToRow(at: insertions.last, at: .none, animated: true)
}
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
print("\(error)")
}
}
}
}
Add below code as extension of tableview.
extension UITableView {
func scrollToBottom() {
let sections = numberOfSections-1
if sections >= 0 {
let rows = numberOfRows(inSection: sections)-1
if rows >= 0 {
let indexPath = IndexPath(row: rows, section: sections)
DispatchQueue.main.async { [weak self] in
self?.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
}
}
Now simply use it in your method:
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false
yourTableView.scrollToBottom()
}
}
Just use this method where you want, it should be scroll down.
yourTableView.scrollToBottom()

Selected row from each section of UITableView ( Multiple Selection )

I have used tableview(grouped).
So i need to select one row from the each section of UITableviewSection.
So for that i have tableview and one submit button .So i need to check when i click on the submit button i need to check whether i have selected one row from the each section ,if not then show alert as not selected the section number.How to check?
This is my data.
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"]
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"]
},
{
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
QuestionModel:-
class QuestionListModel: NSObject {
var selected = false
var dataListArray33:[NH_OptionsModel] = []
var id:Int!
var question:String!
var buttontype:String!
var options:[String]?
var v:String?
var optionsModelArray:[OptionsModel] = []
init(dictionary :JSONDictionary) {
guard let question = dictionary["question"] as? String,
let typebutton = dictionary["button_type"] as? String,
let id = dictionary["id"] as? Int
else {
return
}
if let options = dictionary["options"] as? [String]{
print(options)
print(options)
for values in options{
print(values)
let optionmodel = OptionsModel(values: values)
self.optionsModelArray.append(optionmodel)
}
}
self.buttontype = typebutton
self.question = question
self.id = id
// print(self.dataListArray33)
}
}
optionModel:-
class OptionsModel: NSObject {
var isSelected:Bool? = false
var v:String?
var values:String?
init(values:String) {
self.values = values
print( self.values)
}
ViewModel:-
func numberOfSections(tableView: UITableView) -> Int{
print((datasourceModel.dataListArray?.count)!)
return (datasourceModel.dataListArray?.count)!
}
func titleForHeaderInSection(atsection section: Int) -> NH_QuestionListModel {
return datasourceModel.dataListArray![section]
}
func numberOfRowsIn(section:Int) -> Int {
print( datasourceModel.dataListArray?[section].optionsModelArray.count ?? 0)
return datasourceModel.dataListArray?[section].optionsModelArray.count ?? 0
// return self.questionsModelArray?[section].optionsModelArray.count ?? 0
}
func datafordisplay(atindex indexPath: IndexPath) -> NH_OptionsModel{
print(datasourceModel.dataListArray![indexPath.section].optionsModelArray[indexPath.row])
return datasourceModel.dataListArray![indexPath.section].optionsModelArray[indexPath.row]
}
func question(answer:String) {
print(questions)
questions.append(answer)
print(questions )
}
func questionlist(answer:String) {
print( questionlist )
questionlist.append(answer)
print( questionlist )
}
func answer(answer:String) {
answers.append(answer)
print(answers)
}
and finally viewController:-
func numberOfSections(in tableView: UITableView) -> Int {
return questionViewModel.numberOfSections(tableView: tableView)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = "HeaderCell"
var headercell: questionheader! = tableView.dequeueReusableCell(withIdentifier: identifier) as? questionheader
if headercell == nil {
tableView.register(UINib(nibName: "questionheader", bundle: nil), forCellReuseIdentifier: identifier)
headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
}
headercell.setReviewData(reviews:questionViewModel.titleForHeaderInSection(atsection:section))
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionViewModel.numberOfRowsIn(section: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell: QuestionListCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? QuestionListCell
if cell == nil {
tableView.register(UINib(nibName: "QuestionListCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_QuestionListCell
}
cell.contentView.backgroundColor = UIColor.clear
let questionsModel = questionViewModel.titleForHeaderInSection(atsection:indexPath.section)
print(questionsModel.buttontype)
questionViewModel.button = questionsModel.buttontype
cell.setOptions(Options1: questionViewModel.datafordisplay(atindex: indexPath))
print("Section \(indexPath.section), Row : \(indexPath.row)")
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
print("Section \(indexPath.section), Row : \(indexPath.row)")
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.isSelected)
cell?.setOptions(OptionsSelected:questionViewModel.datafordisplay(atindex: indexPath))
print(model.isSelected)
questionViewModel.isselected = model.isSelected!
let section = indexPath.section
let index = indexPath.row
print(section)
print(index)
if !questionViewModel.selectedIndexPaths.contains(indexPath) {
questionViewModel.selectedIndexPaths.append(indexPath)
print(questionViewModel.selectedIndexPaths.append(indexPath))
let questionModel = questionViewModel.titleForHeaderInSection(atsection: section)
print(questionModel.question)
questionViewModel.question = questionModel.question
questionViewModel.questionlist(answer: questionViewModel.question!)
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.values)
questionViewModel.answer(answer: model.values!)
let value: Int = questionModel.id
let string = String(describing: value)
//let x: Int? = Int(model.id)
questionViewModel.question_id = string
questionViewModel.question(answer: questionViewModel.question_id!)
print(questionModel.id)
// append the selected index paths
} // if indexPath.section == section {
// questionViewModel.indexPath(indexPaths: index)
// }
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = questionViewModel.selectedIndexPaths.index(of: indexPath) {
print(index)
questionViewModel.selectedIndexPaths.remove(at: index)
}
}
According to this i got the output .
But i have button action in viewcontroller.
#IBAction func forward(_ sender: AnyObject) {
}
In this button action i need to check whether from each section did i selected one row or not .if not show alert .How to do
my current didselect method :-
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.isSelected)
cell?.setOptions(OptionsSelected:questionViewModel.datafordisplay(atindex: indexPath))
print(model.isSelected)
questionViewModel.isselected = model.isSelected!
let section = indexPath.section
let index = indexPath.row
print(section)
print(index)
if !questionViewModel.selectedIndexPaths.contains(indexPath) {
questionViewModel.selectedIndexPaths.append(indexPath)
print(questionViewModel.selectedIndexPaths.append(indexPath))
let questionModel = questionViewModel.titleForHeaderInSection(atsection: section)
print(questionModel.question)
questionViewModel.question = questionModel.question
questionViewModel.questionlist(answer: questionViewModel.question!)
let cell = tableview.cellForRow(at: indexPath) as? NH_QuestionListCell
let model = questionViewModel.datafordisplay(atindex: indexPath)
print(model.values)
questionViewModel.answer(answer: model.values!)
let value: Int = questionModel.id
let string = String(describing: value)
//let x: Int? = Int(model.id)
questionViewModel.question_id = string
questionViewModel.question(answer: questionViewModel.question_id!)
print(questionModel.id)
}
I have 3 array
According to this didselect method:-
ex:-for section 1 :-i selected 1st row so the data append as below.
questionlist:["How r u?"]
answelist:["fine"]
But suppose i think that i need 2nd indexpath ,so i need to remove the previous appended data from arrays and append the current data .As below:
questionlist:["How r u?"]
answelist:["not well"]
And next for section 2 : i selected 1st indexpath.row data .then that data is append.So i need to get as below:-
questionlist:["How r u?","Gender"]
answelist:["not well","Male"]
Here selecting i think that i need the 2nd option then remove the added indexpath.row data from array and show as:-
questionlist:["How r u?","Gender"]
answelist:["not well","Female"]
Such way how to set?
you can update your model based on the selection like
"data":[
{
"question": "Gender",
"options": ["Male","Female"],
"optionSelected": "Male"
}
]
and on Submit , check data for selections
The table view has a property to get selected index paths. You can use all native components for that. What you need is to deselect an item at index path where one is already selected in a certain section. You also just need to then check that the number of selected index paths is the same as number of arrays in your data source.
Check something like this:
var dataSource: [[Any]]!
var tableView: UITableView!
func didSelectRowAt(_ indexPath: IndexPath) {
guard let selectedPaths = tableView.indexPathsForSelectedRows else { return } // We need to have selected paths
guard selectedPaths.contains(indexPath) == false else { return } // The same cell being selected
let previouslySelectedCellIndexPaths: [IndexPath] = selectedPaths.filter { $0.section == indexPath.section && $0 != indexPath } // Getting all selected index paths within this section
previouslySelectedCellIndexPaths.forEach { tableView.deselectRow(at: $0, animated: true) } // Deselect waht was previously selected
}
/// Will return array of selected objects only if all sections have a selected index
///
/// - Returns: A result array
func getSelectionData() -> [Any]? {
guard let selectedPaths = tableView.indexPathsForSelectedRows else { return nil } // We need to have selected paths
guard selectedPaths.count == dataSource.count else { return nil } // This should prevent missing selections assuming all index paths are unique in sections
return selectedPaths.map { dataSource[$0.section][$0.row] } // Map selected index paths back to objects
}
I tried to use kind of minimum code to show all of this. It is all commented so you can see row by row what goes on.
You might want to check is all sections are unique the second method but it is not needed if the first one is always used.
You can store selected indexPath in an array. OnClick of submit just loop through array and check either at least one element is from each section.
FYI : indexPath contains section info also.
Declare an mutable array and allocate in viewDidLoad.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[anArray addObject:indexPath];
}
on Submit action follow this, you can improvise based on your requirement
-(void)onSubmitAction{
[anArray addObject:indexPath];
NSMutableArray *countOfSection=[[NSMutableArray alloc]init];
for (NSIndexPath*indexPath in anArray ) {
if(![anArray containsObject:indexPath.section])
[countOfSection addObject:indexPath.section];
}
if(countOfSection.count == self.tableview.numberOfSections){
//write your code
}else{
// show alert
}
}
Step 1 : Create Global Variable
var selectedIndexPaths = [IndexPath]()
Step 2: Add UITableView Property
tableView.allowsMultipleSelection = true
Step 3 : Implement the delegate methods
//On Selection
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndexPathAtCurrentSection = selectedIndexPaths.filter({ $0.section == indexPath.section})
for indexPath in selectedIndexPathAtCurrentSection {
tableView.deselectRow(at: indexPath, animated: true)
if let indexOf = selectedIndexPaths.index(of: indexPath) {
selectedIndexPaths.remove(at: indexOf)
}
}
selectedIndexPaths.append(indexPath)
}
// On DeSelection
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = selectedIndexPaths.index(of: indexPath) {
selectedIndexPaths.remove(at: index)
}
}
Step 4: Getting Selected IndexPaths with sections
#IBAction func forward(sender:Any){
let totalSections = questionViewModel.numberOfSections(tableView: tableView)
for section in 0..<totalSections {
if (selectedIndexPaths.filter({ $0.section == section}).count >= 1) {
continue
} else {
// Show alert
print("Please select item at",(section))
return
}
}
}

UITableView cell insertion failure

I am trying to make a UITableView that can have expandable header views. When you press a button inside of the header view, the following function gets executed:
func expandTheCell(_ sender: UIButton) {
self.tableView.beginUpdates()
if sender.isExpanded == false {
postsArray.append("Hello")
tableView.reloadData()
print("Array Count: \(postsArray.count)")
self.tableView.insertRows(at: [IndexPath.init(row: 0, section: sender.tag)], with: .fade)
} else {
print("test")
}
self.tableView.endUpdates()
}
This are some table view functions:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsArray.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
When I try to insert the rows, I get the following error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 1
How come I can't insert the cells? What am I doing wrong?
I think problem is due to having same dataSource array for the sections, in your case postsArray , when you append the item to postsArray on clicking the button , same postsArray is used for other sections, so after you insert the row in section 0 , section 1 complains that number of rows for me before and after insert operation is not same, but section 0 doesnt complain because it has same number of rows and number of items in postsArray
Now this problem can be solved in two ways:
First way is that you can insert the row for other sections as well, then all the sections have equal number of rows as the number of elements in postsArray
Second way is that you have different dataSource arrays for all the sections , like postsArray1 for section 1, postsArray2 for section 2 and same for other sections. Now in this case you dont need to insert rows for other sections , since each section has different dataSource array, changing one wont affect others.
I have made a simple project to demonstrate the above theory:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(buttonTapped(_:)))
self.navigationItem.rightBarButtonItem = addButton
}
var valuesFirstSection = ["value1", "value2", "value3"]
var valuesSecondSection = ["value1Second", "value2Second", "value3Second"]
//if you want to have the same dataSource array then use this
//var sharedValues = ["value1Shared", "value2Shared", "value3Shared"] // shared dataSource array
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return valuesFirstSection.count
}else {
return valuesSecondSection.count
}
// //if you want to have the same dataSource array then
//use this
//return sharedValues.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = valuesFirstSection[indexPath.row]
}else {
cell.textLabel?.text = valuesSecondSection[indexPath.row]
}
return cell
//if you want to have the same dataSource array then
//use this
//cell.textLabel?.text = sharedValues[indexPath.row]
//return cell
}
func buttonTapped(_ sender: UIBarButtonItem) {
//if you want to have the same dataSource array then
//you need to insert the rows for other sections as well
// sharedValues.insert("NewValue0", at: 0)
// self.tableView.insertRows(
// at: [IndexPath(row: 0, section: 0),
// IndexPath(row: 0, section: 1)
// ],
// with: .automatic)
valuesFirstSection.insert("NewValue0", at: 0)
self.tableView.insertRows(
at: [IndexPath(row: 0, section: 0)
],
with: .automatic)
}
}
Hope this helps.

Insert Row At End Of Section Error

I'm trying to allow the user to insert rows at the end of sections but I'm getting an error. However everything seems correct so there must be something that I'm not seeing. Can someone point me in the right direction. Attached is my code, the error, and a screenshot of the table.
//
// Test.swift
// Table Views
//
// Created by Deion Long on 7/18/15.
// Copyright (c) 2015 Deion Long. All rights reserved.
//
import UIKit
var array = ["Section 1", "Section 2"]
extension UITableView {
func indexPathForView (view : UIView) -> NSIndexPath? {
let location = view.convertPoint(CGPointZero, toView:self)
return indexPathForRowAtPoint(location)
}
}
class Test: UIViewController, UITableViewDelegate, UITableViewDataSource {
var things:NSMutableArray = ["hi", "bye", "kie"]
#IBAction func editBtnClicked(sender: AnyObject) {
//When not in editing mode already set editing to true
if(table.editing == false){
self.editing = true
println("hi")
table.setEditing(true, animated: true)
}else{
self.editing = false
table.setEditing(false, animated: true)
}
}
#IBOutlet weak var table: UITableView!
#IBAction func doneButton(sender: AnyObject) {
table.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
table.allowsSelectionDuringEditing = true
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return array.count // This was put in mainly for my own unit testing
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return array[section]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var addRow = self.editing ? 1 : 0
println(addRow)
return addRow + things.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("textInputCell3") as! DetailCell
if (indexPath.row >= things.count && self.editing) {
cell.configure(text: "Add Row", placeholder: "Enter")
} else{
cell.configure(text: things.objectAtIndex(indexPath.row) as! String, placeholder: "Enter")
}
return cell
}
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.table.setEditing(editing, animated: animated)
if(editing){
table.beginUpdates()
for var i = 0; i < self.table.numberOfSections(); i++
{
var lastRow = table.numberOfRowsInSection(i)
var lastIndex = NSIndexPath(forRow: lastRow, inSection: i)
table.insertRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)
}
table.endUpdates()
}
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if(indexPath.row >= things.count){
return UITableViewCellEditingStyle.Insert
}else{
return UITableViewCellEditingStyle.Delete
}
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
things.removeObject(indexPath.row)
table.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
else if (editingStyle == UITableViewCellEditingStyle.Insert) {
things.insertObject("123", atIndex: self.things.count)
table.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
ERROR: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
The error message says it all. You mustn't insert the row until you've updated the model data to have the extra row. You may be confusing yourself with your sections here; practice with just one section first. Your model does not seem very well designed to distinguish the two sections.
In the setEditing: you are inserting rows inside the beginUpdates and endUpdates function of UITableView
table.beginUpdates()
for var i = 0; i < self.table.numberOfSections(); i++ {
var lastRow = table.numberOfRowsInSection(i)
var lastIndex = NSIndexPath(forRow: lastRow, inSection: i)
table.insertRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)
}
table.endUpdates()
When you are inserting rows make sure that you are updating your model object things. If you just want to reload the data of the cell use
table.reloadRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)

Resources