Access label in a cell in Swift - ios

I have made a custom cell in a table view. There are some buttons and labels in a cell. I'm making a delegate method and call it on the action of the button. The button is also in a cell. Now i'm trying that whenever user press button the label text should increment by one. I'm trying to access the cell label outside the cellForRow delegate method but fail. How can i get the label in a cell outside the cellForRow delegate method in my button action? I have tried some code,
this is in my cell class,
protocol cartDelegate {
func addTapped()
func minusTapped()
}
var delegate : cartDelegate?
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addTapped()
}
#IBAction func minusBtnTapped(_ sender: Any) {
delegate?.minusTapped()
}
This is in my view controller class,
extension CartViewController : cartDelegate{
func addTapped() {
total += 1
print(total)
}
func minusTapped() {
total -= 1
print(total)
}
}
this is cellForRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CartTableViewCell
cell.dishTitleLbl.text = nameArray[indexPath.row]
cell.priceLbl.text = priceArray[indexPath.row]
price = Int(cell.priceLbl.text!)!
print(price)
cell.dishDetailLbl.text = "MANGO,Apple,Orange"
print(cell.dishDetailLbl.text)
total = Int(cell.totalLbl.text!)!
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
cell.delegate = self
return cell
}
I want to access priceLbl in my addTapped and minusTapped functions.

Change your protocol to pass the cell:
protocol cartDelegate {
func addTappedInCell(_ cell: CartTableViewCell)
func minusTappedInCell(_ cell: CartTableViewCell)
}
Change your IBActions to pass the cell:
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addTappedInCell(self)
}
#IBAction func minusBtnTapped(_ sender: Any) {
delegate?.minusTappedInCell(self)
}
And then your delegate can do whatever it wants to the cell.

To be able to access the label inside of CartViewController but outside of cellForRowAt you have to be able to access a particular cell. To achieve that, since you are dynamically dequeueing reusable cells, you will need an indexPath of that cell and then you can ask the tableView to give you the cell:
// I will here assume it is a third cell in first section of the tableView
let indexPath = IndexPath(row: 2, section: 0)
// ask the tableView to give me that cell
let cell = tableView.cellForRow(at: indexPath) as! CartTableViewCell
// and finally access the `priceLbl`
cell.priceLbl.text = priceArray[indexPath.row]

It should be something as simple as:
self.priceLbl.text = "count = \(total)"

Related

How can I disable all textfield of my tableview cell with one switch which not member of tableview?

I have a custom tableViewCell with textField and only one switch which is not the part of tableViewCell.
So I want to do when switch on and off then my tableViewCell textFields disabled and enabled. So How can I do this??
#IBAction func switchServiceTax_Action(_ sender: UISwitch) {
let cell : tbleCell = tblViewServices.dequeueReusableCell(withIdentifier: "cell") as! tbleCell
if switch.isOn{
cell.txtFld.isUserInteractionEnabled = true
tblView.reloadData()
}else{
cell.txtFld.isUserInteractionEnabled = false
tblView.reloadData()
}
}
There are two ways to achieve the requirement.
In cellforRow, instead of if-else you can directly use this below line and make sure to reload the tableview everytime when switch is toggled.
cell.txtFld.isUserInteractionEnabled = switch.isOn
Just implement below UITextFieldDelgate, thats all. No need to reload tableview, so it increases app performance.
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return switch.isOn
}
You can use switch Object in cell for Row it. It is Simple logic write in below and follow instruction.
#IBAction weak var switchTextField: UISwitch!
#IBAction func switchServiceTax_Action(_ sender: UISwitch) {
tblView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TBLcell
cell.txtFirstName.resignFirstResponder()
cell.txtFirstName.isUserInteractionEnabled = switchTextField.isOn
return cell
}

tableview issue, indexPath is nil

I have a button and a label in a table view (I am using 8 rows )and for some reason when I click the first button I get indexPath nil error, but when I click the second button (2nd row) I get the first row label. When I click the 3rd row button, I get the second row label etc. Why are they misaligned. I want when I click the first row button to get the first row label etc. Please see the code below. Thank you !!
#objc func btnAction(_ sender: AnyObject) {
var position: CGPoint = sender.convert(.zero, to: self.table)
print (position)
let indexPath = self.table.indexPathForRow(at: position)
print (indexPath?.row)
let cell: UITableViewCell = table.cellForRow(at: indexPath!)! as
UITableViewCell
print (indexPath?.row)
print (currentAnimalArray[(indexPath?.row)!].name)
GlobalVariable.addedExercises.append(currentAnimalArray[(indexPath?.row)!].name)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? TableCell else {return UITableViewCell() }
// print(indexPath)
cell.nameLbl.text=currentAnimalArray[indexPath.row].name
// print("\(#function) --- section = \(indexPath.section), row = \(indexPath.row)")
// print (currentAnimalArray[indexPath.row].name)
cell.b.tag = indexPath.row
// print (indexPath.row)
cell.b.addTarget(self, action: #selector(SecondVC.btnAction(_:)), for: .touchUpInside)
return cell
}
Frame math is a worst-case scenario if you have no choice. Here you have a lot of choices.
For example why don't you use the tag you assigned to the button?
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}
A swiftier and more efficient solution is a callback closure:
In TableCell add the button action and a callback property. The outlet is not needed. Disconnect the outlet and connect the button to the action in Interface Builder. When the button is tapped the callback is called.
class TableCell: UITableViewCell {
// #IBOutlet var b : UIButton!
#IBOutlet var nameLbl : UILabel!
var callback : (()->())?
#IBAction func btnAction(_ sender: UIButton) {
callback?()
}
}
Remove the button action in the controller.
In cellForRow assign a closure to the callback property
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// no guard, the code must not crash. If it does you made a design mistake
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableCell
let animal = currentAnimalArray[indexPath.row]
cell.nameLbl.text = animal.name
cell.callback = {
GlobalVariable.addedExercises.append(animal.name)
}
return cell
}
You see the index path is actually not needed at all. The animal object is captured in the closure.
You already pass indexPath.row with button tag. Use the tag as index simply
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}

Access selected cell section in delegate function in swift

i'm getting data from API service which i'm passing to my tableview and creating section and cell under it. The number of section and cell are dynamic depends upon the data coming from the service. I have a button on my cell. Button name is add. When i click the add button it shows an alerts which contain a tableview. This table view in alert shows only that data which is related to that particular cell under its section. I have created a delegate method for the button in which i'm getting the indexPath.row of that selected button and pass data from my model to the table view inside the alert. When i click the first cell add button it shows everything fine but when i hit add button from section 2 the cashes. What i observed that app is crashing because compiler only gets indexPath.row but it doesn't get information about which section this cell is. How can i get to know my delegate function that which section cell is selection when add button is pressed. This is my code for the delegate function in my cell class,
protocol ResMenuDetailDelegate {
func addOnBtnTapped(tappedIndex : Int)
}
var delegate: ResMenuDetailDelegate?
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addOnBtnTapped(tappedIndex: addBtn.tag)
}
In my view controller class here i conform the delegate method,
extension RestaurantMenuDetailVC : ResMenuDetailDelegate{
func addOnBtnTapped(tappedIndex: Int) {
print(tappedIndex)
let addonCategory = subCategoryModel![tappedIndex].items[tappedIndex].addonCategory
print(addonCategory as Any)
}
This is my cellForRow table view delegate,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == resMenuTableView{
let cell = resMenuTableView.dequeueReusableCell(withIdentifier: "detailMenuCell", for: indexPath) as! RestaurantMenuDetailTVC
cell.dishTitleLbl.text = subCategoryModel![indexPath.section].items[indexPath.row].itemName
cell.descriptionLbl.text = subCategoryModel![indexPath.section].items[indexPath.row].itemDescription
cell.priceLbl.text = String(subCategoryModel![indexPath.section].items[indexPath.row].itemPrice)
cell.addBtn.tag = indexPath.row
cell.delegate = self
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
return cell
}
You need to send indexpath , the crash because you access the array model section with a row value that exceeds it
func addOnBtnTapped(tappedIndex : IndexPath)
//
extension RestaurantMenuDetailVC : ResMenuDetailDelegate{
func addOnBtnTapped(tappedIndex: IndexPath) {
print(tappedIndex)
let addonCategory = subCategoryModel![tappedIndex.section].items[tappedIndex.row].addonCategory
print(addonCategory as Any)
}
//
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addOnBtnTapped(tappedIndex:self.myIndexPath)
}
//
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == resMenuTableView{
let cell = resMenuTableView.dequeueReusableCell(withIdentifier: "detailMenuCell", for: indexPath) as! RestaurantMenuDetailTVC
cell.myIndexPath = indexPath
}
//
and declare that var in cell
var myIndexPath:IndexPath!
I think you have to pass two parameters in protocol.
protocol ResMenuDetailDelegate {
func addOnBtnTapped(tappedIndex : Int, button: UIButton)
}
Change the protocol like that so you could have both values for the
row and the section.
protocol ResMenuDetailDelegate {
func addOnBtnTapped(tappedIndexRow: Int,tappedIndexSection: Int )
}
var delegate: ResMenuDetailDelegate?
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addOnBtnTapped(tappedIndexRow: addBtn.tag,tappedIndexSection: section )
}
Here you can get the section value tableview's delegate method
cellForRowAt.
Add variable in your custom cell for section
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
...
cell.addBtn.tag = indexPath.row
cell.section = indexPath.section
...
...
}

detecting uibutton pressed in tableview: Swift Best Practices

I have a tableview with a variable number of cells representing students that correspond to their particular instructor. They are custom cells with a button that triggers a segue to a new VC, bringing up detailed information on the student whose cell it was. My question is:
What is the best practice in swift for identifying which button was pressed?
Once i know the index path, I can identify which student's information needs to be passed to the next VC. There is a great answer for objective C in the post below, but I'm not sure how to translate to Swift. Any help would be much appreciated.
Detecting which UIButton was pressed in a UITableView
If your code allows, I'd recommend you set the UIButton tag equal to the indexPath.row, so when its action is triggered, you can pull the tag and thus row out of the button data during the triggered method. For example, in cellForRowAtIndexPath you can set the tag:
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
then in buttonClicked:, you can fetch the tag and thus the row:
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Otherwise, if that isn't conducive to your code for some reason, the Swift translation of this Objective-C answer you linked to:
- (void)checkButtonTapped:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
if (indexPath != nil)
{
...
}
}
is:
func checkButtonTapped(sender:AnyObject) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
...
}
}
Swift 3.0 Solution
cell.btnRequest.tag = indexPath.row
cell.btnRequest.addTarget(self,action:#selector(buttonClicked(sender:)), for: .touchUpInside)
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Updated for Swift 3
If the only thing you want to do is trigger a segue on a touch, it would be against best practice to do so via a UIButton. You can simply use UIKit's built in handler for selecting a cell, i.e. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). You could implement it doing something like the following:
Create a custom UITableViewCell
class StudentCell: UITableViewCell {
// Declare properties you need for a student in a custom cell.
var student: SuperSpecialStudentObject!
// Other code here...
}
When you load your UITableView, pass the data into the cell from you data model:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StudentCell", for: indexPath) as! StudentCell
cell.student = superSpecialDataSource[indexPath.row]
return cell
}
Then use didSelectRow atIndexPath to detect when a cell has been selected, access the cell and it's data, and pass the value in as a parameter to performSegue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! StudentCell
if let dataToSend = cell.student {
performSegue(withIdentifier: "DestinationView", sender: dataToSend)
}
}
And finally in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DestinationView" {
let destination = segue.destination as! DestinationViewController
if let dataToSend = sender as? SuperSpecialStudentObject {
destination.student = dataToSend
}
}
}
Alternatively if you want them to only select a part of the cell instead of when they touch anywhere inside the cell, you could add an accessory item onto your cell such as the detail accessory item (looks like the circle with an "i" inside of it) and use override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) instead.
Another possible solution would be using dispatch_block_t. If you do it with Storyboard you first have to create a member variable in your custom UITableViewCell class.
var tapBlock: dispatch_block_t?
Then you have to create an IBAction and call the tapBlock.
#IBAction func didTouchButton(sender: AnyObject) {
if let tapBlock = self.tapBlock {
tapBlock()
}
}
In your view controller with the UITableView you can simply react to the button events like this
let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell
cell.tapBlock = {
println("Button tapped")
}
However you have to be aware when accessing self inside the block, to not create a retain cycle. Be sure to access it as [weak self].
Swift 3
# cellForRowAt indexPath
cell.Btn.addTarget(self, action: #selector(self.BtnAction(_:)), for: .touchUpInside)
Then
func BtnAction(_ sender: Any)
{
let btn = sender as? UIButton
}
It's never a good idea to use tags to identify cells and indexPaths, eventually you'll end up with a wrong indexPath and consequently the wrong cell and information.
I suggest you try the code bellow (Working with UICollectionView, didn't tested it with a TableView, but it probably will work just fine):
SWIFT 4
#objc func buttonClicked(_ sender: UIButton) {
if let tableView = tableViewNameObj {
let point = tableView.convert(sender.center, from: sender.superview!)
if let wantedIndexPath = tableView.indexPathForItem(at: point) {
let cell = tableView.cellForItem(at: wantedIndexPath) as! SpecificTableViewCell
}
}
}
Detecting the Section and row for UiTableView indexPath on click Button click
//MARK:- Buttom Action Method
#objc func checkUncheckList(_sender:UIButton)
{
if self.arrayRequestList != nil
{
let strSection = sender.title(for: .disabled)
let dict = self.arrayRequestList![Int(strSection!)!]["record"][sender.tag]
print("dict:\(dict)")
self.requestAcceptORReject(dict: dict, strAcceptorReject: "1")
}
}
Here is UITableView Cell Method to add the targate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherPropertySelectiingCell", for: indexPath as IndexPath) as! OtherPropertySelectiingCell
cell.btnAccept.tag = indexPath.row
cell.btnAccept.setTitle("\(indexPath.section)", for: .disabled)
cell.btnAccept.addTarget(self, action: #selector(checkUncheckList(_sender:)), for: .touchUpInside)
return cell
}
Swift 5. In cellForRowAtIndexPath you set the tag:
cell.shareButton.tag = indexPath.row
cell.shareButton.addTarget(self, action: #selector(shareBtnPressed(_:)), for: .touchUpInside)
Then in shareBtnPressed you fetch the tag
#IBAction func shareBtnPressed(_ sender: UIButton) {
let buttonRow = sender.tag
print("Video Shared in row \(buttonRow)")
}
As a follow up to #Lyndsey and #longbow's comments, I noticed that when I had the segue in storyboard going from the button to the destinationVC, the prepareForSegue was being called before the buttonClicked function could update the urlPath variable. To resolve this, I set the segue directly from the first VC to the destinationVC, and had the segue performed programmatically after the code in buttonClicked was executed. Maybe not ideal, but seems to be working.
func buttonClicked(sender:UIButton) {
let studentDic = tableData[sender.tag] as NSDictionary
let studentIDforTherapyInt = studentDic["ID"] as Int
studentIDforTherapy = String(studentIDforTherapyInt)
urlPath = "BaseURL..."+studentIDforTherapy
self.performSegueWithIdentifier("selectTherapySegue", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "selectTherapySegue") {
let svc = segue.destinationViewController as SelectTherapyViewController;
svc.urlPath = urlPath
}
Updated for Swift 5:
Place the following code within your ViewController class
#IBAction func buttonClicked(_ sender: UIButton) {
if let tableView = tableView {
let point = tableView.convert(sender.center, from: sender.superview!)
//can call wantedIndexPath.row here
}
}
}
I am doing it via prepareforSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath = self.tableView.indexPathForSelectedRow()
let item = tableViewCollection[indexPath!.row].id
let controller = segue.destinationViewController as? DetailVC
controller?.thisItem = item
}
and on the next controller i will just reload the full item properties, by knowing its id and setting it to the var thisItem in the DetailVC
I was going to use the indexPath approach until I came to understand that it would be unreliable/wrong in some situations (deleted or moved cell, for instance).
What I did is simpler. By example, I am displaying a series of colors and their RGB values—one per tableview cell. Each color is defined in an array of color structures. For clarity these are:
struct ColorStruct {
var colorname:String = ""
var red: Int = 0
var green: Int = 0
var blue: Int = 0
}
var colors:[ColorStruct] = [] // The color array
My prototype cell has a var to hold the actual index/key into my array:
class allListsCell: UITableViewCell {
#IBOutlet var cellColorView: UIView!
#IBOutlet var cellColorname: UILabel!
var colorIndex = Int() // ---> points directly back to colors[]
#IBAction func colorEditButton(_ sender: UIButton, forEvent event: UIEvent) {
print("colorEditButton: colors[] index:\(self.colorIndex), \(colors[self.colorIndex].colorname)")
}
}
This solution takes three lines of code, one in the prototype cell definition, the second in the logic that populates a new cell, and the the third in the IBAction function which is called when any cell's button is pressed.
Because I have effectively hidden the "key" (index) to the data in each cell AS I am populating that new cell, there is no calculation required -and- if you move cells there is no need to update anything.

Get indexPath of UITextField in UITableViewCell with Swift

So, I'm building a Detail View Controller App that presents a Table with a two-part cell: the label and the Text Field.
I'm trying to retrieve the Text Field value and add it to an array.
I tried to use the "textField.superview.superview" technique but it didn't worked.
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview
var table: UITableView = cell.superview.superview
let textFieldIndexPath = table.indexPathForCell(cell)
}
Xcode fails to build and presents that "UIView is not convertible to UITableViewCell" and "to UITableView".
The referring table has two sections, of four and two rows, respectively.
Thanks in advance.
EDIT:
added ".superview" at the second line of the function.
While the currently accepted answer might work, it assumes a specific view hierarchy, which is not a reliable approach since it is prone to change.
To get the indexPath from a UITextField that is inside a cell, it's much better to go with the following:
func textFieldDidEndEditing(textField: UITextField!){
let pointInTable = textField.convert(textField.bounds.origin, to: self.tableView)
let textFieldIndexPath = self.tableView.indexPathForRow(at: pointInTable)
...
}
This will continue to work independent of eventual changes to the view hierarchy.
You'll want to cast the first and second lines in your function, like this:
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview as UITableViewCell
var table: UITableView = cell.superview as UITableView
let textFieldIndexPath = table.indexPathForCell(cell)
}
superview returns a UIView, so you need to cast it to the type of view you expect.
Using superview and typecasting isn't a preferred aaproach. The best practice is to use delegate pattern. If you have a textField in DemoTableViewCell which you are using in DemoTableViewController make a protocol DemoTableViewCellDelegate and assign delegate of DemoTableViewCell to DemoTableViewController so that viewcontroller is notified when eiditing ends in textfield.
protocol DemoTableViewCellDelegate: class {
func didEndEditing(onCell cell: DemoTableViewCell)
}
class DemoTableViewCell: UITableViewCell {
#IBOutlet var textField: UITextField!
weak var delegate: DemoTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
}
extension DemoTableViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
delegate.didEndEditing(onCell: self)
}
}
class DemoTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoTableViewCell.self, for: indexPath)
cell.delegate = self
return cell
}
}
extension DemoTableViewController: DemoTableViewCellDelegate {
func didEndEditing(onCell cell: DemoTableViewCell) {
//Indexpath for the cell in which editing have ended.
//Now do whatever you want to do with the text and indexpath.
let indexPath = tableView.indexPath(for: cell)
let text = cell.textField.text
}
}
You can use tag property of UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UpdateTableViewCell", for: indexPath) as! UpdateTableViewCell
cell.tag = indexPath.row
cell.setCellData()
return cell
}
now in UITableViewCell
func textFieldDidEndEditing(textField: UITextField!){
let textFieldIndexPath = self.tag
}

Resources