could anyone tell me how to get the index of a cell inside its class which is uitableViewCell more specifically , inside an action function of UISwitch .
I did the following..
var cell = sender.superview?.superview as UITableViewCell
var table: UITableView = cell.superview as UITableView
let indexPath = table.indexPathForCell(cell)
but then it crashes.
what is the solution ?
Try this:
Assuming you have a UISwitch *cellSwitch object in cell custom class
In cellForRowAtIndexPath:
cell.cellSwitch.tag = indexPath.row
In IBAction for this switch:
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
You don't want to know the index path of the cell inside of the cell. The index path is an implementation detail of the UITableViewController. The cell should be an independent object.
What you really want to do is to assign an action to run when your switch is changed.
class MySwitchCell: UITableViewCell {
#IBOutlet weak var switchCellLabel: UILabel!
#IBOutlet weak var mySwitch: UISwitch!
//Declare an action to be run
var action: ((sender: UISwitch) -> Void)?
//then run it
#IBAction func switchAction(sender: UISwitch) {
action?(sender: sender)
}
}
Then give the action something to do when you configure the cell.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SwitchCell", forIndexPath: indexPath) as! MySwitchCell
cell.switchCellLabel.text = items[indexPath.row]
cell.mySwitch.on = NSUserDefaults.standardUserDefaults().boolForKey(items[indexPath.row])
cell.action = { [weak self] sender in
if let tableViewController = self {
NSUserDefaults.standardUserDefaults().setBool(sender.on, forKey: tableViewController.items[indexPath.row]) }
}
return cell
}
For example this one sets a bool in the NSUserDefaults based on the state of that switch.
You can checkout the whole sample project from https://github.com/regnerjr/SimpleCellSwitchAction
Related
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 3 years ago.
There are many other topics of passing data to another ViewController - i know - but i could not find the solution for passing data from UITableViewCell to UIViewController. The question differs from others that here i have to access an ImageView in another class of UITableViewCell. Segues, prepare for segues an other topics are discussed in other posts sufficiently but not this special constellation.
I have a class UITableViewCell:
class PostCell: UITableViewCell {
...
// networkService is downloading an image
networkService.downloadImage({ (imageData) in
let image = UIImage(data: imageData as Data)
...
// image is set to UIImageView
self.postImageView.image = image
In my ViewController i do this to go to the DetailViewController:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
aktIndex = indexPath.section
performSegue(withIdentifier: "segueDetail", sender: self)
}
I tried this:
let MainStory:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let desVC = MainStory.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
desVC.getImage = ???
self.navigationController?.pushViewController(desVC, animated: true)
DetailViewController:
class DetailViewController: UIViewController {
var getImage = UIImage()
...
I have made a segue in xcode (segueDetail):
At the moment i store the imagedata in UserDefaults and read them again in the DetailViewController. Very weird, i know.
Where do i have to pass the data from? In my PostCell or in the ViewController? The problem is to get access to image-data from PostCell in my ViewController.
In tableView(_:didSelectRowAt:) when you call performSegue(withIdentifier: sender:), you can pass any data or references in it. This is now available in prepare(forSegue:sender:) and your last shot at preparing the data to be passed to the segued viewController.
Example (Using Segue):
If segueDetail is properly hooked up via storyboard and your user taps on a row, you could send the indexPath to the segue like:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "segueDetail", sender: indexPath)true)
}
Then in prepare(forSegue:sender:), depending on your solution, you can prepare access to the required data that you need to pass to the next viewController like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? DetailViewController {
let indexPath = sender as! IndexPath
let cell = tableView.cellForRow(at: indexPath) as! PostCell
vc.getImage = cell.postImageView.image
}
}
Example (Manually without segue):
If you are not using a segue and the user taps on a row, you could manually push a viewController with data like:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
let cell = tableView.cellForRow(at: indexPath) as! PostCell
vc.getImage = cell.postImageView.image
self.navigationController?.pushViewController(desVC, animated: true)
}
And your DetailViewController should be:
class DetailViewController: UIViewController {
var getImage: UIImage?
//...
NOTE: This answer is the best I could fit to work with the given content.
It should just about work but please don't just copy-paste as it's not optimized (for example, your user taps on a cell before it's image is downloaded).
This was just to show the basics, so please improvise and apply proper case handling.
I want to attach information (a string) to each UITableViewCell when populating TableView with UITableViewCells. I don't want it to be visible- I just need to access this string when the row/cell is selected. What is the best way to do this?
Your best choice is probably to subclass UITableViewCell and add a string property. This can then be assigned either as a constant, or when setting up your cells in cellForRowAtIndexPath:.
class CustomCell: UITableViewCell {
var customString = "Default"
}
--------
func cellForRow(atIndexPath indexPath: IndexPath) -> UITableViewCell {
guard let cell: CustomCell = tableView.dequeueReusableCell(withReuseIdentifier: "identifier", indexPath: indexPath) as? CustomCell else {
return UITableViewCell()
}
cell.customString = "String"
return cell
}
func didSelectRow(atIndexPath indexPath: IndexPath) {
guard let cell: CustomCell = tableView.cellForRow(atIndexPath: indexPath) as? CustomCell else {
return
}
let stringFromCell = cell.customString
}
The above has not been compiled, so may not work word for word, but you get the principle.
Add a property selected to your model.
In cellForRow show/hide the string depending on the state of selected.
In didSelectRow toggle selected in the model and reload the row.
I am using a custom table view cell. This cell has a label with outlet called chatNameLabel. Adding a UILongPressGestureRecognizer to this label never fires the associated event.
I'm guessing that the problem is that the UILabel is in a TableView and that the table/cell view is intercepting the taps. Can I do something about this?
All I want to do is perform some custom action when a UILabel is long pressed!
I believe this was answered in Objective-C however I am not familiar with the language at all and new to swift.
Here's the code I'm using:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let sessions = self.chatSessions where sessions.indices.contains(indexPath.row) {
let session = sessions[indexPath.row]
if session.sessionId == nil {
//DO A THING
}
// existing session id (existing chat)
else {
let cell = tableView.dequeueReusableCellWithIdentifier("ChatListCell", forIndexPath: indexPath) as! ChatListTableViewCell
cell.tag = indexPath.row
if(session.unreadChats) {
cell.indicatorImageView.tintColor = AppStyles.sharedInstance.indicatorActive
}
else{
cell.indicatorImageView.hidden = true
}
//Want to do gesture on this label below cell.chatNameLabel
cell.chatNameLabel.text = session.chatName
... Some more code not needed for question below this
Your class needs implement UIGestureRecognizerDelegate , then the below code should work.
myLabel.userInteractionEnabled = true
let tap: UILongPressGestureRecognizer = UILongPressGestureRecognizer(
target: self, action: #selector(tappedTheLabel))
tap.minimumPressDuration = 0.5
tap.delaysTouchesBegan = true
myLabel.addGestureRecognizer(tap)
tap.delegate = self
}
func tappedTheLabel(sender: UITapGestureRecognizer)
{
print("label hit \(sender)")
}
Stepper problem when cells need scroll.
My_TableViewCell.swift
import UIKit
class My_TableViewCell: UITableViewCell {
#IBOutlet weak var My_Label1: UILabel!
#IBOutlet weak var My_Label2: UILabel!
#IBOutlet weak var My_Stepper: UIStepper!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
...
My_TableViewController.swift
import UIKit
var My_Ids = ["C01","C02","C03","C04","C05","C06","C07","C08","C09","C10"]
var My_Values = ["0","0","0","0","0","0","0","0","0","0"]
class My_TableViewController: UITableViewController {
#IBAction func My_Stepper(sender: AnyObject) {
let point = sender.convertPoint(CGPointZero, toView: tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)!
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! My_TableViewCell
cell.My_Label2.text = "\(Int(cell.My_Stepper.value))"
}
...
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return My_Ids.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell =
self.tableView.dequeueReusableCellWithIdentifier(
"My_TableCell", forIndexPath: indexPath)
as! My_TableViewCell
let row = indexPath.row
cell.My_Label1.text = My_Ids[row]
cell.My_Label2.text = "\(Int(cell.My_Stepper.value))"
return cell
}
...
When all cells fit in a "page" the Stepper works well but if the cells are resized and you need scroll the tableview the values are repeated in other cells.
If you alter the value on the cells C06 and C07 this will to reflect C01 and C02 cells and vice-versa.
Please view this image showing the snapshots with the errors
UITableViewController intentionally reuses cells as an optimization. You need to clear the old cell settings as part of your solution. Add a prepareForReuse method to your My_TableViewCell class.
You do this:
- (void) prepareForReuse
{
[super prepareForReuse];
// clear previous cell settings
}
Additional Notes:
recommend against calling cellForRowAtIndexPath directly. See this thread.
recommend you use Pascal casing for class names: Use MyTableViewCell instead of My_TableViewCell.
What's the difference between this:
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
And this:
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
They both seem t work fine for me.
PS: I know this seems to be an amateur question but i'm beginner in Xcode, so no reason to be a smug.
When you write :
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
You are initializing a new cell using its constructor.
And when you write :
var cell = self.tableView.dequeueReusableCellWithIdentifier("cell")
You are dequeuing a cell, so you are assuming your cell with the identifier cell has been already registered in the tableView.
Typically, if the cell was designed in Interface Builder and set as a prototype cell or if you have registered your cell for reuse using the method self.tableView.registerClass(MyCell.classForCoder(), forCellReuseIdentifier: "cell") you won't need to use the constructor because it is already initialized in the tableView.
But if your cell is designed programmatically such as creating UILabel, UIImage or whatever components, you will have to use the constructor instead, and then use the dequeue method.
So, if you have to use the constructor (because you're initializing everything by code) your code will look like this :
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
cell.cellLabel.text = "Hello world"
cell.cellImage.image = UIImage(named: "funny_cat.jpg")
return cell
}
But if your cell was registered for reuse or if it is a prototype cell you will just have to use
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.cellLabel.text = "Hello world"
cell.cellImage.image = UIImage(named: "funny_cat.jpg")
return cell
}
I think the best place to look how tableview work, you should look the official documentation here : Table View Programming Guide for iOS