I'm having some trouble implementing a UISwitch in a UITableViewCell. My tableViewCell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = "Hello Magic Switch buyers!"
cell.textLabel?.textColor = UIColor.whiteColor()
cell.backgroundColor = UIColor.clearColor()
lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.on = false
lightSwitch.addTarget(self, action: "switchTriggered", forControlEvents: .ValueChanged );
cell.accessoryView = lightSwitch
return cell
}
This creates the switch, everything goes right, until the point the function gets called.. What you would normally do with for example a button, is just using it's indexPath.row to make a difference between all the cells, but as this is an accessory view, I wasn't able to get this working! The switchTriggered function:
func switchTriggered() {
if lightSwitch.on {
let client:UDPClient = UDPClient(addr: "192.168.1.177", port: 8888)
let (_) = client.send(str: "HIGH" )
print(String(indexPath.row) + " set to high")
} else {
let client:UDPClient = UDPClient(addr: "192.168.1.177", port: 8888)
let (_) = client.send(str: "LOW" )
print(String(indexPath.row) + " set to low")
}
}
The function doesn't know which lightSwitch was switched and what the indexPath is.. How could I fix this? If it was a button, I could use accessoryButtonTappedForRowWithIndexPath but this is not the case.
Some help would be appreciated, as all the information about UISwitches in TableViewCells is in Objective-C.
Thank you very much!
The easiest solution is to use tag property of the switch. When creating switch, assign a tag
lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.tag = 2000
lightSwitch.addTarget(self, action: Selector("switchTriggered:"), forControlEvents: .ValueChanged );
Then modify your handler method to have a sender parameter
func switchTriggered(sender: AnyObject) {
let switch = sender as! UISwitch
if switch.tag == 2000 {
// It's the lightSwitch
}
}
Related
In my App, I have a tableview with 7 sections and each section has 8cells containing Stepper(in UIView),UIButton(in UIView) and UISwitch(in UIview).
Now I am able to set and get values for stepper and UIButton But my issue is with UISwitch.
It is getting reused and shows On/Off states simultaneously(wiered UI).
Also I have maintained an Array to save status of switch but it doesnot reflect properly.
Below is the code
var myData1 : [Bool] = [false,false,false,false,false,false,false,false]
let cell:DefaultTableViewCell = (self.tblviewSwitchPOints.dequeueReusableCell(withIdentifier: "defaultcell", for: indexPath) as? DefaultTableViewCell)!
cell.viewForButton.tag = indexPath.row
cell.selectionStyle = .none
cell.viewForSwitch.tag = indexPath.row
print("in cell for row")
if indexPath.section == 0
{
switch indexPath.row
{
case 0:
cell.lblMain.text = timePerDay[indexPath.row]
cell.lblTemp.text = row1[0]
cell.lblTime.text = row1[1]
cell.lblMain.tag = indexPath.row
cell.viewForStepper.addSubview(valueStepper1)
cell.viewForButton.addSubview(btnCustom1)
cell.viewForSwitch.addSubview(switchOnOff1)
if myData1.count==0
{
}else
{
switchOnOff1.isOn = myData1[0]
}
switchOnOff1.tag = 0
switchOnOff1.addTarget(self,action:#selector(actionOnOff1(sender:)), for:.valueChanged )
let temp = arrTemp1[indexPath.row]
if arrTimeInDouble1.count==0
{
}else if temp.elementsEqual("FFFF")
{
valueStepper1.value = 5
}else
{
valueStepper1.value = arrTimeInDouble1[indexPath.row]
}
valueStepper1.tag = indexPath.row
btnCustom1.tag = indexPath.row
if arrTime1.count==0
{
}else
{
btnCustom1.titleLabel?.text = arrTime1[indexPath.row]
}
valueStepper1.addTarget(self, action: #selector(valueChangedTemp1(sender:)), for: .valueChanged)
btnCustom1.addTarget(self, action:#selector(datePickerTapped1), for: .touchUpInside)
print("in cell fro row1\(myData1)\(switchOnOff3.isOn)\(switchOnOff1.tag)")
return cell
case 1:
cell.lblMain.text = timePerDay[indexPath.row]
cell.lblTemp.text = row2[0]
cell.lblTime.text = row2[1]
cell.lblMain.tag = indexPath.row
cell.viewForStepper.addSubview(valueStepper2)
cell.viewForSwitch.addSubview(switchOnOff2)
if myData1.count==0
{
}else
{
switchOnOff2.isOn = myData1[1]
}
switchOnOff2.tag = indexPath.row
if arrTimeInDouble1.count==0
{
}else
{
valueStepper2.value = arrTimeInDouble1[indexPath.row]
}
cell.viewForButton.addSubview(btnCustom2)
btnCustom2.tag=indexPath.row
valueStepper2.tag = indexPath.row
if arrTime1.count==0
{
}else
{
btnCustom2.titleLabel?.text = arrTime1[indexPath.row]
}
switchOnOff2.addTarget(self, action: #selector(actionOnOff1(sender:)), for:.valueChanged )
valueStepper2.addTarget(self, action: #selector(valueChangedTemp1(sender:)), for: .valueChanged)
btnCustom2.addTarget(self, action:#selector(datePickerTapped1), for: .touchUpInside)
return cell
}
A lot may have gone wrong but what I suspect in your case is that each of your switches has multiple target/action pairs assigned; You dequeue a cell and then call addTarget on switches where a previous target is still on the switch.
In any case what is best done when dealing in these situations is to put most of your code into your table view cell. I actually suggest you to use delegate to get messages back from cell to your view controller so dequeuing should look something like this:
let cell: DefaultTableViewCell = tableView.dequeueReusableCell(withIdentifier: "defaultcell", for: indexPath) as! DefaultTableViewCell
cell.myObject = myObjectSections[indexPath.section][indexPath.row]
cell.delegate = self
return cell
Nothing else. Now cell should do all the logic:
class DefaultTableViewCell: UITableViewCell {
weak var delegate: DefaultTableViewCellDelegate?
var myObject: MyObject? {
didSet { refresh() }
}
override func awakeFromNib() {
super.awakeFromNib()
self.switch.addTarget(self, action:#selector(switchMoved)), for:.valueChanged)
}
func refresh() {
// Change all UI related stuff here
}
#objc private func switchMoved() {
if let myObject = myObject {
delegate?.defaultTableViewCell(self, didChangeWhateverStateOf:myObject to: switch.on)
}
}
}
Naturally a protocol needs to be defined appropriately. For my case that is
protocol DefaultTableViewCellDelegate: class {
func defaultTableViewCell(_ sender: DefaultTableViewCell, didChangeWhateverStateOf myObject: MyObject, to flag: Bool)
}
and your view controller needs to implement this protocol.
Sometimes it may actually make sense to add an index path with the data as well. To do so you simply upgrade this code so cell also contains var indexPath: IndexPath!. Now after dequeuing a cell you assign its new index path. And then in your delegate method you can access it at any time:
extension MyViewController: DefaultTableViewCellDelegate {
func defaultTableViewCell(_ sender: DefaultTableViewCell, didChangeWhateverStateOf myObject: MyObject, to flag: Bool) {
print("This is cell at section \(sender.indexPath.section), row: \(sender.indexPath.row)")
}
}
Would be good if you create a custom cell with UISwitch in it. Then you can assign value to the switch like this -
var cell : SwitchTableViewCell? = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as? SwitchTableViewCell
if(cell == nil)
{
cell = SwitchTableViewCell(style: .default, reuseIdentifier: "CellIdentifier")
}
if(indexPath.section == 0)
{
if(myData1.count != 0)
{
cell?.switchOnOff.isOn = myData1[indexPath.row];
}
}
return cell!;
I have a TableView and a label inside of it called Post. I have a Gesture Tap function and when a user taps that label then the TableView label that was tapped changes color. The issue is with the tableView recycling if I go down the tableView then other cells are also highlighted that were not clicked . How can I fix it so that only the clicked cell is highlighted ? This is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileTVC", for: indexPath) as! UserProfileTVC
cell.post.text = Posts[indexPath.row]
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
cell.post.addGestureRecognizer(post_tap)
return cell
}
func post_tapped(sender:UITapGestureRecognizer) {
let point = CGPoint(x: 0, y: 0)
let position: CGPoint = sender.view!.convert(point, to: self.TableSource)
if self.TableSource.indexPathForRow(at: position) != nil {
sender.view?.backgroundColor = UIColor.blue
}
}
Again the code works and it highlights the correct TableCell label the issue is when scrolling down the tableView other tableCells label also get highlighted without being clicked .
I have updated the code with the sample given below but it is still giving the same results
if let existingRecognizerView = cell.viewWithTag(101) as UIView? {
existingRecognizerView.backgroundColor = UIColor.white
} else {
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
post_tap.view?.tag = 101
cell.post.addGestureRecognizer(post_tap)
}
In your cellForRowAtIndexPath function you are dequeuing a cell that already has a UITapGestureRecognizer on it with a blue background. You will need to add a tag to its view so that you can get access to it when dequeuing and remove the background color.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileTVC", for: indexPath) as! UserProfileTVC
cell.post.text = Posts[indexPath.row]
// Check if we already have a GestureRecognizer view
if let existingRecognizerView = cell.post.viewWithTag(101) {
existingRecognizerView.backgroundColor = UIColor.white
} else {
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
cell.post.addGestureRecognizer(post_tap)
post_tap.view.tag = 101
}
return cell
}
func post_tapped(sender:UITapGestureRecognizer) {
let point = CGPoint(x: 0, y: 0)
let position: CGPoint = sender.view!.convert(point, to: self.TableSource)
if self.TableSource.indexPathForRow(at: position) != nil {
sender.view?.backgroundColor = UIColor.blue
}
}
*Note: coded from a mobile device... not tested for syntactical or functional errors.
I have programmatically created a TableView using sections in swift. The amount of sections and rows will always be the same, as they're just displaying info that I pull in from Firebase.
I would like to change the textLabel of one of the cells to align to the top, instead of the middle of the cell. All other textLabels I would like to remain centered. The cell in question is section 1, row 1.
It currently looks like:
I've been looking for how to do this for a while, and a lot of people say to do:
cell.textLabel?.numberOfLines = 0
cell.textLabel?.sizeToFit()
But this isn't working. I'm placing this code in my cellForRowAtIndexPath method.
if indexPath.section == 0 {
if indexPath.row == 0 {
cell.textLabel!.text = "Our review"
} else {
cell.textLabel!.text = film?.Main_Review
cell.textLabel?.numberOfLines = 0 // HERE
cell.textLabel?.sizeToFit() // HERE
}
}
Nothing happens, the text still aligns to centre of the cell.
Does anyone know a simple way of doing this? Preferably without having to create a custom cell (as this only needs to effect 1 row)? Thanks
UPDATE - All code for cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: .Value1, reuseIdentifier: cellId)
cell.selectionStyle = .None
cell.textLabel?.font = UIFont(name: "Verdana", size: 14)
cell.textLabel?.textColor = UIColor.darkGrayColor()
cell.detailTextLabel?.font = UIFont(name: "Verdana", size: 14)
if indexPath.section == 0 {
if indexPath.row == 0 {
cell.textLabel!.text = "Share"
}
} else if indexPath.section == 1 {
if indexPath.row == 0 {
cell.textLabel!.text = "Our review"
} else {
cell.textLabel!.text = film?.Main_Review
cell.textLabel?.numberOfLines = 0
cell.textLabel?.sizeToFit()
}
} else if indexPath.section == 2 {
if indexPath.row == 0 {
cell.textLabel!.text = "UK release date"
cell.detailTextLabel!.text = film?.Date_Released?.capitalizedString
} else if indexPath.row == 1 {
cell.textLabel!.text = "Directed by"
cell.detailTextLabel!.text = film?.Directed_By?.capitalizedString
} else if indexPath.row == 2 {
cell.textLabel!.text = "Running time"
cell.detailTextLabel!.text = film?.Running_Time
} else {
cell.textLabel!.text = "Certificate rating"
cell.detailTextLabel!.text = film?.Certificate_Rating
}
}
return cell
}
cell.textLabel is a default label in UITableViewCell. If you want your text to start at the top you should create a subclasse of UITableViewCell and create your own view for this cell.
For example
import Foundation
import UIKit
class ItemCell: UITableViewCell {
var label : UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.label = UILabel()
self.contentView.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.label.frame = CGRectMake(15,10,200,30)
self.label.adjustsFontSizeToFitWidth = true
self.label.textColor = UIColor.greenColor()
}
}
Then in your viewController dont forget to use the register class method like this
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.registerClass(ItemCell.self, forCellReuseIdentifier: "cellIdentifier")
And in your cellForRowAtIndexPath
let cell:ItemCell! = tableView.dequeueReusableCellWithIdentifier("cellIdentifier") as? ItemCell
cell.textLabel?.sizeToFit()
should make your UILabel has the size need it to wrap the content. So a good thing you could do is change the background to see if this line is do the job.
I guess the problem is your constraints in the cell. You should have a look either looking at your code or your storyboards.
Autolayout tutorial
Note: you should edit your question, otherwise you could get some negatives for this. If it is section 0 can not be section 1.
if indexPath.section == 0 {
if indexPath.section == 1 {
/*your code */
}
}
I've googled this answer multiple times and what I'm finding is not either working or doesn't make sense to me. Could anyone take a whack at this and help me out? Been stuck on this issue for hours.
Goto to CellForRowAtIndexPath in UITableView Delegate Method
Add this code it works for you
For UILabel:
var newLabel = UILabel(frame: CGRectMake(280.0, 14.0, 300.0, 30.0)) //make frame according to your need
newLabel.text = newData[indexPath.row]
newLabel.tag = 1
for UISwitch:
var switchView: UISwitch = UISwitch(frame: CGRectZero)
aCell.accessoryView = switchView
switchView.setOn(false, animated: false)
switchView.addTarget(self, action: "switchChanged:", forControlEvents: .ValueChanged)
What do you mean by not working? Just drag a tableview in the storyboard's view controller and then a UITableViewCell into it, followed by a UILabel and UISwitch. I've attached a screenshot.
Create a tableviewcell custom class, drag and link the switch and label into .h of the class file and that's it you'll get it working as simple as that.
Don't forget to link UITableview's 'datasource' and 'delegate' method's.
You can download custom cell with table view example.
There is label and switch.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : CustomCell! = tableView.dequeueReusableCellWithIdentifier("ID_CustomCell", forIndexPath: indexPath) as! CustomCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.lblData.text = myArray.objectAtIndex(indexPath.row).valueForKey("Data") as? String
if let str = myArray.objectAtIndex(indexPath.row).valueForKey("Data") as? String
{
cell.lblData.text = str as String
}
cell.switch1.tag = indexPath.row
cell.switch1.addTarget(self, action: "switchChanged:", forControlEvents: UIControlEvents.ValueChanged)
return cell
}
func switchChanged(mySwitch: UISwitch) {
let no = mySwitch.tag
let selectedIndex = NSIndexPath(forRow: no, inSection: 0)
let currentCell : CustomCell = table1.cellForRowAtIndexPath(selectedIndex)! as! CustomCell
if currentCell.switch1.on
{
currentCell.switch1.setOn(true, animated: true)
}
else
{
currentCell.switch1.setOn(false, animated: true)
}
}
In switchChanged method you can change state of you array. so you can utilize that array later on.
All the best.
Update
Just updated downloadable code with cellForRowAtIndexPath and switchChanged.
I need to present a UIStepper in a row of a UITableView(only the second row - see the image below).
Therefore I implemented func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell like below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
switch(debugModeOptionType!) {
case .DummyCurrentLocation:
cell.textLabel!.text = "Dummy current location"
case .StepLength:
cell.textLabel!.text = "Step Length: \(stepLength)"
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
case .TrueHeading:
cell.textLabel?.text = "True Heading: \(trueHeading)"
case .MagneticHeading:
cell.textLabel?.text = "Magnetic Heading: \(magneticHeading)"
case .HeadingAccuracy:
cell.textLabel?.text = "Heading Accuracy: \(headingAccuracy)"
case .CurrentDirection:
cell.textLabel?.text = "Current Direction: \(currentDirection)"
case .DrawWalking:
cell.textLabel?.text = "Draw walking while navigating"
}
if selectedDebugModeOptions.contains(debugModeOptionType!) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
However when I touch the UIStepper on a real device(this does not happen inside the simulator) the following happens:
When this happens, the other cells' UISteppers start flashing as well. Why does such a problem occurs?
I can't say why this happening only on a real device, but because table view cells are reused, you have to be careful when adding elements to the cells programmatically, because those elements (such as your stepper) will be spread to other cells as the cells are reused.
There are (at least) two ways you can deal with this:
Check for the presence of a stepper after you dequeue a reusable cell and remove it if it is on a row that doesn't need a stepper. You could do this by giving the stepper a unique tag number (such as 123) and then search for subviews with that tag and remove them.
let stepperTagNumber = 123
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
if let stepper = cell.contentView.viewWithTag(stepperTagNumber) {
// We have a stepper but don't need it, so remove it.
if debugModeOptionType != .StepLength {
stepper.removeFromSuperview()
}
} else {
// We don't have a stepper, but need one.
if debugModeOptionType == .StepLength {
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
stepper.tag = stepperTagNumber // This is key, don't forget to set the tag
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
}
}
OR:
Create a second prototype cell for your tableview (called "OptionCellWithStepper"). Add the stepper to that cell in your storyboard. Then, when you call dequeueReusableCellWithIdentifier, use "OptionCellWithStepper" for case .StepLength and use identifier "OptionCell" for all the other cases. Doing it this way, you don't have to programmatically add the stepper, and you don't have to remember to remove it for the other cells.
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
let cellID = (debugModeOptionType == .StepLength) ? "OptionCellWithStepper" : "OptionCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellID)!