Get Switch State from specific cell

霸气de小男生 提交于 2020-12-06 16:55:11

问题


I am working on a iOS app that has two ViewControllers. The first is a TableView which creates a row for each index in a array. Each cell of this TableView shows the content in the array corresponding to the index and has a switch. The second ViewController has an image and a label and they are supposed to change depending the switch state. So, how can I get the switch state from a specific cell?

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

@IBOutlet weak var state_label: UILabel!
@IBOutlet weak var descr_label: UILabel!
@IBOutlet weak var myimg: UIImageView!
let arr: [String] = ["Text1","Text2", "Text3", "Text4"]

var switch_isOn = false

override func viewDidLoad() {
    super.viewDidLoad()
    if(switch_isOn == false){
        myimg?.image = UIImage(named: "img1")
    }else{
        myimg?.image = UIImage(named: "img2")
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return arr.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
    cell.textLabel?.text = arr[indexPath.row]

    let mySwitch = UISwitch()
    cell.accessoryView = mySwitch

    mySwitch.tag = 1001

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let segueIdentifier: String
    segueIdentifier = "segue"

    // Get the selected Cell and Iterate through it's subviews to find the switch using the tag
    let cell = tableView.cellForRow(at: indexPath)

    //Get the Cell Text
    print("\(cell?.textLabel?.text ?? "")")

    // Iterate through subviews of Cell
    for v in cell?.subviews ?? [] {

        // If a view found with tag == 1001 then it's the switch view because we had assigned 1001 to the switch view
        if v.tag == 1001 {

            // One last check we cast the view to UISwitch if it succeed then it's the switch view
            if let mySwitch = v as? UISwitch {

                if(mySwitch.isOn == true){
                    descr_label?.text = "\(cell?.textLabel?.text ?? "")"
                    print("The cell has the Switch On")
                    switch_isOn = true
                }else{
                    descr_label?.text = "\(cell?.textLabel?.text ?? "")"
                    switch_isOn = false
                    print("The cell has the Switch Off")
                }
            }
        }
    }
    self.performSegue(withIdentifier: segueIdentifier, sender: self)
}

}


回答1:


Using the accessory view for the switch seems to be an easy solution but it's very cumbersome to access the view. Something like for v in cell?.subviews ?? [] and dealing with tags is horrible.

A better more efficient solution is a custom cell class.

In Interface Builder set the style of the cell to custom and drag an UILabel and an UISwitch into the canvas. Set the class of the cell to TableViewCell.

Add a new CocoaTouch class TableViewCell as subclass of UITableViewCell. You need two IBOutlets, one IBAction and a callback variable. The callback is important to keep the state of the switch in the model. Connect the outlets and the action in IB.

class TableViewCell: UITableViewCell {

    @IBOutlet weak var switcher : UISwitch!
    @IBOutlet weak var label : UILabel!

    var callback : ((Bool)->())?

    @IBAction func switchChanged(_ sender : UISwitch) {
        callback?(sender.isOn)
    }
}

Create a data source model containing the text and the state of the switch

struct Item {
    var text : String
    var isSelected : Bool 

    init(text : String, isSelected : Bool = false {
        self.text = text
        self.isSelected = isSelected
    }
}

Declare the data source array

var arr : [Item] = [Item(text: "Text1"), Item(text: "Text2"), Item(text: "Text3"), Item(text: "Text4")]

Replace cellForRow with

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
    let item = arr[indexPath.row]
    cell.label.text = item.text
    cell.switcher.isOn = item.isSelected

    // the callback updates the model and is called when the value of the switch changes
    cell.callback = { newValue in
         item.isSelected = newValue
    }

    return cell
}

Replace didSelectRow with (yes, it's only one line, it passes the index path as sender parameter)

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    self.performSegue(withIdentifier: "segue", sender: indexPath)
}

Finally Implement prepare(for segue

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "segue" {
        let viewController = segue.destination as! ViewController // the class of the second view controller
        // get the current index path
        let indexPath = sender as! IndexPath
        let item = arr[indexPath.row]
        // get the state of the switch from the model, not from the view 
        let isSelected = item.isSelected
        // do something with `isSelected`  
        }
    }
}



回答2:


In order to achieve what you want properly, you're going to want to set up a custom cell.

An example of this is below, and assumes a Storyboard/XIB UI:

import UIKit

class SwitchTableViewCell: UITableViewCell {
    @IBOutlet weak var textLabel: UILabel!
    @IBOutlet weak var contentSwitch: UISwitch!

    // So that we can identify the cell in our table view controller.
    static let identifier: String {
        return String(describing: type(of: self))
    }
}

in order to use this with your table view. you will have to register the cell for use in SwitchTableViewController.viewDidLoad():

tableView.register(SwitchTableViewCell.self, forCellReuseIdentifier: SwitchTableViewCell.identifier)

Next, you're going to want to modify cellForRowAt:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(SwitchTableViewCell.identifier, forIndexPath: indexPath) as! SwitchTableViewCell
    cell.textLabel?.text = arr[indexPath.row]
    // cell.contentSwitch will be setup as an outlet via Storyboard / XIB.
    return cell
}

after that's done, go ahead and add a variable to SwitchTableViewController:

fileprivate var selectedState: UIControl.State?

And update didSelectRowAt to store the state from the cell:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRowAtIndexPath(indexPath) as! SwitchTableViewCell
    selectedState = cell.contentSwitch.state
    segueIdentifier = "segue" // probably want a more meaningful segue name here.
    self.performSegue(withIdentifier: segueIdentifier, sender: self)
}

finally, override prepare(for:sender:):

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "segue" {
        if let vc = segue.destination as? ContentViewController { // cast accordingly, 'ContentViewController' is placeholder
            // pass the state to the destination view controller
            vc.state = selectedState
            selectedState = nil
        }
    }
}

and that's you done!




回答3:


There are many ways to read the state of a switch from a cell. You can create a custom cell class and access that using IBOutlets and even you can use delegates to get back from the Custom Cell class back to your View Controller. If you're using this code for learning purposes it's Ok to use and add any types of Controls to the cell like this but in real project you might try Custom cells.

See the commented areas in the code

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

@IBOutlet weak var myimg: UIImageView!
   var arr: [String] = ["bla", "blablabla", "blabla"]

   override func viewDidLoad() {
    super.viewDidLoad()
    myimg?.image = UIImage(named: "Image1")
}

override func didReceiveMemoryWarning() {
     super.didReceiveMemoryWarning()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->   UITableViewCell {
    let cell = UITableViewCell(style: UITableViewCellStyle.default,
    reuseIdentifier: "cell")
    cell.textLabel?.text = arr[indexPath.row]
    let mySwitch = UISwitch()

    // Add a tag to your switch so later on you can access the switch using this tag
    mySwitch.tag = 1001

    cell.accessoryView = mySwitch
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
     segueIdentifier = "segue"

     // Get the selected Cell and Iterate through it's subviews to find the switch using the tag
     let cell = tableView.cellForRow(at: indexPath)

     // Iterate through subviews of Cell
     for v in cell?.subviews ?? [] {

        // If a view found with tag == 1001 then it's the switch view because we had assigned 1001 to the switch view
        if v.tag == 1001 {

            // One last check we cast the view to UISwitch if it succeed then it's the switch view
            if let mySwitch = v as? UISwitch {

               // Here you can get the state of the switch
                let switchState = mySwitch.state

            }
        }
    }
      self.performSegue(withIdentifier: segueIdentifier, sender: self)
   }
}

As I said this is not the best way to add and read views using tags but still good to know that you can

Edit:

Here is the complete solution for your project to work. You already have a ViewController but you don't have a DetailViewController to which you want to segue

View Controller Code

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {


let rooms: [String] = ["Kitchen","Living Room", "Master's Bedroom", "Guest's Bedroom"]
let segueIdentifier = "segueIdentifier"
var switch_isOn = false


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return rooms.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
    cell.textLabel?.text = rooms[indexPath.row]

    let mySwitch = UISwitch()
    cell.accessoryView = mySwitch

    mySwitch.tag = 1001

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    // Get the selected Cell and Iterate through it's subviews to find the switch using the tag
    let cell = tableView.cellForRow(at: indexPath)

    // Iterate through subviews of Cell
    for v in cell?.subviews ?? [] {

        // If a view found with tag == 1001 then it's the switch view because we had assigned 1001 to the switch view
        if v.tag == 1001 {
            // One last check we cast the view to UISwitch if it succeed then it's the switch view
            if let mySwitch = v as? UISwitch {
                // Assign the current state of the switch to switch_isOn variable
                self.switch_isOn = mySwitch.isOn
            }
        }
    }
    self.performSegue(withIdentifier: segueIdentifier, sender: indexPath)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == segueIdentifier {
        if let detailViewController = segue.destination as? DetailViewController {
            // used guard let to be on safe side
            guard let indexPath = sender as? IndexPath else { return }
            // pass in the data needs to the detail view controller
            detailViewController.descr = rooms[indexPath.row]
            detailViewController.isOn = switch_isOn
        }
    }
}
}

Detail View Controller Code

import UIKit

class DetailViewController: UIViewController {


@IBOutlet weak var descr_label: UILabel!
@IBOutlet weak var state_label: UILabel!
@IBOutlet weak var myImageView: UIImageView!

var descr = ""
var isOn = false

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    descr_label.text = descr
    // for UIImage you can use UIImage(named: "on_image") but i have used the imageLiteral which is pics directly the image from xcassets 
    myImageView.image = isOn ? #imageLiteral(resourceName: "on_image") : #imageLiteral(resourceName: "off_image")
    state_label.text = isOn ? "Switch is ON" : "Switch is Off"

}

}

Example Project download here



来源:https://stackoverflow.com/questions/51712309/get-switch-state-from-specific-cell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!