Swift: fatal error: Index out of range - ios

I'm getting an "Index out of range" when populating a UITableView.
I don't know why it gives me error. I have a condition statement on my cell creation function which checks for the count of each array and then adds a custom cell on index 0.
If anyone knows about this problem, please tell me the solution. I've been working on it for days and can't seem to figure this one out.
var homeArr: [services] = []
var autoArr: [services] = []
Alamofire.request(url_specialist_request_url, method: .post, parameters: parameters).responseJSON {
response in
if response.data != nil {
let json = JSON(data: response.data!)
let json_count = json.count
// print(json)
self.homeArr.append(services(service_name:"",service_icon:"",service_category:""))
self.autoArr.append(services(service_name:"",service_icon:"",service_category:""))
self.personalArr.append(services(service_name:"",service_icon:"",service_category:""))
for i in 0 ..< json_count {
let categoryId = json[i]["category_id"].string!
if(categoryId == "1") {
self.homeArr.append(services(service_name:json[i]["service_name"].string!,service_icon:"\(json[i]["service_icon"].string!)Icon",service_category:json[i]["category_id"].string!))
} else if(categoryId == "2") {
self.autoArr.append(services(service_name:json[i]["service_name"].string!,service_icon:"\(json[i]["service_icon"].string!)Icon",service_category:json[i]["category_id"].string!))
} else {
self.personalArr.append(services(service_name:json[i]["service_name"].string!,service_icon:"\(json[i]["service_icon"].string!)Icon",service_category:json[i]["category_id"].string!))
}
}
self.tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "serviceCell",for:indexPath) as! servicesCell
let home_dict = self.homeArr[indexPath.row]
// ** Index out of range on the following line **
let auto_dict = self.autoArr[indexPath.row]
if(self.homeArr.count > 1) {
if (indexPath.row == 0)
{
cell.serviceLabel!.text = "Home"
cell.contentView.backgroundColor = UIColor.blue
} else {
cell.serviceLabel!.text = home_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(home_dict.service_icon)")
}
}
if(self.autoArr.count > 1) {
if (indexPath.row == 0)
{
cell.serviceLabel!.text = "Personal"
cell.contentView.backgroundColor = UIColor.blue
} else {
cell.serviceLabel!.text = auto_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(auto_dict.service_icon)")
}
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(homeArr.count + arrAuto.count)
}
This is what I want to achieve
Home Arr cell
value1
value2
Auto Arr cell
value1
value2

Create two sections One for HomeArr and one for AutoArr. I believe for each section you wanna show a additional cell with some title. So below code should help you.
extension ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return (homeArr.count > 0) ? homeArr.count + 1 : 0
}
else {
return (autoArr.count > 0) ? autoArr.count + 1 : 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "serviceCell",for:indexPath) as! servicesCell
if indexPath.section == 0 {
if (indexPath.row == 0)
{
cell.serviceLabel!.text = "Home"
cell.contentView.backgroundColor = UIColor.blue
} else {
let home_dict = self.homeArr[indexPath.row - 1]
cell.serviceLabel!.text = home_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(home_dict.service_icon)")
}
}
else {
if (indexPath.row == 0)
{
cell.serviceLabel!.text = "Personal"
cell.contentView.backgroundColor = UIColor.blue
} else {
let auto_dict = self.autoArr[indexPath.row - 1]
cell.serviceLabel!.text = auto_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(auto_dict.service_icon)")
}
}
return cell
}
}
EDIT:
As pointed out by rmaddy in comments below
Why the extra row in each section? Why not use a section header
instead?
As we are not aware of OP's exact requirement I am updating my code to show section title as well.
extension ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return homeArr.count
}
else {
return autoArr.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "serviceCell",for:indexPath) as! servicesCell
if indexPath.section == 0 {
let home_dict = self.homeArr[indexPath.row]
cell.serviceLabel!.text = home_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(home_dict.service_icon)")
}
else {
let auto_dict = self.autoArr[indexPath.row]
cell.serviceLabel!.text = auto_dict.service_name
cell.serviceIcon!.image = UIImage(named:"\(auto_dict.service_icon)")
}
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Home Arr cell"
}
else {
return "Auto Arr cell"
}
}
}

Related

How to add extra row to tableview with textfield text "Amount" in swift

This struct in HomeVC:
struct JsonData{
var name: String = ""
var categoryname: String = ""
var customerdetails: [cDetails] = [cDetails]()
init(name: String, categoryname: String, customerdetails: [cDetails]){
self.name = name
self.categoryname = categoryname
self.customerdetails = customerdetails
}
}
struct cDetails{
var dValue: String = ""
init(dValue: String) {
self.dValue = dValue
}
}
In numberOfRowsInSection if categoryname is Mobile i need extra row in tableview and in cellForRowAtindexPath i need its text cell?.searchTextfield.text = "Amount"
like below how to do that?
if section == 0 {
if categoryname == "Mobile"{
//here i need extra row with cell?.searchTextfield.text = "Amount"
}
else
{
return selectedDetail?.customerdetails.count ?? 0
}
}
below is code for present view controller, please help me in the below code.
class NewSearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var tableView: UITableView!
var cell : DetailTableViewCell?
var selectedDetail: JsonData?
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return selectedDetail?.customerdetails.count ?? 0
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
cell = tableView.dequeueReusableCell(withIdentifier: "textfieldCell", for: indexPath) as! DetailTableViewCell
cell?.searchTextfield.delegate = self
if let value = selectedDetail?.customerdetails[indexPath.row] {
cell?.searchTextfield.text = value.dValue
} else {
cell?.searchTextfield.text = "missing data"
}
} else if indexPath.section == 1 {
cell = tableView.dequeueReusableCell(withIdentifier: "buttonCell", for: indexPath) as! DetailTableViewCell
}
return cell!
}
}
Please help me in the code.
try this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if categoryname == "Mobile"{
return selectedDetail?.customerdetails.count + 1 ?? 0
}
else
{
return selectedDetail?.customerdetails.count ?? 0
}
}
}
then
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
cell = tableView.dequeueReusableCell(withIdentifier: "textfieldCell", for: indexPath) as! DetailTableViewCell
cell?.searchTextfield.delegate = self
//play with this validation
if selectedDetail?.customerdetails.count - 2 >= indexPath.row
{
if let value = selectedDetail?.customerdetails[indexPath.row] {
cell?.searchTextfield.text = value.dValue
} else {
cell?.searchTextfield.text = "missing data"
}
}
else
{
cell?.searchTextfield.text = "Amount"
}
} else if indexPath.section == 1 {
cell = tableView.dequeueReusableCell(withIdentifier: "buttonCell", for: indexPath) as! DetailTableViewCell
}
return cell!
}
Somehow i can't add a comment so i will respond with this answer.
For what i can see, you're returning 2 in func numberOfSections(in tableView: UITableView).
I think you need to find a way to program this dynamically. For example, if you store all the data you need in an array. you can simply return arrayName.count
So i think you need to write a function to store all the data you need in an array. and return this in your TableView.
I hope i understood your problem well, and this answer made sense.

Issue deleting a single row from a table with multiple sections (Swift 4)

I'm trying to delete a row from a section in my table with multiple sections using commit editingStyle. However, it's deleting the proper indexPath.row from the section above.
How can I get it to delete from the proper section?
I followed a couple of examples on how to section a single array and index it for the tableView. I am unable to properly delete from the initial Array of custom class objects. I am also unable to find a way to transfer the IndexPath Section and Row to a second view controller to display the selected Code. It just transfers the indexPath.row but I can't get it to send the entire indexPath including the section.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if tableView == firstTableView {
if editingStyle == .delete {
if inSearchMode == false {
codeArray.remove(at: [indexPath.section][indexPath.row])
userDefaults.setValue(NSKeyedArchiver.archivedData(withRootObject: codeArray), forKey: "codeArrayKey")
userDefaults.synchronize()
tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let subArrayArray = codeArray.compactMap { $0.subArray as? String }
var dic = [String:[Code]]()
subArrayArray.forEach {
let subArrayKey = $0
let filterArray = codeArray.filter { $0.subArray as? String == subArrayKey }
dic[$0] = filterArray
}
let sectionTitle = sectionTitles[section]
let sectionCodes:[Code] = dic[sectionTitle]!
if tableView == firstTableView {
if inSearchMode == true {
return filteredCodeArray.count
} else {
return sectionCodes.count
}
} else if tableView == autoTableview {
return locationFilteredCodeArray.count
} else {
return 1
}
}
func numberOfSections(in tableView: UITableView) -> Int {
if tableView == firstTableView {
if inSearchMode == false {
indexCodes(enterArray: codeArray)
return sectionTitles.count
} else if inSearchMode == true {
return 1
}
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == firstTableView {
if let cell = tableView.dequeueReusableCell(withIdentifier: "newCodesProtoCell") as? NewCodesViewCell {
let code: Code!
if inSearchMode == true {
code = filteredCodeArray[indexPath.row]
cell.configureCell(code: code)
} else {
let subArrayArray = codeArray.compactMap { $0.subArray }
var dic = [String:[Code]]()
subArrayArray.forEach {
let subArrayKey = $0
let filterArray = codeArray.filter { $0.subArray == subArrayKey }
dic[$0] = filterArray
}
let sectionTitle = sectionTitles[indexPath.section]
let sectionCodes:[Code] = dic[sectionTitle]!
code = sectionCodes[indexPath.row]
cell.configureCell(code: code)
}
return cell
}
}
if let cell = tableView.dequeueReusableCell(withIdentifier: "secondNewCodesProtoCell") as? SecondNewCodesProtoCell {
let code: Code!
if locationFilteredCodeArray.count != 0 {
locationAuthStatus()
code = locationFilteredCodeArray[indexPath.row]
cell.configureSecondCell(code: code)
}
return cell
}
return UITableViewCell()
}
This is how I'm getting the array of index names (headers) as this may be causing some of the issues.
This is written to index the [Code] by the second letter in the .location.
func indexCodes(enterArray: [Code]) {
var codeValues = [String]()
for code in enterArray {
var initCodeKey = String(code.location.prefix(2))
initCodeKey.remove(at: initCodeKey.startIndex)
let codeKey = initCodeKey.capitalized
codeValues.append(codeKey)
}
var encountered = Set<String>()
var result: [String] = []
for value in codeValues {
if encountered.contains(value) {
} else {
encountered.insert(value)
result.append(value)
}
}
sectionTitles = result.sorted(by: <)
}
You need to do 2 steps.
1. From your codeArray, get the dictionary from indexPath.section and then your Code object from [Code] saved in that dictionary. Remove code at indexPath.row and then reserve it to dictionary and replace dictionary object in CodeArray
2. self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if tableView == firstTableView {
if editingStyle == .delete {
if inSearchMode == false {
codeArray.remove(at: [indexPath.section][indexPath.row])
var dict = codeArray[indexPath.section]
let key = dict.allKeys.first
var codes = dict[key]
//remove code from [Code]
codes.remove(at: indexPath.row)
dict[key] = codes
codeArray[indexPath.section] = dict
userDefaults.setValue(NSKeyedArchiver.archivedData(withRootObject: codeArray), forKey: "codeArrayKey")
userDefaults.synchronize()
self.tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: indexPath.section)], with: .fade)
}
}
}
let me know if you find any issue implementing it.

How to hide the sections in the tableview after deleting a particular section from tableview?

I am having three sections (see the image) in which one section is to display the items and remaining two cells are designed by using stroyboard now if i delete all the items using delete button in first section then the remaining two sections need to be hidden and to display some text can anyone help me how to do this ?
func numberOfSections(in tableView: UITableView) -> Int{
// #warning Incomplete implementation, return the number of sections
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if (section == 0){
return itemsArray.count
}else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! productTableViewCell
tableDetails.isHidden = false
myActivityIndicator.stopAnimating()
let arr = itemsArray[indexPath.row]
let urls = NSURL(string: arr["img"] as! String)
let data = NSData (contentsOf: urls! as URL)
cell.imageview.image = UIImage(data: data! as Data)
cell.nameLabel.text = arr["productName"]as! String
var price = arr["productPrice"] as! String
print(price)
var Quantity : Float = 1
let itemId : Int = arr["sku"] as! Int
print(itemId)
for aDic in CartArray{
if aDic["id"] == String(itemId){
Quantity = Float(String(aDic["quantity"]!))!
}
}
print(CartArray)
cell.stepper.value = Double(Int(Quantity))
cell.stepper.tag = indexPath.row
cell.stepper.addTarget(self, action: #selector(stepperValueChange(stepper:)), for:.valueChanged)
price = price.replacingOccurrences(of: "KD", with: "")
cartstring = String(Float(price)! * Quantity) + "0KD"
cell.priceLabel.text = cartstring
let quantityText = String(Quantity)
let endIndex = quantityText.index(quantityText.endIndex, offsetBy: -2)
let truncated = quantityText.substring(to: endIndex)
cell.quantityTextField.text = truncated
cell.price = price
cell.deleteButton.addTarget(self, action: #selector(deleteButtonAction(button:)), for: .touchUpInside)
cell.deleteButton.tag = indexPath.row
return cell
}else if indexPath.section == 1{
let cell = tableView.dequeueReusableCell(withIdentifier: "couponcell", for: indexPath) as! CouponTableViewCell
cell.applyButton.addTarget(self, action: #selector(applyButtonAction(button:)), for: .touchUpInside)
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: "checkout", for: indexPath) as! checkoutTableViewCell
cell.finalCartpriceLabel.text = total
return cell
}
}
func deleteButtonAction(button : UIButton) {
let buttonPosition = button.convert(CGPoint(), to: tableDetails)
let index = tableDetails.indexPathForRow(at: buttonPosition)
self.itemsArray.remove(at: (index?.row)!)
self.tableDetails.deleteRows(at: [index!], with: .automatic)
tableDetails.reloadData()
}
Modify your numberOfSections with:
func numberOfSections(in tableView: UITableView) -> Int {
if self.itemsArray.count > 0 {
return 3
}
//Show Message List is Empty
return 1
}
You can manage something like,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if (section == 0){
return itemsArray.count
}else{
if self.itemsArray.count == 0 {
return 0
}
else{
return 1
}
}
}
You can just check for self.itemsArray.count > 0 inside your func numberOfSections(in:) and show all the three sections for this condition. Otherwise, return only the first section and this will automatically hide the other two.
Example:
func numberOfSections(in tableView: UITableView) -> Int {
if self.itemsArray.count > 0 {
return 3
}
return 1
}
in numberOfSection datasource methods, check if itemArray has no elements, return just one section. In cellForRowAtIndexPath, check the same condition again, and show the empty text in this.
func numberOfSections(in tableView: UITableView) -> Int {
if(itemArray.count > 0)
{
if (section == 0){
return itemsArray.count
}else{
return 1
}
} else {
return 1;
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(itemArray.count > 0)
{
}else {
//Show empty text view
}
}
this code worked for me perfectly
func deleteButtonAction(button : UIButton) {
let buttonPosition = button.convert(CGPoint(), to: tableDetails)
let index = tableDetails.indexPathForRow(at: buttonPosition)
self.itemsArray.remove(at: (index?.row)!)
self.tableDetails.deleteRows(at: [index!], with: .automatic)
tableDetails.reloadData()
if (tableView(tableDetails, numberOfRowsInSection: 0) == 0){
tableDetails.isHidden = true
}
if (tableDetails.isHidden == true){
self.loadingLabel.textColor = UIColor.gray
self.loadingLabel.textAlignment = NSTextAlignment.center
self.loadingLabel.text = "Your shopping cart is empty"
self.loadingLabel.frame = CGRect(x: 130, y: 320, width: 140, height: 30)
view.addSubview(loadingLabel)
}
}

Single table view through two different NSFetchedResultsControllers with sections

Good morning to everyone. I am using Swift 3.1.1, Xcode 8.3.2. I need to connect a single Table View to two different tables (entities) in Core Data through two different NSFetchedResultsControllers. I have created two NSFetchedResultsControllers, and even fetched data from table but I faced problem how to tell Table View that first controller should response for section one and second controller should be responsible for section two.
I can show you the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tv: UITableView!
#IBAction func pressed(_ sender: UIButton) {
ModelA.read { table1 in
ModelB.read { table2 in
if table1.isEmpty {
ModelA.save(recordToSave: [(a: 1, b: "a"), (a: 2, b: "b"), (a: 3, b: "c")]) {
ModelB.save(recordToSave: [(a: 4, b: 5.0, c: true), (a: 6, b: 7.0, c: false)]) {
self.tvReload()
}
}
} else {
self.tvReload()
}
}
}
}
var fetchedResultsControllerForModelA = CoreDataFunctions.fetchedResultsController
var fetchedResultsControllerForModelB = CoreDataFunctions.fetchedResultsController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tvReload()
}
func modelOfTableA(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelA = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelA, indexPath: indexPath) {
if let model = ModelA.read(nsmanagedobject: fetchedResultsControllerForModelA) {
return (forLabel1: "\(model.a)", forLabel2: model.b, forLabel3: "")
}
}
return nil
}
func modelOfTableB(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelB = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelB, indexPath: indexPath) {
if let model = ModelB.read(nsmanagedobject: fetchedResultsControllerForModelB) {
return (forLabel1: "\(model.a)", forLabel2: "\(model.b)", forLabel3: "\(model.c)")
}
}
return nil
}
func tvReload() {
fetchedResultsControllerForModelA = CoreDataFunctions(tableName: .a).fetchedResultsController(keyForSort: ModelA.a.rawValue, searchParameters: nil)
fetchedResultsControllerForModelB = CoreDataFunctions(tableName: .b).fetchedResultsController(keyForSort: ModelB.a.rawValue, searchParameters: nil)
do {
try fetchedResultsControllerForModelA?.performFetch()
try fetchedResultsControllerForModelB?.performFetch()
DispatchQueue.main.async {
self.tv.reloadData()
}
} catch {
print("Error")
}
}
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections1 = fetchedResultsControllerForModelA?.sections {
if let sections2 = fetchedResultsControllerForModelB?.sections {
return sections1[section].numberOfObjects + sections2[section].numberOfObjects
}
return sections1[section].numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
if let modelOfTable = modelOfTableB(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
}
I could not find any tutorial on this theme, so I am asking question there. I do not want to use inheritance from single entity in Core Data, because, in real life it would be impossible.
Thank you for any help or advice!
OK - I downloaded your code, and there are a couple issues...
1) If you want your data to fill two sections in the table, the table must have two sections - currently, you are just returning 1, so use this (although you may want/need different handling based on data retrieved):
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
if fetchedResultsControllerForModelA == nil || fetchedResultsControllerForModelB == nil {
return 0
}
return 2
}
2) For number of rows in each section, your code was close but not quite right...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if let sections = fetchedResultsControllerForModelA?.sections {
return sections[0].numberOfObjects
}
} else {
if let sections = fetchedResultsControllerForModelB?.sections {
return sections[0].numberOfObjects
}
}
return 0
}
3) For the actual cellForRowAtIndexPath data, again your code was close but you need to keep track of which data to get for each section...
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
// Each "Table data model" has only one section... since this is the 2nd table section, we need
// to change the section of the index path for our call to the data model
let dataIndexPath = IndexPath(row: indexPath.row, section: 0)
if let modelOfTable = modelOfTableB(indexPath: dataIndexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
That should get you on your way.

Swift/iOS: Collapsing a section in a UITableView

I have a UITableView with about 5 sections. I am trying to collapse and expand one of those section by the click of a button, but I am seeing an issue where the code I'm using to do so results in the collapsing of other sections as well. Specifically, the first row of all visible sections are collapsed.
Here is what that code looks like:
func didClickSectionCollapseButton() {
shouldCollapseSection = !shouldCollapseSection
tableView.beginUpdates()
tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
tableView.endUpdates()
}
And here is the numberOfRowInSection method:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 1
case 1:
// collapsible section
return shouldCollapse ? 0 : collapsibleSectionCellCount
case 2:
return getCellCount()
case 3:
return 1
case 4:
return 1
default:
return 0
}
}
Is there anything I'm missing here? I've gone through various tutorials and questions, but I haven't been able to find a solution yet.
Hi after a lot of research, i found a solution which worked for me perfectly using storyboard.
View controller code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tblView: UITableView!
var sections = ["section1","section2","section3"]
var cells = ["cell1","cell2","cell3","cell4"]
var selectedIndx = -1
var thereIsCellTapped = false
override func viewDidLoad() {
super.viewDidLoad()
tblView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 2
case 1:
return 3
default:
return 4
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == selectedIndx && thereIsCellTapped{
return 50
}else{
return 0
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCell(withIdentifier: "SectionTableViewCell") as! SectionTableViewCell
headerCell.lblHeader.text = sections[section]
headerCell.btnSelection.tag = section
headerCell.btnSelection.addTarget(self, action: #selector(ViewController.btnSectionClick(sender:)), for: .touchUpInside)
return headerCell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandeTableViewCell") as! ExpandeTableViewCell
cell.lblCell.text = cells[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.section)
}
#objc func btnSectionClick(sender:UIButton!){
print("selected index",sender.tag)
if selectedIndx != sender.tag {
self.thereIsCellTapped = true
self.selectedIndx = sender.tag
}
else {
// there is no cell selected anymore
self.thereIsCellTapped = false
self.selectedIndx = -1
}
tblView.reloadData()
}
}
If you don't want to do select and unselect on the same selection then, see code below.
#objc func btnSectionClick(sender:UIButton!){
print("selected index",sender.tag)
selectedIndx = sender.tag
tblView.reloadData()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == selectedIndx{
return 50
}else{
return 0
}
}
It works for me, i referred lot of answers and made it. I hope it will help you.
I've Used this code long time ago it is in Swift 2.3. I don't know if this will help or not but worth to mention it.
class DriversVC : UIViewController , UITableViewDelegate , UITableViewDataSource {
//-----------------------------------------------------------------------
//MARK: - Outlets
#IBOutlet var tvDriverList: UITableView! {
didSet {
tvDriverList.delegate = self
tvDriverList.dataSource = self
}
}
//-----------------------------------------------------------------------
//MARK: - Variables
var arrDriverList : NSArray? //Section data
var arrWorkerList : NSArray? //Section data
var collapseSection0 : Bool = false
var collapseSection1 : Bool = false
var btnSection0Headder : UIButton = UIButton()
var btnSection1Headder : UIButton = UIButton()
//------------------------------------------------------
func btnSection0HeadderTapped () {
if collapseSection0 {
collapseSection0 = false
} else {
collapseSection0 = true
}
tvDriverList.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Fade)
}
//------------------------------------------------------
func btnSection1HeadderTapped () {
if collapseSection1 {
collapseSection1 = false
} else {
collapseSection1 = true
}
tvDriverList.reloadSections(NSIndexSet(index: 1), withRowAnimation: UITableViewRowAnimation.Fade)
}
//-----------------------------------------------------------------------------------
//MARK:- Table delegate and data sources
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
//------------------------------------------------------
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 20
}
//------------------------------------------------------
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 50))
view.backgroundColor = OrangeColor //Set your color
let lbl = UILabel(frame: CGRect(x: 10, y: 5, width: UIScreen.mainScreen().bounds.width - 20, height: 40))
lbl.font = UIFont(name: OpenSansRegular, size: 18) //Set your font
lbl.textColor = UIColor.whiteColor()
view.addSubview(lbl)
if section == 0 {
lbl.text = "D R I V E R"
btnSection0Headder.addTarget(self, action: #selector(self.btnSection0HeadderTapped), forControlEvents: .TouchUpInside)
btnSection0Headder.frame = view.frame
view.addSubview(btnSection0Headder) // uncomment to apply collapse effect
} else {
lbl.text = "W O R K E R"
btnSection1Headder.addTarget(self, action: #selector(self.btnSection1HeadderTapped), forControlEvents: .TouchUpInside)
btnSection1Headder.frame = view.frame
view.addSubview(btnSection1Headder) // uncomment to apply collapse effect
}
return view
}
//------------------------------------------------------
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
//------------------------------------------------------
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if arrWorkerList != nil && arrWorkerList?.count > 0 {
return 2
}
return 1
}
//------------------------------------------------------
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if !collapseSection0 {
guard arrDriverList != nil else {return 0}
return arrDriverList!.count
} else {
return 0
}
} else {
if !collapseSection1 {
guard arrWorkerList != nil else {return 0}
return arrWorkerList!.count
} else {
return 0
}
}
}
//------------------------------------------------------
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier(NSStringFromClass(DriversCVC).componentsSeparatedByString(".").last!) as? DriversCVC else { fatalError("unexpected DriversCVC dequeued from tableView") }
cell.superViewController = self
if indexPath.section == 0 {
guard let dict = arrDriverList![indexPath.row] as? NSDictionary else {return cell}
cell.data = dict
} else {
guard let dict = arrWorkerList![indexPath.row] as? NSDictionary else {return cell}
cell.data = dict
}
cell.setup()
return cell
}
//----------------------------------------------------------------------
//MARK: - Action Method
#IBAction func btnBackTapped(sender: AnyObject) {
guard self.navigationController != nil else {
self.dismissViewControllerAnimated(true, completion: nil)
return
}
guard self.navigationController?.popViewControllerAnimated(true) != nil else {
guard self.navigationController?.dismissViewControllerAnimated(true, completion: nil) != nil else {
AppDelegate.sharedInstance().loginCall()
return
}
return
}
}
//-----------------------------------------------------------------------
//MARK: - View Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
//----------------------------------------------------------------------
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setUpVC()
}
//----------------------------------------------------------------------
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
} }
You can use:
func didClickSectionCollapseButton() {
shouldCollapseSection = !shouldCollapseSection
tableView.beginUpdates()
tableView.deleteSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
tableView.endUpdates()
}
beginUpdates() and endUpdates() works in pair if you want subsequent insertions, deletion, and selection operations, but not for the reloadData.
In your code, remove beginUpdates() and endUpdates().
Is there a difference between the shouldCollapseSection variable being set in the button action and the shouldCollapse variable used in the numberOfRowsInSection method ?
It would seem that you are not setting the same variable you are using in the data source delegate.

Resources