I created the Expandable table view cells which is like Below mentioned image.
Library used is JExpandableTableView.
Code for Creating This ExpandableTable View is given below :
Model For Sections:
class SectionInfo: NSObject {
var cells = [CellInfo]()
var CategoryName: String
var CategoryCount: String
var CategoryImage: UIImage
init(_ text: String,SubCount: String, Image: UIImage ) {
self.CategoryName = text
self.CategoryCount = SubCount
self.CategoryImage = Image
}
}
Model For SubCategoryCell:
class CellInfo: NSObject {
var SubcategoryName: String!
var SubcategoryCount: String!
init(_ SubName: String, SubCount: String) {
self.SubcategoryName = SubName
self.SubcategoryCount = SubCount
}
}
View Controller :
class CategoryVC: BaseVC,JExpandableTableViewDataSource,JExpandableTableViewDelegate{
#IBOutlet weak var tblViewCategory: JExpandableTableView!
var tableViewData = [SectionInfo]()
var expandedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Category"
self.tblViewCategory.dataSource = self
self.tblViewCategory.delegate = self
self.tblViewCategory.keepPreviousCellExpanded = false
self.LoadData()
}
func LoadData() {
var cellInfo = CellInfo("SubCategory 1",SubCount: "10")
let sec1 = SectionInfo("Category 1", SubCount: "5", Image: UIImage(named: "Category3")!)
sec1.cells.append(cellInfo)
let sec2 = SectionInfo("Category 2", SubCount: "8", Image: UIImage(named: "Category3")!)
cellInfo = CellInfo("SubCategory 2",SubCount: "20")
sec2.cells.append(cellInfo)
cellInfo = CellInfo("SubCategory 2.1",SubCount: "30")
sec2.cells.append(cellInfo)
let sec3 = SectionInfo("Category 3", SubCount: "10", Image: UIImage(named: "Category3")!)
cellInfo = CellInfo("SubCategory 3",SubCount: "30")
sec3.cells.append(cellInfo)
tableViewData.append(sec1)
tableViewData.append(sec2)
tableViewData.append(sec3)
let celNib = UINib.init(nibName: "SubCategoryCell", bundle: nil)
tblViewCategory.register(celNib, forCellReuseIdentifier: "SubCategoryCell")
let headerNib = UINib.init(nibName: "HeaderView", bundle: nil)
tblViewCategory.register(headerNib, forHeaderFooterViewReuseIdentifier: "HeaderView")
}
#IBAction func DrawerMenutap(_ sender: Any) {
self.OpenDrawerMenu()
}
func tableView(_ tableView: JExpandableTableView, numberOfRowsInSection section: Int, callback: #escaping (Int) -> Void) {
let sectionInfo = self.tableViewData[section]
callback(sectionInfo.cells.count)
}
func tableView(_ tableView: JExpandableTableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: JExpandableTableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat {
return 44
}
func tableView(_ tableView: JExpandableTableView, initialNumberOfRowsInSection section: Int) -> Int {
// let sectionInfo = self.tableViewData[section]
return 0
}
func numberOfSections(in tableView: JExpandableTableView) -> Int {
return tableViewData.count
}
func tableView(_ tableView: JExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let section = tableViewData[indexPath.section]
let row = section.cells[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "SubCategoryCell", for: indexPath) as! SubCategoryCell
cell.contentView.backgroundColor = UIColor.white
cell.lblSubCategoryName.text = row.SubcategoryName
return cell
}
func tableView(_ tableView: JExpandableTableView, viewForHeaderInSection section: Int) -> UIView? {
let section = self.tableViewData[section]
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! HeaderView
header.contentView.backgroundColor = UIColor.groupTableViewBackground
header.CatName.text = section.CategoryName
header.CatImgView.image = UIImage(named: "Category4")
header.CatCount.text = section.CategoryCount
return header
}
}
//MARK: Table View Cell for Category
class CatCell: UITableViewCell {
#IBOutlet weak var lblName: UILabel!
}
My further requirement is I want to expand the Cells (Subcategory 2, SubCategory 2.1) in order to accommodate SubSubCategory(Childrens of SubCategory) in Case if they Exist. So what should be the approach for achieving this.
UITableView is really designed in a way to show two levels, sections and rows.
But to show more then two levels you can manipulate rows that will increase/expand or decrease/collapse according to your model for Section, SubCategory.
So table structure will look like that
section_header_1
subCategory_1.0
subSubCategory_1.0.1
subCategory_1.1
subSubCategory_1.1.1
subCategory_1.2
subSubCategory_1.2.1
section_header_2
subCategory_2.0
subSubCategory_2.0.1
subCategory_2.1
subSubCategory_2.1.1
subCategory_2.2
subSubCategory_2.2.1
For Header you have to make your own custom header row and put that as the first row of each section. You could set up a cell to LOOK like a header, and setup the tableView:didSelectRowAt to manually expand or collapse the section, subCategory or SubSubCategory it is in. the rows after first row will be your subCategory and subSubCategory.
Then a Model For Section, SubCategory and SubSubCategory to store a booleans corresponding the the "expend" value of each of your sections, subCategories. you can avoid SubSubCategory model if it's only store it's name but it's easy to understand if you do so. for an example a struct for holding Section, SubCategory "expend" booleans.
public struct Section {
var name: String
var expand: Bool
var subCategory:[SubCategory]
public init(name: String, expand: Bool = false ,subCategory: [SubCategory]) {
self.name = name
self.expand = expand
self.subCategory = subCategory
}
}
public struct SubCategory {
var name: String
var expand: Bool
var subSubCategory: SubSubCategory
public init(name: String, expand: Bool = false, subSubCategory: SubSubCategory) {
self.name = name
self.expand = expand
self.subSubCategory = subSubCategory
}
}
public struct SubSubCategory {
var name: String
public init(name: String) {
self.name = name
}
}
Create 3 Custom cell one for Header other for SubCategory and SubSubCategory and display it in the first row of every section Header Cell and after expand or collapse show your SubCategory or SubSubCategory Cell accordingly.
after all together your code should be look that that.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var sampleData: [Section] = [
Section(name: "Category 1", expand: false,
subCategory: [
SubCategory(name: "Category 1.1", expand: false, subSubCategory: SubSubCategory(name: "SubSubCategory 1.1.1")),
SubCategory(name: "Category 1.2", expand: false, subSubCategory: SubSubCategory(name: "SubSubCategory 1.2.1"))
]
),
Section(name: "Category 2", expand: false,
subCategory: [
SubCategory(name: "Category 2.1", expand: false, subSubCategory: SubSubCategory(name: "SubSubCategory 2.1.1")),
SubCategory(name: "Category 2.2", expand: false, subSubCategory: SubSubCategory(name: "SubSubCategory 2.2.1"))
]
)
]
override func viewDidLoad() {
super.viewDidLoad()
}
//
// MARK: - View Controller DataSource and Delegate
//
func numberOfSections(in tableView: UITableView) -> Int {
return sampleData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var expandCount = 0
if sampleData[section].expand {
// if header is expanded all subCategory will be also expanded
expandCount = sampleData[section].subCategory.count
for subCategory in sampleData[section].subCategory{
//check for how many subSubCategory is expanded
if subCategory.expand{
expandCount += 1
}
}
}
// returning the count of total expanded SubCategories and SubSubCategories
// 1 is for header you can remove if you are using `viewForHeaderInSection`
return 1 + expandCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Header cell
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "header")!
return cell
}else{
var countValue = 0
var indexSubCategory = 0
let sampleDataSection = sampleData[indexPath.section]
// check for how many "subCategory" expanded or collapsed
if sampleDataSection.expand{
for (index, subCategory) in sampleDataSection.subCategory.enumerated(){
countValue += 1
if countValue >= indexPath.row{
indexSubCategory = index
break
}
// check for how many "subSubCategory" expanded or collapsed
if subCategory.expand{
if index == sampleDataSection.subCategory.count-1{
countValue += 2
indexSubCategory = index + 1
}else{
countValue += 1
}
}
}
// if countValue is grater then indexPath.row it will return "subSubCategory" cell
// else/countValue = indexPath.row then return "subCategory" cell
if countValue > indexPath.row{
// Cell subSubCategory
let cell = tableView.dequeueReusableCell(withIdentifier: "subSubCategory")!
cell.textLabel?.text = self.sampleData[indexPath.section].subCategory[indexSubCategory - 1].subSubCategory.name
return cell
}else{
// Cell subCategory
let cell = tableView.dequeueReusableCell(withIdentifier: "subCategory")!
cell.textLabel?.text = self.sampleData[indexPath.section].subCategory[indexSubCategory].name
return cell
}
}
else{
// Cell subCategory
let cell = tableView.dequeueReusableCell(withIdentifier: "subCategory")!
cell.textLabel?.text = self.sampleData[indexPath.section].subCategory[indexPath.row].name
return cell
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// then header cell is selected switch between collapse or expand between "subCategory"
if indexPath.row == 0{
let expand = !sampleData[indexPath.section].expand
//Toggle collapse
sampleData[indexPath.section].expand = expand
self.tableView.reloadSections([indexPath.section], with: .none)
}else{
var countValue = 0
var indexSubCategory = 0
let sampleDataSection = sampleData[indexPath.section]
if sampleDataSection.expand{
for (index, subCategory) in sampleDataSection.subCategory.enumerated(){
countValue += 1
if countValue >= indexPath.row{
indexSubCategory = index
break
}
if subCategory.expand{
if index == sampleDataSection.subCategory.count-1{
countValue += 2
indexSubCategory = index + 1
}else{
countValue += 1
}
}
}
// and if "subCategory" cell is selected switch between collapse or expand between "subSubCategory"
if countValue == indexPath.row{
let subSubCategory = sampleData[indexPath.section].subCategory[indexSubCategory]
let expand = !subSubCategory.expand
sampleData[indexPath.section].subCategory[indexSubCategory].expand = expand
UIView.performWithoutAnimation {
self.tableView.reloadSections([indexPath.section], with: .none)
self.tableView.layoutIfNeeded()
}
}
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
}
Download demo project from here
You can achieve this by creating a custom cell for the SubCategory 2.1, and displaying it in the first row of every section.
Than, in the didSelectRow method, if the first row is selected, you update the state of the SubCategory 2.1 to collapsed (or non collapsed),reload the section and in the numberOfRows method you should return the appropriate number of rows according to the state of the SubCategory 2.1.
A UITableView really isn't designed to show more than two levels of a hierarchy, as sections and rows.
If you want to show more than two levels there are all deferent type of custom solutions to this, one easy way is to design different cell for Subcategory and display accordingly like showed in the image bellow.
In the above example it is same cell but with different background colour to indicate subcategory.
I came across this video on youtube :
https://www.youtube.com/watch?v=VFtsSEYDNRU
It might help you to achieve what you want.
But I think that you could achieve this by making a custom UIView and putting it into your cell so that when you tap on the subcategory it expands and shows more detail as you wanted it.
Hope this helps.
Related
I am using a tableView to take some surveys.
Header I use for a question. Footer for «back» and «next» buttons. And tableView cells for answer options.
Now I started to have a problem, with some user interaction: when you simultaneously click on the “next” button and select an answer, the answer options cease to be active, nothing can be selected. Although the buttons remain active.
Tell me in what direction to look for the problem and how you can debug this problem in order to understand what's wrong.
It all started after fixing bugs, when the application crashed when simultaneously (or almost) pressing the "next" button and choosing an answer. Because the didSelectRowAt method worked after I changed the current array of answer options, and the selected index in the previous question turned out to be larger than the size of the array with the answers to the new question.
class AssessmentVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var footer: FooterTableView?
var header: UIView?
var arrayAssessmnet = [AssessmentDM]()
var assessment: AssessmentDM!
var question: QuestionDM!
var viewSeperationHeader = UIView()
var arrayOptions: [Option]?
var countAssessment = 0
var numberAssessment = 0
var numberQuestion = 0
var countQuestion = 0
var numberQusttionForLabel = 1
var arrayQuestion = [QuestionDM]()
var arrayAnswers = [AnswerDM]()
var arrayEvents = [EventDM]()
override func viewDidLoad() {
super.viewDidLoad()
settingAssessment()
}
//MARK: - settingAssessment()
private func settingAssessment() {
let id = self.assessment.serverId
arrayQuestion = QuestionDM.getQuestions(id: id)
assessmentName.text = assessment.name
countQuestion = arrayQuestion.count
let day = self.assessment.day
arrayAnswers = AnswerDM.getAnswers(idAssessment: id, day: day)
settingQuestion(eventType: .start)
}
//MARK: - settingQuestion()
private func settingQuestion(eventType: EventType? = nil) {
let prevQuestion = question
question = arrayQuestion[numberQuestion]
timeQuestion = 0
footer!.grayNextButton()
//first question
if numberQuestion == 0 && numberAssessment == 0 {
footer!.previousButton.isHidden = true
} else {
footer!.previousButton.isHidden = false
}
arrayOptions = [Option]()
let sortOption = question.options!.sorted {$0.numberOption < $1.numberOption}
for option in sortOption {
arrayOptions?.append(Option(label: option.label, value: option.value))
}
tableView.rowHeight = UITableView.automaticDimension
tableView.reloadData()
heightTableView()
tableView.setContentOffset(.zero, animated: false)
}
//MARK: - heightTableView()
func heightTableView() {
}
//MARK: - UITableViewDataSource
extension AssessmentVC: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewSeperationHeader.isHidden = false
footer?.viewSeperationFooter.isHidden = false
tableView.separatorStyle = .singleLine
return question.options?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath as IndexPath) as AnswerAssessmentCell
cell.initCell(text: arrayOptions![indexPath.row].label, value: arrayOptions![indexPath.row].value, arrayValue: arrayAnswers[numberQuestion].response, isCheckbox: true)
return cell
}
}
//MARK: - UITableViewDelegate
extension AssessmentVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
if question.answerType == "Radio" || question.answerType == "Checkbox"{
selectRadioOrChekbox(indexPath: indexPath)
}
}
}
//MARK: - selectRadioOrChekbox
extension AssessmentVC {
private func selectRadioOrChekbox(indexPath: IndexPath) {
if question.answerType == "Radio" {
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
for cell in cells {
cell.select = false
cell.isSelected = false
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = true
cell.isSelected = true
if arrayOptions?.count ?? 0 > indexPath.row {
arrayAnswers[numberQuestion].response = arrayOptions![indexPath.row].value
footer?.greenNextButton()
}
}
if question.answerType == "Checkbox" {
if arrayOptions?.count ?? 0 > indexPath.row {
//если нажато что-то, что должно сбросить "None"
// question.options![0].isSelect = false
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
if cells[0].answerLabel.text == "None" {
cells[0].select = false
cells[0].isSelected = false
}
var array = arrayAnswers[numberQuestion].response?.components(separatedBy: ";")
array?.removeAll { $0 == "0"}
if array?.count == 0 {
arrayAnswers[numberQuestion].response = nil
} else {
arrayAnswers[numberQuestion].response = array?.joined(separator: ";")
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = !cell.select
cell.isSelected = cell.select
arrayAnswers[numberQuestion].response = array.joined(separator: ";")
if array.count == 0 {
arrayAnswers[numberQuestion].response = nil
footer?.grayNextButton()
} else {
footer?.greenNextButton()
}
}
}
}
}
//MARK: - Navigation between questions
extension AssessmentVC {
func nextQuestion() {
footer!.grayNextButton()
numberQuestion += 1
numberQusttionForLabel += 1
settingQuestion(eventType: .next)
} else {
}
func previousQuestion() {
numberQusttionForLabel -= 1
settingQuestion(eventType: .previous)
}
}
Some snippets that can help you :
// Answer type : use enum . Here the Strong/Codable is if you want to
// save using JSON encoding/decoding
enum AnswerType: String, Codable {
case checkBox = "CheckBox"
case radio = "Radio"
}
Setup of your cell :
class AnswerAssessmentCell: UITableViewCell {
...
// work with Option type
func initCell(option: Option, response: String?, answerType: AnswerType) {
// setup cell contents (labels)
// check for selected status
switch answerType {
case .checkBox:
// check if option is in response
// set isSelected according
break
case .radio:
// check if option is response
// set isSelected according
break
}
}
}
In table view data source :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnswerAssessmentCell
// Use the option to init the cell
// this will also set the selected state
let optionNumber = indexPath.row
cell.initCell(option: arrayOptions![optionNumber], response: arrayAnswers[numberQuestion].response, answerType: question.answerType)
return cell
}
In Table view delegate :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
let optionNumber = indexPath.row
switch question.answerType {
case .radio:
selectRadio(optionNumber: optionNumber)
case .checkBox:
selectCheckBox(optionNumber: optionNumber)
}
// Reload tableview to show changes
tableView.reloadData()
}
// Separate in 2 function for smaller functions
// in this function work only with model data, the reload data will do
// cell update
// only the footer view button cooler may need to be changed
private func selectRadio(optionNumber: Int) {
// Reset current response
// set response to optionNumber
// update footer button cooler if necessary
}
private func selectCheckBox(optionNumber: Int) {
// if option is in response
// remove option from response
// else
// add response to option
// update footer button cooler if necessary
}
Hope this can help you
I'm trying to create a number of sections based on the input from the user in the text field.
take a look at my snapshot to make it easier.
as default, I created 1 section of label 1, label 2, label 3, label 4.
but if the number of section text field is inputed 3, I want the section that fills with labels to increase into 3.
How do I do that?
My problem is that it does not add up another section when I type it.
I had also put tableView.reloadData() in some possible places but it didn't work.
Here's my code, any suggestion will be appreciated.
Thankyou in advance.
var numberOfSection: String = "0"
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
if numberOfSection == "0" {
return 2
}
else if numberOfSection == "1" {
return 2
}
else {
print(numberOfSection)
return Int(numberOfSection) ?? 2
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 1
case 1:
return WSSpec.count
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cells = tableView.dequeueReusableCell(withIdentifier: "serverNumbCell", for: indexPath) as! CellOutlet
let txtFieldServerNumber = cells.txtFieldServerNumb
cells.lblSectionNumb.text = "Number of section:"
txtFieldServerNumber?.addTarget(self, action: #selector(sectionTxtFieldDidChange), for: .editingChanged)
return cells
case 1:
let cells = tableView.dequeueReusableCell(withIdentifier: "serverSpecCell", for: indexPath) as! CellOutlet
let specWS = testSpec[indexPath.row]
cells.labels.text = specWS.spec
return cells
default:
return CellOutlet()
}
}
#objc func sectionTxtFieldDidChange(_ sender: UITextField) {
numberOfSection = sender.text ?? "2"
}
Try adding a didSet to the variable then reloading data from there. Like this:
var numberOfSection: String = "0" {
didSet {
tableView.reloadData()
}
}
I Know same question has been asked before , but it did not help.
I am trying to add section titles to my UITableView. I am able to create the sections and count the number of elements in each section properly, , the cells are repeated the data in all the sections.
I am posting only relevant code -
My model is -
import UIKit
struct Product:Equatable {
let imagename: UIImage }
var productarray = [Product(imagename:#imageLiteral(resourceName: "CakeImage")),
Product( imagename:#imageLiteral(resourceName: "PeasImge")),Product(imagename:#imageLiteral(resourceName: "vectorlogo")),
Product(imagename: #imageLiteral(resourceName: "blue"))]
The ProductViewController is -
import UIKit
class ProductViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let sections = ["Section A", "Section B","Section C", "Section D"]
let rowspersection = [1, 1,1,1]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowspersection[section]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = productarray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
cell.imageView?.image = data.imagename
cell.myParent = self
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0:return "Section A"
case 1:return "Section B"
case 2:return "Section C"
case 3 :return "Section D"
default :return ""
}
}
}
Now, in the above only the first image of the productarray i.e. "[Product(imagename:#imageLiteral(resourceName: "CakeImage"))," is repeated in all the sections as shown in the image below:-
I want all the images/cell to be in the respective sections and not just one image/cell to be repeated in all the sections.
Any help will be appreciated.
Reason:
Since you've a single row in each section, everytime you use indexPath.row which is 0 for the 1st row, you end up accessing productarray[0] for each row in every section.
That's the reason all the rows are same because all of them are filled with productarray[0].
Solution:
Simply use indexPath.section instead of indexPath.row
let data = productarray[indexPath.section]
Note: Instead of making 3 different arrays, you can create a single array of custom type and use that as the dataSource of your tableView. Example:
struct Section {
let name: String
let products: [Product]
}
let sections = [
Section(name: "Section A", products: [Product(imagename:#imageLiteral(resourceName: "CakeImage"))]),
Section(name: "Section B", products: [Product(imagename:#imageLiteral(resourceName: "PeasImge"))]),
Section(name: "Section C", products: [Product(imagename:#imageLiteral(resourceName: "vectorlogo"))]),
Section(name: "Section D", products: [Product(imagename:#imageLiteral(resourceName: "blue"))]),
]
Use sections array as the dataSource. This will avoid a lot of confusion.
in every section indexPath start with zero so it display first index of productarray.
let data = productarray[indexPath.row]
replace with
let data = productarray[indexPath.row + indexPath.section]
Edit
var index = indexPath.row
if indexPath.section != 0, rowspersection.count > indexPath.section - 1{
index += rowspersection[indexPath.section - 1]
}
if index < productarray.count{
let data = productarray[index]
cell.imageView?.image = data.imagename
}
Edit Checkout
Replace this methods
func updateCart(cell: ProductTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
var index = indexPath.row
if indexPath.section != 0, rowspersection.count > indexPath.section - 1{
index += rowspersection[indexPath.section - 1]
}
if index < productarray.count{
let product = productarray[index]
//Update Cart with product
cart.updateCart(with: product)
self.navigationItem.rightBarButtonItem?.title = "Checkout (\(cart.items.count))"
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductTableViewCell") as! ProductTableViewCell
cell.delegate = self // original issue was here, now resolved.
var index = indexPath.row
if indexPath.section != 0, rowspersection.count > indexPath.section - 1{
index += rowspersection[indexPath.section - 1]
}
if index < productarray.count{
let data = productarray[index]
cell.name?.text = data.name
cell.imageView?.image = data.imagename
let product = productarray[index]
cell.setButton(state: self.cart.contains(product: product))
}
return cell
}
I have 5 tebleView sections, code is mostly the same for each of them. Main difference is in cell.labelCell.text:
CellForRow method:
let cell = tableView.dequeueReusableCell(withIdentifier: "ToggleTableViewCell", for: indexPath) as! ToggleTableViewCell
cell.cellSwitch.setOn(false, animated: false)
switch (indexPath.section, indexPath.row) {
case (1,0):
cell.labelCell.text = "Section 1"
cell.callback = { [unowned self] check in
UIView.transition(with: tableView,
duration: 0.5,
options: .showHideTransitionViews,
animations: { self.tableView.reloadData() })
}
cell's class:
class ToggleTableViewCell: UITableViewCell {
#IBOutlet weak var labelCell: UILabel!
#IBOutlet weak var cellSwitch: UISwitch!
var callback:((Bool) -> Void)?
#IBAction func toggleSwitch(_ sender: Any) {
if cellSwitch.isOn == true {
callback?(true)
}
else {
callback?(false)
}
}
}
Question : If I have 5 sections like this and I want to hide for example the first one when toggleSwitch is ON in the last one, is it possible to do it?
Approach:
In your Model, you can create a isHidden property that will keep a track of whether the section should be hidden or not, i.e.
class Model {
var sectionName: String
var isHidden = false
init(sectionName: String) {
self.sectionName = sectionName
}
}
Now, modify the UITableViewDataSource methods to,
class VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
let arr = [Model(sectionName: "Section 1"), Model(sectionName: "Section 2"), Model(sectionName: "Section 3"), Model(sectionName: "Section 4"), Model(sectionName: "Section 5")]
lazy var dataSource = self.arr
func numberOfSections(in tableView: UITableView) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.cellSwitch.setOn(false, animated: false)
cell.labelCell.text = dataSource[indexPath.section].sectionName
cell.callback = {[weak self] in
guard let `self` = self else {
return
}
self.arr[indexPath.section].isHidden = !(self.arr[indexPath.section].isHidden)
self.dataSource = self.arr.filter({ !$0.isHidden })
tableView.reloadData()
}
return cell
}
}
And you can simply call the closure callback?() in toggleSwitch(_:) and the hiding/unhiding will be handled automatically.
class TableViewCell: UITableViewCell {
#IBOutlet weak var labelCell: UILabel!
#IBOutlet weak var cellSwitch: UISwitch!
var callback:(()->())?
#IBAction func toggleSwitch(_ sender: Any) {
callback?()
}
}
I do not know what you want to display, but if the question is "Can you show/hide a section of a tableView" answer is YES
For this to work you need to separate your view with the data you want to display
// let this be declared somewhere
// assume that the top array will be the number of section and inner one will be number fo rows
var tableData: [[String]] = [["1","2","3"], ["4","5","6"], ["7","8","9"], ["10","11","12"], ["13","14","15"]]
// tableView dataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return tableData.count
}
func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int {
return tableData[section].count
}
func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToggleTableViewCell", for: indexPath) as! ToggleTableViewCell
cell.cellSwitch.setOn(false, animated: false)
let data = tableData[indexPath.section]
cell.labelCell.text = data[indexPath.row]
cell.callback = { [unowned self] check in
//removes the data from the selection section
// so if you remove section 2, ["7","8","9"] will be remove
// atm, it just removes but when you decide what you actually want to do then you can play around and implement a toggling action
tableData.remove(at: indexPath.section)
UIView.transition(with: tableView,
duration: 0.5,
options: .showHideTransitionViews,
animations: { self.tableView.reloadData() })
}
}
I have a JSON is look some thing like this :
"product": [
{
"product_id": 471,
"info": "123456",
},
{
"product_id": 471,
"info": "356697456",
},
{
"product_id": 472,
"info": "1432",
},
{
"product_id": 473,
"info": "4321",
},
]
I want to set my TableView to look something like this image below :
what I want is:
If the first cell in the TableView I want the Product 1 (in Red color) shown.
If the second cell's product_id is same with it previous cell's product_id then Product 1 is no longer shown, it disappears.
Since the 3rd cell's product_id is not same with previous cell (second cell), so the red label Product 2 is shown up.
Same go to Product 3 and the rest of the cell in the TableView
What I already tried:
In order to achieve this,I need to get the indexPath inside cellAtRow delegate,so I compare each cell's product_id with the previous one,and then control the logic inside.
Here is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
let thisIndexPath = indexPath.row
if thisIndexPath - 1 > -1 {
let previousProductId = self.productItem[thisIndexPath - 1].productId
let thisProductId = self.productItem[thisIndexPath].productId
if previousProductId == thisProductId {
cell.productLabel.isHidden = true
cell.productHeight.constant = 0
cell.productnameTopContraint.constant = 0
cell.productnameBottomContraints.constant = 0
}else {
cell.productnameLabel.isHidden = false
}
}else{
cell.productnameLabel.isHidden = false
}
cell.item = selfProductItem[indexPath.row]
return cell
}
}
But now the problem is:
-- When the TableView first launch,the UI display like that I shown above,but when I start scrolling,all the cell's Product label(in red color) is gone,although the product_id is not same with the previous cell's product_id.
-- When I scroll back to the first cell,the Product Label(In red color) is gone as well.Which means the UI is only right at first launch of the screen,which is not persistent.
So my question is:
What is the correct way to compare data from current cell to the previous cell?
Is it right to do the comparison inside cellForRowAt delegate method?If not,where should I do this?
I think that to solve your issue, you should think about how you will store your JSON data.
You could begin by creating a struct called 'Product' which will store the product information and then by making it Equatable you can add a function which will allow you to compare the productID's:
/// Structure Of A Product
struct Product: Equatable{
var productID: Int
var productInfo: Int
static func == (lhs: Product, rhs: Product) -> Bool {
return lhs.productID == rhs.productID
}
}
Now to use this your structure you can create an Array variable to store your Products:
//Array To Store Array Of Product
var products = [Product]()
In this example I am just manually inputting the product information but you should handle this in a better way. However, it does illustrate one way you could 'start' to handle this:
override func viewDidLoad() {
super.viewDidLoad()
//1. Create Products
let productOne = Product(productID: 471, productInfo: 123456)
let productTwo = Product(productID: 471, productInfo: 356697456)
let productThree = Product(productID: 472, productInfo: 1432)
let productFour = Product(productID: 473, productInfo: 4321)
//2. Add Them To The Products Array
addUnique(productOne)
addUnique(productTwo)
addUnique(productThree)
addUnique(productFour)
}
/// Checks That A Product Doesn't Exist
///
/// - Parameter product: Product
func addUnique(_ product: Product) {
if !products.contains(product) {
products.append(product)
}
}
In Step 1 we are manually creating the products.
In Step 2 we are calling the addUnique(_ product) function which will only allow unique Products to be stored.
After ensuring that there are no duplicate ProductID's, it should be easy for you to set the format as you like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.productLabel.text = products[indexPath.row].productID
cell.productnameLabel.text = products[indexPath.row].productInfo
}
Of course you will need to fix any colouring of labels etc.
I tried and it is working fine. I make one dummy array for you. Please check bellow
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tblProduct: UITableView!
var arrProduct = NSMutableArray()
var arrForSection = NSMutableArray()
var arrForProductId = NSMutableArray()
override func viewDidLoad()
{
super.viewDidLoad()
let dict = NSMutableDictionary()
dict.setValue("471", forKey: "product_id")
dict.setValue("123456", forKey: "info")
arrProduct.add(dict)
let dict1 = NSMutableDictionary()
dict1.setValue("471", forKey: "product_id")
dict1.setValue("356697456", forKey: "info")
arrProduct.add(dict1)
let dict2 = NSMutableDictionary()
dict2.setValue("472", forKey: "product_id")
dict2.setValue("1432", forKey: "info")
arrProduct.add(dict2)
let dict3 = NSMutableDictionary()
dict3.setValue("472", forKey: "product_id")
dict3.setValue("4321", forKey: "info")
arrProduct.add(dict3)
print(arrProduct)
self.createSection()
}
//MARK:
//MARK: Create section
func createSection()
{
arrForSection.removeAllObjects()
let arrtemp = NSMutableArray()
arrtemp.addObjects(from: (self.arrProduct as NSArray) as! [Any])
for i in 0 ..< arrtemp.count
{
let dict = self.arrProduct[i] as! NSMutableDictionary
let strProductId = (dict["product_id"] as? String)!
if(!arrForProductId .contains(strProductId))
{
arrForProductId.add(strProductId)
}
}
for j in 0 ..< arrForProductId.count
{
let strTempDate:String = arrForProductId .object(at: j) as! String
let arr1 = NSMutableArray()
for i in 0 ..< arrtemp.count
{
let dict = arrtemp .object(at: i) as! NSMutableDictionary
let strProductId = (dict["product_id"] as? String)!
if(strProductId == strTempDate)
{
arr1.add(dict)
}
}
arrForSection.add(arr1)
}
self.tblProduct.reloadData()
}
//MARK:
//MARK: TableView Delegate
func numberOfSections(in tableView: UITableView) -> Int {
return self.arrForSection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return (((arrForSection .object(at: section)) as! NSMutableArray).count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:UITableViewCell = self.tblProduct.dequeueReusableCell(withIdentifier: "cell")!
let dictData = ((arrForSection .object(at: indexPath.section)) as! NSMutableArray).object(at: indexPath.row) as! NSDictionary
cell.textLabel?.text = dictData["info"] as? String
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
return arrForProductId[section] as? String
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Result see attach
Hope it helps!
I think in this case you can divide cells of your table view in sections and assign header (name of product) to each section. Please rever to official documentation for more information.
try setting the height constraints in else part too.
else part of this : if previousProductId == thisProductId { and this : if thisIndexPath - 1 > -1 {.