Reputation: 621
I'm trying to implement UISegmentedControl
in each dequeueReusableCell UITableViewCell
like so:
The Issue: Each TableViewCell is referencing to the same Segmented Control and I'm unable to fetch the state of the control for any cell in particular. As per my understanding, there's only one instance of SegmentedControl that is being initialised and that instance is being shared by all the TableViewCells, and because of that I can't access the unique value of the state for any particular TableViewCell, eg: I'm unable to access what the SegmentControl state is set to for the 3rd cell.
View Controller Code:
import UIKit
import UserNotifications
class MarkAttendanceViewController: UIViewController {
var pickedDate: Date = Date.init()
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .light
}
}
@IBAction func datePicker(_ sender: UIDatePicker) {
pickedDate = sender.date.addingTimeInterval(19800)
let weekDay = Calendar(identifier:.gregorian).component(.weekday, from: pickedDate)
print(weekDay)
updateSubjects(pickedDate)
}
func updateSubjects(_ pickedDate: Date) {
}
}
extension MarkAttendanceViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class SubjectTableViewCell: UITableViewCell {
@IBOutlet var SessionType: UILabel!
@IBOutlet var SessionName: UILabel!
@IBOutlet var segmentControlOutlet: UISegmentedControl!
@IBAction func segmentedControlIndex(_ sender: UISegmentedControl) {
print(sender.selectedSegmentIndex)
}
}
Github Link here
Please let me know if there's any more information that I need to provide or if the question isn't clear. TIA
Upvotes: 4
Views: 3292
Reputation: 892
I would suggest that the answers you have been given, including the accepted answers, are quick fixes that don't actually address the real problem with how you have architected this piece of software. You may not care at this point, but for future readers of the question this may be helpful.
You may have heard of the Model-View-Controller (MVC) architecture that is commonly used when developing for the iOS platform. In the case of your software you have a View -- for simplicity's sake, let's just consider the table view cells as the view in this case. You have a controller -- your MarkAttendanceViewController
which implements the UITableViewDataSource
and UITableViewDelegate
interfaces. The issue, however, is that you don't really have a model for the data you are displaying in the view. In fact, the root of your problem stems from the fact that you are using the view as the model as well, which is problematic because table view cells are reused and the data contained in them can be lost during the cell reuse process if it is not stored somewhere else. If the data is stored in a data model class, you can keep it separate from the table view cells and it will persist through cell reuse.
You have 3 pieces of data associated with each table view cell: The SessionType
, the SessionName
and the attendance status for the session (ie: Attended
, Missed
, Mass Bunk
or No Lecture
). A data model for this could look like this (with an enumerated type to represent the attendance status):
enum AttendanceStatus: Int {
case attended
case missed
case massBunk
case noLecture
}
struct Session {
let name: String
let type: String
var attendanceStatus: AttendanceStatus
}
You may also want to represent type
with an enum, but let's keep this simple.
You can instantiate an instance of this data model as follows:
var session = Session(name: "Network Security", type: "Lecture", attendanceStatus: .attended)
Note the var
keyword to make it mutable, as you will want to change the attendanceStatus
when the UISegmentedControl
value changes. Changing this property is done like so:
session.attendanceStatus = .noLecture
To map from your segmented control to AttendanceStatus
, you can use the Int raw value for the enum, as follows:
AttendanceStatus(rawValue: segmentedControl.selectedSegmentIndex)
And to map from your data model's attendanceStatus
property to a selectedSegmentIndex
for your segmented control:
segmentedControl.selectedSegmentIndex = session.attendanceStatus.rawValue
Now in your view controller, you can instantiate an array of Session
objects and use that to populate your table view. When a segmented control changes, you can use the indexPath.row
of the table view cell for the segmented control in order to find the Session
instance in your array of sessions!
(For a more advanced implementation of this, you can also looking into the Model-View-ViewModel (MVVM) architecture which provides an even cleaner way of bidirectional mapping between the data model and the view)
Upvotes: 2
Reputation: 336
it seems that the root cause of the issue that would like to pass the data between the cell and the VC containing the table and this is done simple by delegate and protocol design pattern as below
protocol SubjectTableViewCellDelegate {
func didSelectSegmentControlCell(cell: SegmentCell)
}
import UIKit
class SubjectTableViewCell: UITableViewCell {
// MARK: Properties
var delegate: SubjectTableViewCellDelegate?
// MARK: IBOutlets
@IBOutlet weak var segmentControl: UISegmentedControl!
// MARK: Life Cycle Methods
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: IB Actions
@IBAction func segmentControlAction(_ sender: UISegmentedControl) {
delegate?.didSelectSegmentControlCell(cell: self)
}
}
import UIKit
class MarkAttendanceViewController: UIViewController, SegmentCellDelegate, UITableViewDelegate, UITableViewDataSource {
// MARK: SegmentCellDelegate Methods
func didSelectSegmentControlCell(cell: SegmentCell){
// you will have the cell that contains all the data
/* all your business here */
}
extension MarkAttendanceViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
/* remember to have thee delegate of the cell to self as the below line */
cell.delegate = self
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
Upvotes: 3
Reputation: 12214
You should set the tag
of your segmentControlOutlet
to indexPath.row
in cellForRowAt:IndexPath
method.
Also you must add an action on valueChange
event on each of your UISegmentedControl
in the same method.
below code might give you some idea:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
// add an action on value change to detect a change in the value of segmented control
cell.segmentControlOutlet.addTarget(self, action: #selector(segmentValueChanged(_:)), for: .valueChanged)
// set the tag property of your segmented control to uniquely identify each segmented control in the value change event
cell.segmentControlOutlet.tag = indexPath.row
return cell
}
and you can distinguish among various instances of UISegmentedControl
using the tag
property that you set inside the cellForRow
method.
@objc func segmentValueChanged(_ sender: UISegmentedControl) {
switch sender.tag {
case 0:
// do something on value changed in segmented control in first cell and so on...
print(sender.tag)
default:
break
}
print(sender.selectedSegmentIndex)
}
Hope this helps
Upvotes: 6
Reputation: 942
There are multiple way to solve this Problem.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
cell.SessionType.text = "Lecture"
cell.SessionName.text = "Network Security"
// add an action on value change to detect a change in the value of segmented control
cell.segmentControlOutlet.addTarget(self, action: #selector(segmentChanged(_:)), for: .valueChanged)
// set the tag property of your segmented control to uniquely identify each segmented control in the value change event
cell.segmentControlOutlet.tag = indexPath.section
return cell
}
Then Find cell based on Segment Control.
@objc func segmentChanged(_ sender: UISegmentedControl) {
if let cell = sender.superview as! UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
print(indexPath.row)
if indexPath.row == 0 {
print("segment event of cell 0")
}
else if indexPath.row == 1 {
print("segment event of cell 1")
}
}
}
you can also use delegate and Clouser
Upvotes: 1
Reputation: 869
Use this toterial. add UITableViewCell
to your project and set UISegment
action in custom UITableViewCell
Upvotes: 4