简体   繁体   中英

UISegmentedControl in each UITableViewCell of UITableView

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

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

Use this toterial. add UITableViewCell to your project and set UISegment action in custom UITableViewCell

在此输入图像描述

在此输入图像描述

在此输入图像描述

在此输入图像描述

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

  • you will have a protocol defining the data to be passed between two members as below
protocol SubjectTableViewCellDelegate {
    func didSelectSegmentControlCell(cell: SegmentCell)
}
  • then you will have cell containing the segment control and a delegate var of type SegmentControlDelegate as below
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)
    }

}
  • then you will have your VC acting as a delegate of the Segment cell after having each cell delegate to be the VC containing the Table
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)
    }
}

}
  • the idea is a general idea imagine there is a button or date picker or any other outlet you should use this pattern to move data between two sides

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

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM