Swift, XCode6: Adding and deleting new sections in uitableview - ios

I am trying to create a view which has a button at the top and a uitableview right beneath it. The uitableview has a section of 3 rows by default. What I wish to achieve is that whenever the button is pressed, I wish to add a new section of 4 rows. The first row of every newly added section should delete the section on selection. Sections are appended to the bottom of the existing sections as the top button is pressed. However, they can be deleted from the middle.
I have posted my code below. Sections are being added fine. However, deletion is not happening properly. If I select the 0th row of a newly added section, nothing happens. But if I select the 0th row and then select the 1st row of the same section, the section gets deleted. I am also not sure about my add new section logic. I am new to iOS development, please help me in getting this right, thanks in advance!
#IBOutlet weak var tableView: UITableView!
var retailProduct: RetailProduct?
let mandatoryVariants = 1
var additionalVariants = 0
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if indexPath.section == 0 {
switch indexPath.row {
case 0:
var variantImagesCell = tableView.dequeueReusableCellWithIdentifier("variantImagesCell") as! VariantImagesCell
cell = variantImagesCell
break
case 1:
var variantColorCell = tableView.dequeueReusableCellWithIdentifier("addColorCell") as! AddColorCell
variantColorCell.colorLabel.text = "Color"
if let color = retailProduct?.colorVariants[0].color {
variantColorCell.colorName.text = color.description
} else {
variantColorCell.colorName.text = "Select Color"
}
cell = variantColorCell
break
case 2:
var variantQuantityCell = tableView.dequeueReusableCellWithIdentifier("addQuantityCell") as! AddQuantityCell
variantQuantityCell.addQuantityLabel.text = "Size and Quantity"
variantQuantityCell.quantity.text = String(0)
cell = variantQuantityCell
break
default:
break
}
} else {
switch indexPath.row {
case 0:
var deleteVariantCell = tableView.dequeueReusableCellWithIdentifier("deleteCell") as! DeleteCell
cell = deleteVariantCell
break
case 1:
var variantImagesCell = tableView.dequeueReusableCellWithIdentifier("variantImagesCell") as! VariantImagesCell
cell = variantImagesCell
break
case 2:
var variantColorCell = tableView.dequeueReusableCellWithIdentifier("addColorCell") as! AddColorCell
variantColorCell.colorLabel.text = "Color"
if let color = retailProduct?.colorVariants[0].color {
variantColorCell.colorName.text = color.description
} else {
variantColorCell.colorName.text = "Select Color"
}
cell = variantColorCell
break
case 3:
var variantQuantityCell = tableView.dequeueReusableCellWithIdentifier("addQuantityCell") as! AddQuantityCell
variantQuantityCell.addQuantityLabel.text = "Size and Quantity"
variantQuantityCell.quantity.text = String(0)
cell = variantQuantityCell
break
default:
break
}
}
//println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ " + String(indexPath.section) + " " + String(indexPath.row))
return cell
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ " + String(indexPath.section) + " " + String(indexPath.row))
if (indexPath.section > 0) && (indexPath.row == 0) {
additionalVariants--
let indexSet = NSMutableIndexSet()
indexSet.addIndex(indexPath.section)
self.tableView.deleteSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
#IBAction func addColorVariants(sender: AnyObject) {
additionalVariants++
let indexSet = NSMutableIndexSet()
indexSet.addIndex(additionalVariants)
//self.tableView.insertSections(NSIndexSet(indexesInRange: NSMakeRange(1, 5)), withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.beginUpdates()
self.tableView.insertSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
//self.tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Automatic)
//self.tableView.reloadData()
}

Related

UITableView reloadData() causing reload to UISearchBar inside every sections

I have a tableview which has 2 sections. Both of the sections have UISearchBar in the indexPath.row 0 and the rest of the rows in each section populate the list of array.
Whenever I type some text in search bar every time the searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) delegate method gets called and inside the delegate method I call tableView.reloadData() to reload the search results in tableview.
Now the problem is each time the tableView reloads the UISearchBar reloads too (as UISearchbar is in row number 1) and every time the SearchBar keypad Resigns.
Instead of doing tableView.reloadData() I even tried to reload every row except the first one using bellow code
let allButFirst = (self.tableView.indexPathsForVisibleRows ?? []).filter { $0.section != selectedSection || $0.row != 0 }
self.tableView.reloadRows(at: allButFirst, with: .automatic)
But no luck. App gets crashed saying
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 2 into section 0, but there are only 2 rows in section 0 after the update'
You are probably changing the data source and then you are reloading rows at index paths what doesn't exist yet.
It is not so easy, but let's have an example: Before you start typing, the search result will contain something like this:
["aa", "ab", "ba", "bb"]
Then you will type "a" to the search bar and data source changes into:
["aa", "ab"]
tableView.deleteRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic)
then you delete everything in this searchbar and your data source will change to the default: ["aa", "ab", "ba", "bb"]
so in this case you need to call:
tableView.insertRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic)
I created some working example - without storyboard source, I believe it is pretty simple to recreated it according this class.
class SearchCell: UITableViewCell {
#IBOutlet weak var textField:UITextField?
}
class TextCell: UITableViewCell {
#IBOutlet weak var label:UILabel?
}
class ViewController: UIViewController, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var tableView: UITableView?
weak var firstSectionTextField: UITextField?
var originalDataSource:[[String]] = [["aa","ab","ba","bb"], ["aa","ab","ba","bb"]]
var dataSource:[[String]] = []
let skipRowWithSearchInput = 1
override func viewDidLoad() {
super.viewDidLoad()
dataSource = originalDataSource
tableView?.tableFooterView = UIView()
tableView?.tableHeaderView = UIView()
}
func numberOfSections(in tableView: UITableView) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource[section].count + skipRowWithSearchInput
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0, let cell = tableView.dequeueReusableCell(withIdentifier: "search", for: indexPath) as? SearchCell {
cell.textField?.removeTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
cell.textField?.addTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
if indexPath.section == 0 {
firstSectionTextField = cell.textField
}
return cell
} else if let cell = tableView.dequeueReusableCell(withIdentifier: "text", for: indexPath) as? TextCell {
cell.label?.text = dataSource[indexPath.section][indexPath.row - skipRowWithSearchInput]
return cell
} else {
return UITableViewCell()
}
}
#objc func textFieldDidChangeText(sender: UITextField) {
let section = sender == firstSectionTextField ? 0 : 1
let text = sender.text ?? ""
let oldDataSource:[String] = dataSource[section]
//if the search bar is empty then use the original data source to display all results, or initial one
let newDataSource:[String] = text.count == 0 ? originalDataSource[section] : originalDataSource[section].filter({$0.contains(text)})
var insertedRows:[IndexPath] = []
var deletedRows:[IndexPath] = []
var movedRows:[(from:IndexPath,to:IndexPath)] = []
//resolve inserted rows
newDataSource.enumerated().forEach { (tuple) in let (toIndex, element) = tuple
if oldDataSource.contains(element) == false {
insertedRows.append(IndexPath(row: toIndex + skipRowWithSearchInput, section: section))
}
}
//resolve deleted rows
oldDataSource.enumerated().forEach { (tuple) in let (fromIndex, element) = tuple
if newDataSource.contains(element) == false {
deletedRows.append(IndexPath(row: fromIndex + skipRowWithSearchInput, section: section))
}
}
//resolve moved rows
oldDataSource.enumerated().forEach { (tuple) in let (index, element) = tuple
if newDataSource.count > index, let offset = newDataSource.firstIndex(where: {element == $0}), index != offset {
movedRows.append((from: IndexPath(row: index + skipRowWithSearchInput, section: section), to: IndexPath(row: offset + skipRowWithSearchInput, section: section)))
}
}
//now set dataSource for uitableview, right before you are doing the changes
dataSource[section] = newDataSource
tableView?.beginUpdates()
if insertedRows.count > 0 {
tableView?.insertRows(at: insertedRows, with: .automatic)
}
if deletedRows.count > 0 {
tableView?.deleteRows(at: deletedRows, with: .automatic)
}
movedRows.forEach({
tableView?.moveRow(at: $0.from, to: $0.to)
})
tableView?.endUpdates()
}
}
the result:
If do you need to clarify something, feel free to ask in comment.
Try this-
tableView.beginUpdates()
//Do the update thing
tableView.endUpdates()
It worked.
I took two sections one for search field and another for reloading data (rows populating data).
I took separate custom cell for search and took outlet in that class itself.
and in viewForHeaderInSection I used tableView.dequeueReusableCell(withIdentifier:) and returned customCell.contentView
Then I called tableview.ReloadData() in searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
It worked without problem.

How to properly insert new row in correct specific row cell in the table view (swift)?

so here is my question..
i was trying to insert a row in some index with add button inside that row..
example : i click add button at row 1, then it should add new row below that row, if i click add button at row 3 it should add new row below it..
the problem is after i add example 5 new row, then sometimes if i click add in row 5 (this is new row added with new add button to inside it) it keep adding below last row that clicked.. (i click add on row 3 then it has new row below it, then i click add on the new row it keep adding below row 3)
Steps in my GIF :
I add new first row it adding new row below it.. (correct behavior)
I try add new row in last row but keep adding new row below row 3.
(wrong behavior)
I try click again add row in row 4 it correctly add
new row below it, and click on row 3 , it add correctly new row
below it. (correct behavior)
Why i keep get problem in number 2 ?
Here is my code :
the problem is in case ingredient and step.. i already give rowId in the cell using indexPath so each cell has their own id.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
switch item.type {
case .title:
if let cell = tableView.dequeueReusableCell(withIdentifier: PopUpTitleCell.identifier, for: indexPath) as? PopUpTitleCell {
return cell
}
case .category:
if let cell = tableView.dequeueReusableCell(withIdentifier: PopUpCategoryCell.identifier, for: indexPath) as? PopUpCategoryCell {
return cell
}
case .ingredient:
if let item = item as? PopUpModelIngredient, let cell = tableView.dequeueReusableCell(withIdentifier: PopUpIngredientStepCell.identifier, for: indexPath) as? PopUpIngredientStepCell {
cell.cellId = indexPath.row
cell.item = item
cell.delegate = self
return cell
}
case .step:
if let item = item as? PopUpModelStep, let cell = tableView.dequeueReusableCell(withIdentifier: PopUpIngredientStepCell.identifier, for: indexPath) as? PopUpIngredientStepCell {
cell.cellId = indexPath.row
cell.item = item
cell.delegate = self
return cell
}
case .post:
if let cell = tableView.dequeueReusableCell(withIdentifier: PopUpPostCell.identifier, for: indexPath) as? PopUpPostCell {
return cell
}
}
return UITableViewCell()
}
this one is the delegate handler that i create to pass which cell that click the add button
func addedNewCell(inSection: Int, cellId: Int) {
popUpTableView?.beginUpdates()
let item = items[inSection]
if let item = item as? PopUpModelIngredient {
item.ingredientsCell += 1
popUpTableView?.insertRows(at: [IndexPath(row: cellId, section: inSection)], with: .automatic)
}else if let item = item as? PopUpModelStep {
item.stepsCell += 1
popUpTableView?.insertRows(at: [IndexPath(row: cellId, section: inSection)], with: .automatic)
}
popUpTableView?.endUpdates()
print("added in section \(inSection) and cell id \(cellId)")
}
the last one is the cell itself
class PopUpIngredientStepCell: UITableViewCell {
var cellId: Int?
var delegate: CellAddedDelegate?
var item: PopUpViewModelItem?
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
#IBAction func addMoreCell(_ sender: Any) {
print("Cell id for this one is : ", cellId)
if item?.type == .ingredient {
delegate?.addedNewCell(inSection: 2, cellId: cellId! + 1)
}else{
delegate?.addedNewCell(inSection: 3, cellId: cellId! + 1)
}
}
}
Here is the GIF and the number of the step information..
GIF Link
or the real low quality gif here

How to get the textvalue of each dynamically created row in UITableView in swift

As shown in below image,i created a prototype cell with label and textfield in tableviewController.
Each row is created at runtime as shown below. When user clicks SAVE button all the details like Course detail,Registration id, Username and passowrd should be saved in respective varaibles. But it does not work. It stores value of last textfield in all variables.
How do i get text value in each row of the table.
// Row creation code
cell variable is global
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch(indexPath.row)
{
case 0 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Course detail", txtValue: "Enter course.")
break;
case 1 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Registration ID", txtValue: "Enter registration id.")
cell.txtIpValue.tag = 1
break;
case 2 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Username ", txtValue: "Username")
cell.txtIpValue.tag = 2
break;
case 3 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Password ", txtValue: "Password")
cell.txtIpValue.tag = 2
break;
default :
break;
}
return cell
}
// Save button code
func saveButtonClicked() {
if(cell.txtIpValue.tag == 1)
{
strCourse = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 2)
{
strID = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 3)
{
strUsername = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 1)
{
strPassword = cell.txtIpValue.text
}
}
// Regcell
class RegCell: UITableViewCell {
#IBOutlet var txtIpValue: UITextField
#IBOutlet var lblstr: UILabel
func configure(lblValue : String,txtValue :String)) {
lblstr.text = lblValue
txtIpValue.text = txtValue
}
}
All code is in swift. Please if possible give an example.
Iterate over all cells in your tableView.
Assuming you have only one section, try this:
Update : Using label texts as keys for the values - you might want to think about finding constant values for that
func save() {
var fields = NSMutableDictionary();
for rowIndex in 0...self.tableView.numberOfRowsInSection(0) {
let indexPath : NSIndexPath = NSIndexPath(forItem: rowIndex, inSection: 0);
let cell : RegCell = self.tableView.cellForRowAtIndexPath(indexPath);
fields.setObject(cell.txtIpValue.text, forKey: cell.lblstr.text)
}
// ... here fields will contain all values, using the lblstr.text as key
let username = fields["Username "];
}

UITableView Cells shifting positions or disappearing entirely on scroll

I am currently developing and application for the iPhone in Swift, and I have run into a very peculiar error: in one of my UITableViewControllers,enter code here cells disappear or change sections when I scroll up and down on the Table View.
I've been dealing with this issue for a few days now, and it has even prompted me to recode the entire class, but to no avail. I have researched extensively on this error, and I believe it has something to do with my data source and how the tableView handles it, and I have also noticed that other users have had the same problem before, but I cannot find a solution that applies to my problems.
For example, here seems to deal with the cell height, but I have continued to check and double check my code, and the cell height returns the correct values.
In addition, this post talks about different errors with the tableView's data source, but I have a strong pointer to the datasource's alert array and my content and heights are correct in cellForRowAtIndexPath.
This post also deals with my question, but everything I am currently doing with the tableView is on the main thread.
Currently the tableView has 4 sections: the first, second, and fourth contain only one cell and the third has a dynamic amount of cells based on the amount of alerts the user has added (for example, it has 3 alert cells plus one "Add Alert" cell always at the bottom). The only cells that are affected are those in the 2, 3, and 4 sections.
This is what my tableView should look like always:
But, however, here is what happens when I scroll:
I first create the variables here:
var currentPrayer: Prayer! // This is the prayer that the user is currently editing
var prayerAlerts: NSMutableOrderedSet! // This is the mutable set of the prayer alerts that are included in the prayer
Then I initialize them in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
if currentPrayer == nil {
NSException(name: "PrayerException", reason: "Current Prayer is nil! Unable to show prayer details!", userInfo: nil).raise()
}
navItem.title = currentPrayer.name // Sets the Nav Bar title to the current prayer name
prayerAlerts = currentPrayer.alerts.mutableCopy() as! NSMutableOrderedSet // This passes the currentPrayer alerts to a copy called prayerAlerts
prayerAlertsCount = prayerAlerts.count + 1
}
Below are my TableView methods:
Here is my cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
println("cellForRowAtIndexPath called for the \(cellForRowRefreshCount) time")
cellForRowRefreshCount += 1
switch indexPath.section {
case 0:
var cell = tableView.dequeueReusableCellWithIdentifier(DetailsExtendedCellID, forIndexPath: indexPath) as! PrayerDetailsExtendedCell
cell.currentPrayer = currentPrayer
cell.refreshCell()
return cell
case 1:
var cell = tableView.dequeueReusableCellWithIdentifier(SetPrayerDateCellID, forIndexPath: indexPath) as! AddPrayerDateCell
cell.currentPrayer = currentPrayer
cell.refreshCell(false, selectedPrayer: cell.currentPrayer)
return cell
case 2:
if indexPath.row == prayerAlerts.count {
var cell = tableView.dequeueReusableCellWithIdentifier(AddNewAlertCellID, forIndexPath: indexPath) as! AddPrayerAlertCell
cell.currentPrayer = currentPrayer
cell.refreshCell(false, selectedPrayer: currentPrayer)
cell.saveButton.addTarget(self, action: "didSaveNewAlert", forControlEvents: .TouchDown)
return cell
} else {
var cell = tableView.dequeueReusableCellWithIdentifier(PrayerAlertCellID, forIndexPath: indexPath) as! PrayerAlertCell
let currentAlert = prayerAlerts[indexPath.row] as! Alert
cell.alertLabel.text = AlertStore.sharedInstance.convertDateToString(currentAlert.alertDate)
return cell
}
case 3:
var cell = tableView.dequeueReusableCellWithIdentifier(AnsweredPrayerCellID, forIndexPath: indexPath) as! PrayerAnsweredCell
cell.accessoryType = currentPrayer.answered == true ? .Checkmark : .None
return cell
default:
return UITableViewCell()
}
}
And my numberOfRowsInSection:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: println("Returning 1 Row for section 0"); return 1
case 1: println("Returning 1 Row for section 1"); return 1
case 2: println("Returning \(prayerAlertsCount) Rows for section 2"); return prayerAlertsCount
case 3: println("Returning 1 Row for section 3"); return 1
default: println("Returning 0 Rows for section Default"); return 0
}
}
And my heightForRowAtIndexPath:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.section {
case 0: return UITableViewAutomaticDimension
case 1:
let cell = tableView.cellForRowAtIndexPath(indexPath) as? AddPrayerDateCell
if let thisCell = cell {
let isAdding = thisCell.isAddingDate
if isAdding {
if thisCell.selectedType == PrayerType.None || thisCell.selectedType == PrayerType.Daily {
println("Selected Type is None or Daily")
println("Returning a height of 89 for AddPrayerDateCell")
return 89
} else {
println("Returning a height of 309 for AddPrayerDateCell")
return 309
}
} else {
println("Returning a height of 44 for AddPrayerDateCell")
return 44
}
} else {
println("Returning a default height of 44 for AddPrayerDateCell")
return 44
}
case 2:
if indexPath.row == prayerAlerts.count {
let cell = tableView.cellForRowAtIndexPath(indexPath) as? AddPrayerAlertCell
if let thisCell = cell {
let isAdding = thisCell.isAddingAlert
if isAdding { return 309 }; return 44
} else {
return 44
}
} else {
return 44
}
case 3: return 44
default: return 44
}
}
And estimatedHeightForRowAtIndexPath:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.section {
case 0: return 130
case 1: return 44
case 2: return 44
case 3: return 44
default: return 44
}
}
I have tried editing these methods extensively, as well as checking the code in each individual cell. Nothing seems to work.
Does anyone have any solutions to this error? I can always update with more code if necessary, but I believe that either my data source could be the problem, or that the cell's resuse could be creating this error, but I cannot seem to pinpoint anything. Thanks in advance for any help!
UPDATE
Here is my AddAlertCell "refreshCell()" method as well as my UITableViewCell extension:
func refreshCell(didSelect: Bool, selectedPrayer: Prayer!) {
tableView?.beginUpdates()
selectionStyle = didSelect == true ? .None : .Default
saveButton.hidden = !didSelect
cancelButton.hidden = !didSelect
addNewAlertLabel.hidden = didSelect
isAddingAlert = didSelect
dateLabel.text = AlertStore.sharedInstance.convertDateToString(datePicker.date)
println("AddPrayerAlertCell: Cell Refreshed")
tableView?.scrollEnabled = !didSelect
tableView?.endUpdates()
}
UITableViewCell Extension:
extension UITableViewCell {
var tableView: UITableView? {
get {
var table: UIView? = superview
while !(table is UITableView) && table != nil {
table = table?.superview
}
return table as? UITableView
}
}
}
You shouldn't need to call beginUpdates / endUpdates when you refresh the cell - these methods are used if you are adding / deleting rows or sections from the tableview.
What happens if you remove the beginUpdates() and endUpdates() calls?

Invalid Update When adding Rows to UITableView

Invalid update: invalid number of rows in section 0. 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 (1), plus or minus the number of rows inserted or deleted from
that section (1 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
I'm trying to add rows to a table view when a user taps a row, to create an expandable section, however the extra rows aren't being counted before Xcode tries to add them in and as such causes this error (I think). Can anybody point me in the right direction?
// sectionExpanded is set to false in viewDidLoad. It is set to true when
// the user taps on the expandable section (section 0 in this case)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 && sectionExpanded {
return 5
} else {
return 1
}
}
// This should recount the rows, add the new ones to a temporary array and then add
// them to the table causing the section to 'expand'.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = menu[indexPath.row]
let cell = tableView.cellForRowAtIndexPath(indexPath) as MenuCell
if indexPath.section == 0 {
var rows: Int
var tmpArray: NSMutableArray = NSMutableArray()
sectionExpanded = !sectionExpanded
rows = tableView.numberOfRowsInSection(0)
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
} else {
delegate?.rightItemSelected(selectedItem)
}
}
It is telling you that you are trying to insert 1 new row, but numberofrows should be 5, before was 1 and you are trying to insert 1 new row, thats 2. Theres your problem.
rows = tableView.numberOfRowsInSection(0) //this returns 1
for i in 1...rows { //
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)//this will contain only 1 object, because the loop will run only for 1 cycle
}
EDIT
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = menu[indexPath.row]
let cell = tableView.cellForRowAtIndexPath(indexPath) as MenuCell
if indexPath.section == 0 {
var rows: Int
var tmpArray: NSMutableArray = NSMutableArray()
sectionExpanded = !sectionExpanded
rows = 1
if sectionExpanded {
rows = 5
}
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
} else {
delegate?.rightItemSelected(selectedItem)
}
}
Since you know number of rows will be always 5 or 1, you can try something like this. However, this is not a standard approach, I would suggest to alter your datasource array.
Here is some example how to do it: http://www.nsprogrammer.com/2013/07/updating-uitableview-with-dynamic-data.html its for Objective-C but you will get the gist of it.
You can try modifying the data source and then reload the table.
You should use insertRowsAtIndexPaths... and the like between a beginUpdates() and endUpdates(). The tableView will collect all the changes after beginUpdates() and then will apply them coherently after endUpdates(). So try something like:
tableView.beginUpdates()
if !sectionExpanded {
tableView.deleteRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
} else {
tableView.insertRowsAtIndexPaths(tmpArray, withRowAnimation: UITableViewRowAnimation.Top)
}
tableView.endUpdates()
Remember that after the call to endUpdates() the number of sections and rows must be consistent with your model.
Since I don't know about your model, here's a simple example:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var sectionExpanded: Bool = false {
didSet {
if oldValue != sectionExpanded {
let expIndexes = map(0..<model.count) { r in
NSIndexPath(forRow: r, inSection: 0)
}
// Here we start the updates
tableView.beginUpdates()
switch sectionExpanded {
case false:
// Collapsing
tableView.deleteRowsAtIndexPaths(expIndexes, withRowAnimation: .Top)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Top)
default:
// Expanding
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Top)
tableView.insertRowsAtIndexPaths(expIndexes, withRowAnimation: .Bottom)
}
// Updates ended
tableView.endUpdates()
}
}
}
let model = ["foo", "bar", "zoo"]
//MARK: UITableView DataSource
struct TableConstants {
static let sectionCellIdentifier = "SectionCell"
static let expandedCellIdentifier = "ExpandedCell"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionExpanded ? model.count : 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch sectionExpanded {
case false:
let cell = tableView.dequeueReusableCellWithIdentifier(
TableConstants.sectionCellIdentifier,
forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "The Section Collapsed Cell"
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier(
TableConstants.expandedCellIdentifier,
forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "\(model[indexPath.row])"
cell.detailTextLabel?.text = "Index: \(indexPath.row)"
return cell
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
sectionExpanded = !sectionExpanded
}
}
Note that I moved the table updates to the sectionExpanded observer.
You already have 1 row in section = 0, and trying to insert 5 new rows. You can only add 4 rows more to map with numberOfRowsInsection.
Try following code:
sectionExpanded = !sectionExpanded
rows = self.numberOfRowsInSection(0)-1
for i in 1...rows {
var tmpIndexPath: NSIndexPath
tmpIndexPath = NSIndexPath(forRow: i, inSection: 0)
tmpArray.addObject(tmpIndexPath)
}

Resources