问题
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