Getting TableViewCell Index is nil on dropdown item selection - ios

I want tableviewcell index on dropdown item selection. But the index is nil when I am clicking on dropdown item. Is there any way to get index on dropdown item selection? If anyone have any better solution give me some idea.
let EditDropDown = DropDown()
lazy var dropDowns: [DropDown] = {
return [
self.EditDropDown
]
}()
This is my function which I am using for DropDown List.
func setupGenderDropDown() {
let cellHeader = tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
EditDropDown.anchorView = cellHeader.btnDots
EditDropDown.bottomOffset = CGPoint(x: 0, y: 40)
// You can also use localizationKeysDataSource instead. Check the docs.
EditDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
// Action triggered on selection
EditDropDown.selectionAction = { [weak self] (index, item) in
cellHeader.btnDots.setTitle(item, for: .normal)
if item == "Edit"
{
// I am Getting Cell Index but index is nil
let cell = self!.tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
let indexPath = self!.tableview.indexPath(for: cell)
print(indexPath as Any)
let occupation_id = self!.arrayOccupation[(indexPath?.row)!].occupation_Main_id
print(occupation_id)
let next = self!.storyboard?.instantiateViewController(withIdentifier: "EditOccupationVCSID") as! EditOccupationVC
self!.navigationController?.pushViewController(next, animated: false)
next.occupationId = occupation_id
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
}

I am assuming you are using DropDown library to show dropdown. There is a problem where you are getting cell when it is tapped so I have created a demo project (simple tableView and not with custom UITableViewCell) for you and I have added comment to explain the changes. Consider below code:
import UIKit
import DropDown
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [String] = ["We", "Heart", "Swift"]
let editDropDown = DropDown() //Object name should start with small letter
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setupGenderDropDown(cell: UITableViewCell) { //Pass your cell with argument and change type to your custom cell
//By changing cell argument with your custom cell you will get your button for anchor
editDropDown.anchorView = cell.textLabel
editDropDown.bottomOffset = CGPoint(x: 0, y: 40)
editDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
//Here you need to update selectionAction from their library page
editDropDown.selectionAction = { [unowned self] (index: Int, item: String) in
//Here you will get selected item and index
print("Selected item: \(item) at index: \(index)")
if item == "Edit"
{
print(item)
print(index)
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
//This was missing in your code
editDropDown.show()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Get the selected cell this way
guard let indexPath = tableView.indexPathForSelectedRow else { return }
guard let currentCell = tableView.cellForRow(at: indexPath) else { return }
//Pass your selected cell to setupGenderDropDown method
setupGenderDropDown(cell: currentCell)
}
}
HERE you can check demo project. And it's created into Xcode 10.1

Related

Is it wrong to add action to button in tableViewCell with tag?

I have a UItableViewCell with a button inside it, I set the tag of the button and add the action of the button in my ViewController using the tag.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.retryButton.tag = indexPath.row
return cell
}
#IBAction func billHistoryRetryButtonDidTap(_ sender: UIButton) {
let index = sender.tag
if let id = billHistories[index].transactionInfo?.billUniqueID {
hidePayIdGeneralTextField()
billIdTextField.text = id.toNormalNumber()
inquiryGeneralBillRequest()
}
}
I want to know is it wrong for any reason? someone told me it is not good because it uses lots of memory to use tags.
Will it work? yes, but as mentioned above, this is not the best approach, I'd avoid using tags unless this is just for some POC. There are better approaches to handle it.
The first I'd suggest is using delegation to inform back to the controller, here's an example:
class BillHistoryTableViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.index = indexPath.row
cell.delegate = self
return cell
}
}
extension BillHistoryTableViewController: BillHistoryTableViewCellDelegate {
func didTapButton(index: Int) {
print("tapped cell with index:\(index)")
if let id = billHistories[index].transactionInfo?.billUniqueID {
hidePayIdGeneralTextField()
billIdTextField.text = id.toNormalNumber()
inquiryGeneralBillRequest()
}
}
}
protocol BillHistoryTableViewCellDelegate: AnyObject {
func didTapButton(index: Int)
}
class BillHistoryTableViewCell: UITableViewCell {
weak var delegate: BillHistoryTableViewCellDelegate?
var cellData: CellData?
var index: Int?
func setup(with cellData: CellData) {
self.cellData = cellData
}
#IBAction func buttonPressed(_ sender: UIButton) {
guard let index = index else {
return
}
delegate?.didTapButton(index: index)
}
}
Another approach that I prefer lately is using Combine's PassThroughSubject, it requires less wiring and delegate definitions.
import Combine
class BillHistoryTableViewController {
var cancellable: AnyCancellable?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.index = indexPath.row
cancellable = cell.tappedButtonSubject.sink { [weak self] index in
guard let self = self else { return }
print("tapped cell with index:\(index)")
if let id = self.billHistories[index].transactionInfo?.billUniqueID {
self.hidePayIdGeneralTextField()
self.billIdTextField.text = id.toNormalNumber()
self.inquiryGeneralBillRequest()
}
}
return cell
}
}
class BillHistoryTableViewCell: UITableViewCell {
var tappedButtonSubject = PassthroughSubject<Int, Never>()
var cellData: CellData?
var index: Int?
func setup(with cellData: CellData) {
self.cellData = cellData
}
#IBAction func buttonPressed(_ sender: UIButton) {
guard let index = index else {
return
}
tappedButtonSubject.send(index)
}
}
You can make it even shorter by injecting the index with the cellData, e.g:
func setup(with cellData: CellData, index: Int) {
self.cellData = cellData
self.index = index
}
but from what I see in your example, you don't even need the index, you just need the CellData, so if we'll take the Combine examples these are the main small changes you'll have to make:
var tappedButtonSubject = PassthroughSubject<CellData, Never>()
tappedButtonSubject.send(cellData)
and observing it by:
cancellable = cell.tappedButtonSubject.sink { [weak self] cellData in
if let id = cellData.transactionInfo?.billUniqueID {
//
}
}

Sending a signal to collection view within tableview cell from a segment outlet in another tableview cell

I have a segment outlet in a tableview cell in a VC. There are two indexes: 1 and 2.
When I click on 2, I want to tell the collection view within another tableviewcell to reload another view.
And when I click back to 1, I want the same collection view to reload again and display the original content.
Here are my View Controller Functions:
class MyProfileTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,segment
{
//Variable selection to determine what is selected - 1 by default
var viewSelected = "1"
//Segment Function - viewSelected is used to tell VC what index it's on
func segmentSelected(tag: Int, type: String) {
if type == "1" {
print("1")
viewSelected = "1"
} else if type == "2" {
print("2")
viewSelected = "2"
}
}
//Cell For Row - tells tableviewcell to look at viewSelected
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = AboutTableView.dequeueReusableCell(withIdentifier: "ProfileSegmentTableViewCell", for: indexPath) as! ProfileSegmentTableViewCell
cell.segmentCell = self
return cell
} else {
let cell = AboutTableView.dequeueReusableCell(withIdentifier: "1_2Cell", for: indexPath) as! 1_2Cell
cell.viewSelected = viewSelected
return cell
}
Here is the Segment Control TableviewCell
//protocol used to delegate
protocol segment: UIViewController {
func segmentSelected(tag: Int, type: String)
}
class ProfileSegmentTableViewCell: UITableViewCell {
#IBOutlet weak var profileSegmentControl: UISegmentedControl!
var segmentCell: segment?
#IBAction func segmentPressed(_ sender: Any) {
profileSegmentControl.changeUnderlinePosition()
let Index = self.profileSegmentControl.selectedSegmentIndex
if Index == 0
{
segmentCell?.segmentSelected(tag: (sender as AnyObject).tag, type: "1")
)
} else {
segmentCell?.segmentSelected(tag: (sender as AnyObject).tag, type: "2")
}
}
CollectionView
//variable by default
var viewSelected = "1"
//viewDidLoad
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
cView.delegate = self
cView.dataSource = self
get {
self.cView.reloadData()
self.cView.layoutIfNeeded()
}
}
func get(_ completionHandler: #escaping () -> Void) {
getCount.removeAll()
if viewSelected = "1" {
print("1") } else {
print("2)
}
completionHandler()
}
Here's a very simple example of using a closure so your segmented-control cell can communicate with your table view controller.
Your cell class might look like this:
class ProfileSegmentTableViewCell: UITableViewCell {
#IBOutlet var profileSegmentControl: UISegmentedControl!
var callback: ((Int)->())?
#IBAction func segmentPressed(_ sender: Any) {
guard let segControl = sender as? UISegmentedControl else { return }
// tell the controller that the selected segment changed
callback?(segControl.selectedSegmentIndex)
}
}
When the user changes the selected segment, the cell uses the callback closure to inform the controller that a segment was selected.
Then, in your controller, you could have a var to track the currently selected segment index:
// track selected segment index
var currentIndex: Int = 0
and your cellForRowAt code would look like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
// first row - use cell with segemented control
let cell = tableView.dequeueReusableCell(withIdentifier: "ProfileSegmentTableViewCell", for: indexPath) as! ProfileSegmentTableViewCell
// set the segemented control's selected index
cell.profileSegmentControl.selectedSegmentIndex = self.currentIndex
// set the callback closure
cell.callback = { [weak self] idx in
guard let self = self else {
return
}
// update the segment index tracker
self.currentIndex = idx
// reload row containing collection view
self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
}
return cell
} else if indexPath.row == 1 {
// second row - use cell with collection view
let cell = tableView.dequeueReusableCell(withIdentifier: "1_2Cell", for: indexPath) as! My_1_2Cell
// tell the cell which segment index is selected
cell.setData(currentIndex)
return cell
}
// all other rows - use simple Basic cell
let cell = tableView.dequeueReusableCell(withIdentifier: "PlainCell", for: indexPath) as! PlainCell
cell.textLabel?.text = "Row \(indexPath.row)"
return cell
}
Here is a complete example you can run and examine: https://github.com/DonMag/ClosureExample
You can use NotificationCenter.default.addObserver... method and NotificationCenter.default.post..... Read about them. And don't forget to remove observers in deinit

UITableView SIGBRT error when trying to "swipe left to delete " at indexpath.row

I have a simple app that populates a UITableView based on data inputed in a different ViewController. I am trying to implement the "swipe left to delete"
My problem is that this UITableView is a dropdown table view. That is when I click on one cell of the UITableView the cells open up and show me the internal cells associated with that one cells.
I think I am missing something simple as my code to delete the row does not work, it just throws a SIGBRT error. I think because maybe I trying to remove the wrong array maybe? I think it is messed up because it is a dropdown UITableView, so I am left with a bunch of extra UITableview rows?
Code to added delete button and remove selected row.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the data model
tableViewData.remove(at: indexPath.row)
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Whole code in the UITableViewController is as follows:
import Foundation
import UIKit
private let reuseidentifier = "Cell"
struct cellData {
var opened = Bool()
var title = String()
var exerciseData = [String]()
var repsSetsData = [String]()
}
//here
struct Contact {
var fullname: String
var exercises : [Exercise]
}
class Exercise : NSObject , NSSecureCoding{
static var supportsSecureCoding: Bool = true
var excerciseName: String
var excerciseReps: String
var excerciseSets: String
init(Name : String, Reps : String, Sets : String) {
excerciseName = Name
excerciseReps = Reps
excerciseSets = Sets
}
func encode(with aCoder: NSCoder) {
aCoder.encode(excerciseName, forKey: "excerciseName")
aCoder.encode(excerciseReps, forKey: "excerciseReps")
aCoder.encode(excerciseSets, forKey: "excerciseSets")
}
required convenience init?(coder aDecoder: NSCoder) {
let excerciseName = aDecoder.decodeObject(forKey: "excerciseName") as! String
let excerciseReps = aDecoder.decodeObject(forKey: "excerciseReps") as! String
let excerciseSets = aDecoder.decodeObject(forKey: "excerciseSets") as! String
self.init(Name: excerciseName, Reps: excerciseReps, Sets: excerciseSets)
}
}
class ContactController: UITableViewController {
//new
var tableViewData = [cellData]()
var contacts = [Contact]()
override func viewDidLoad() {
super.viewDidLoad()
//getting data from CoreData
self.contacts = CoreDataManager.sharedInstance.retrieveDataFromCoreData()
tableView.register(UINib(nibName: "ExerciseCell", bundle: nil), forCellReuseIdentifier: "ExerciseCell")
for contact in contacts{
var exerciseData = [String]()
var repsSetsData = [String]()
for exercise in contact.exercises{
let name = exercise.excerciseName
let sets = exercise.excerciseSets
let reps = exercise.excerciseReps
exerciseData.append(name)
repsSetsData.append("Reps: " + reps + " Sets: " + sets)
}
self.tableViewData.append(cellData.init(opened: false, title: contact.fullname, exerciseData:exerciseData, repsSetsData: repsSetsData))
}
self.tableView.reloadData()
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Workouts"
view.backgroundColor = .white
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseidentifier)
}
#IBAction func handleAddContact(_ sender: Any) {
let controller = AddContactController()
controller.delegate = self
self.present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
}
//UITABLEVIEW
//all new
override func numberOfSections(in tableView: UITableView) -> Int {
//new
return tableViewData.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
//new
if tableViewData[section].opened == true {
return tableViewData[section].exerciseData.count + 1
}else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
cell.textLabel?.text = tableViewData[indexPath.section].title
return cell
}else {
//use a different cell identifier if needed
let cell = tableView.dequeueReusableCell(withIdentifier: "ExerciseCell", for: indexPath) as! ExerciseCell
cell.exerciseLabel.text = tableViewData[indexPath.section].exerciseData[indexPath.row - 1]
cell.repsSetsLabel.text = tableViewData[indexPath.section].repsSetsData[indexPath.row - 1]
cell.repsSetsLabel.sizeToFit()
return cell
}
}
//did select row new
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableViewData[indexPath.section].opened == true {
tableViewData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}else {
tableViewData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}
}
//being able to delete a row
// this method handles row deletion
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the data model
tableViewData.remove(at: indexPath.row)
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
//this is an extention to addContactController. this is what happens whent he done button is clicked in addcontactcontroller
extension ContactController: AddContactDelegate {
func addContact(contact: Contact) {
self.dismiss(animated: true) {
//Saving Data to CoreData
CoreDataManager.sharedInstance.addContactsToCoreData(contact: contact)
self.contacts.append(contact)
//Settings values in table view
var exerciseData = [String]()
var repsSetsData = [String]()
for exercise in contact.exercises{
let name = exercise.excerciseName
let sets = exercise.excerciseSets
let reps = exercise.excerciseReps
exerciseData.append(name)
repsSetsData.append("Reps: " + reps + " Sets: " + sets)
}
self.tableViewData.append(cellData.init(opened: false, title: contact.fullname, exerciseData:exerciseData, repsSetsData: repsSetsData))
self.tableView.reloadData()
}
}
}
After deleting the rows can you try to reload the tableview cells like this: self.tableView.reloadData()
It's supposed that you delete a row not an entire section , so replace
tableViewData.remove(at: indexPath.row)
with
tableViewData[indexPath.section].exerciseData.remove(at: indexPath.row)
also make sure exerciseData is mutable ( declared as var )

How can i create a hamburger menu with Sub Menu inside

I am working on an app in which requirement is to create a hamburger menu with submenu in it like
I tried different method using table inside table view cell etc but unable to create this menu.
if someone has a solution then recommend me
You can create such an item structure as your data source
struct Item {
let text: String
var subItems: [String]?
var isExpanded = false
init(_ text: String, items: [String]? = nil) {
self.text = text
self.subItems = items
}
}
Usage
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
private let imgOpen = UIImage(named: "open")
private let imgClose = UIImage(named: "close")
private var dataSource = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")
self.tableView.dataSource = self
self.tableView.delegate = self
self.dataSource.append(Item("HOME"))
self.dataSource.append(Item("ABOUT US"))
self.dataSource.append(Item("OUR PROJECTS", items: ["Project-1", "Project-2", "..."]))
self.dataSource.append(Item("BAHRIA TOWN PHASE 1 - 7"))
self.dataSource.append(Item("BAHRIA TOWN PHASE 8"))
self.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let item = self.dataSource[section]
if item.isExpanded, let count = item.subItems?.count {
return count + 1
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.dataSource[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
var imageView: UIImageView?
if indexPath.row > 0, let text = item.subItems?[indexPath.row - 1] {
cell.textLabel?.text = text
} else {
cell.textLabel?.text = item.text
if item.subItems != nil {
imageView = UIImageView(image: item.isExpanded ? self.imgClose : self.imgOpen)
}
}
cell.accessoryView = imageView
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = self.dataSource[indexPath.section]
if indexPath.row == 0 && item.subItems != nil {
self.dataSource[indexPath.section].isExpanded = !item.isExpanded
let indexSet = IndexSet(integer: indexPath.section)
tableView.reloadSections(indexSet, with: .automatic)
} else {
// non-expandable menu item tapped
}
}
}
You should separate the process.
First, create the hamburger menu: for this, I recommend using this 3rd party library: https://github.com/John-Lluch/SWRevealViewController
With the help of this, it is very easy to create a side out menu from the left side of the screen.
The best thing that you will get a ViewController which will responsible for the menu, so you can easily customize it.
Second, as mentioned below, you should use a tableView with expandable cells. The best way to do this is to basically just show the headers of the cells. If the user taps on a header, then show the actual cell. (rowheight > 0). There is a tutorial about this: https://www.youtube.com/watch?v=bSKUYRsMCrM
-> you can create a sliding drawer menu(hamburger menu) using any of the following libraries:
1) REFrostedViewController
2) SWRevealViewController or any other
-> Sub Menu: In the drawer view controller, you have to add a table view and implement expandable/collapsible sections to display a submenu. You can follow any tutorial explaining about expand-collapse table view sections. Some of the tutorial links are below:
https://github.com/jeantimex/ios-swift-collapsible-table-section
https://medium.com/#legonaftik/uitableview-with-collapsible-sections-927d726b985c
1st Follow https://github.com/jonkykong/SideMenu.
And then to make EXPANDABLE Cells:-
You just need to create 2 Cells in UITableView(In Storyboard). First cell for those who are not expandable and Second cell for the expandable.
class SideMenuTableViewController: UITableViewController {
// MARK:- Constants And Vars
var isOurProjectCellExpanded = false
}
class SideMenuTableViewController: UITableViewDataSource, UITableViewDelegate {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "simpleCell", for: indexPath) as! SideMenuBasicTableViewCell
switch indexPath.row {
case 0:
cell.itemName.text = "HOME"
break
case 1:
cell.itemName.text = "About Us"
break
case 2:
if(isOurProjectCellExpanded){
//expandedCell
let cell = tableView.dequeueReusableCell(withIdentifier: "expandedCell", for: indexPath) as! SideMenuBasicTableViewCell
cell.itemName.text = "Our Projects"
return cell
}else{
cell.arrowDownImageView.isHidden = false
cell.itemName.text = "Our Projects"
}
break
case 3:
cell.itemName.text = "Bahria Town phase 1-7"
break
case 4:
cell.itemName.text = "Bahria Town phase 8"
break
default:
break
}
return cell
}
//And in `DidSelectRow` Method
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.row == 2){
if(isOurProjectCellExpanded){
isOurProjectCellExpanded = false
tableView.reloadRows(at: [indexPath], with: .none)
}else{
isOurProjectCellExpanded = true
tableView.reloadRows(at: [indexPath], with: .none)
}
}else if(indexPath.row == 0){
// Handle it yourself
}else if(indexPath.row == 1){
// Handle it yourself
}else if(indexPath.row == 3){
// Handle it yourself
}else if(indexPath.row == 4){
// Handle it yourself
}
}
}

Selected row from each section of UITableView ( Multiple Selection )

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

Resources