Add target for button in table view cells - swift

I have a table view that its cells have a button in themselves and these buttons should open a view with an unique id. So I need to passing an argument to my buttons but with addTarget property I just can call function without any parameter.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.editButton.addTarget(self, action: #selector(goToEdit(id:)), for: .touchUpInside)
}
func goToEdit(id: String) {
let edit = EditAdViewController(editingAdId: id)
self.navigationController?.pushViewController(edit, animated: true)
}
Is there any way to refer an action with some parameters to a button? Thanks everyone :)

You can try adding delegate functions to your custom UITableViewCell.
For example, I have a button inside this custom tableViewCell:
PickupTableViewCell.swift
import UIKit
protocol PickupTableViewCellDelegate: NSObjectProtocol {
func pickupTableViewCell(userDidTapPickup pickup: Pickup, pickupTableViewCell: PickupTableViewCell)
}
class PickupTableViewCell: UITableViewCell {
// MARK: - Properties
#IBOutlet private weak var label_UserFullName: UILabel!
....
// MARK: - Functions
// MARK: IBAction
#IBAction func pickup(_ sender: Any) {
self.delegate?.pickupTableViewCell(userDidTapPickup: self.pickup, pickupTableViewCell: self)
}
}
Then in I conform my controller via the UITableViewDataSource (cellForRow) and of course implement the delegate function of my tableViewCell.
HomeViewController.swift
// MARK: - UITableViewDataSource
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let pickupTVC = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.pickupTableViewCell)!
pickupTVC.delegate = self
pickupTVC.pickup = self.pickups[indexPath.section]
return pickupTVC
}
}
// MARK: - PickupTableViewCellDelegate
extension HomeViewController: PickupTableViewCellDelegate {
func pickupTableViewCell(userDidTapPickup pickup: Pickup, pickupTableViewCell: PickupTableViewCell) {
// Do something
}
}

Maybe you can try to link your button to a #IBAction and use params[indexPath.row].
To get the indexPath:
var cell = sender.superview() as? UITableViewCell
var indexPath: IndexPath? = yourTableView.indexPath(for: cell!)

Related

How to UITableView Click image and title

I've created a tableView, but when I click it, I don't get any results. I wanted to add a new feature to improve my project, but I couldn't add the videos I watched and the things I researched.
Table View
import UIKit
class ViewController1: UIViewController {
#IBOutlet weak var FoodView: UITableView!
let dogfoods = ["pork", "banana", "chicken-leg"]
override func viewDidLoad() {
super.viewDidLoad()
FoodView.delegate = self
FoodView.dataSource = self
// not tapped no see
FoodView.allowsSelection = false
}
}
extension ViewController1: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dogfoods.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = FoodView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
let dogfood = dogfoods[indexPath.row]
cell.foodImageView.image = UIImage(named: dogfood)
cell.nameLabel.text = dogfood
return cell
}
}
CustomCell
class CustomCell: UITableViewCell {
#IBOutlet weak var dogView: UIView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var foodImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When I click on one of the cells in the picture, I want to write a larger version of the picture and a description, how can I do this? When I searched on the internet, I applied similar ones, but I couldn't get any results.
You have a few choices. You can add one or more buttons to your custom cell. You can attach a tap gesture recognizer to your cell's content view.
Probably the easiest way to respond to a tap on the whole cell is to have view controller conform to the UITableViewDelegate protocol and implement the tableView(_:didSelectRowAt:) method.
That method will be called when the user selects a cell, and you would use the indexPath of the tapped cell to figure out which one was tapped and do whatever is appropriate.
You can do that with easy and efficient way using Delegate in Swift
//create protocol as we used interface in Java
#objc protocol TableViewCellDelegate {
#objc func click(indexPath: IndexPath?)
}
// Modify your class CustomCell as:
#IBOutlet weak var dogView: UIView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var foodImageView: UIImageView!
var delegate: TableViewCellDelegate?
var indexPath: IndexPath?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
foodImageView.isUserInteractionEnabled = true
foodImageView.addGestureRecognizer(tapGestureRecognizer)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) {
let tappedImage = tapGestureRecognizer.view as! UIImageView
self.delegate.click?(indexPath: self.indexPath)
}
// Modify your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) in ViewController1 as
let cell = FoodView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
cell.delegate = self
cell.indexPath = indexPath
let dogfood = dogfoods[indexPath.row]
cell.foodImageView.image = UIImage(named: dogfood)
cell.nameLabel.text = dogfood
return cell
// And now add extension add the end of ViewController1
extension ViewController1: TableViewCellDelegate {
func click(indexPath: IndexPath?) {
// open image for preview here
}
}
You can do it in many ways
use a add a UITableViewDelegate method which is
override func tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath){
//your code...
}
it will called every time when cell clicked.
if you prefer to trigger any button click rather then cell click then go for delegate (Izaan Saleem already explained), or you can use NotificationCenter, but for this task I prefer didSelect or delegate solution.

Unable to access protocol function in TableViewcell class in swift

I want to access a function present in the protocol in tableviewcell class.But unable to do it. can anyone help me to fix this problem?
protocol updateLcodeCell: class {
func updateAfterSearchApiCall(searchTextArray: [String]?)
}
class PatientFormTableViewController: UIViewController{
var lcodeDelegate: updateLcodeCell?
func callApi(arr: [String]){
lcodeDelegate?.updateAfterSearchApiCall(searchTextArray: arr)}
}
In my cell class
class LcodeTableViewCell: UITableViewCell {
weak var lcodeDelegate: updateLcodeCell?
override func awakeFromNib() {
super.awakeFromNib()
self.lcodeDelegate = self
}
}
extension LcodeTableViewCell: updateLcodeCell{
func updateAfterSearchApiCall(searchTextArray: [String]?) {
searcApiResponseDataArray = searchTextArray
}
}
On your tableView indexPath.row method call your delegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: yourCellId, for: indexPath) as! YourCustomTableCell
cell.lcodeDelegate = self // here call your delegate
return cell
}

Send data from CustomTableView Class to its parent View Controller

I have a View Controller "VCInicio", that has a TableView in his View, the Cells for that TableView are in a .xib format, the cells has a custom class called "CustomiseTableViewCell", inside there I have logic that gets and print a String (Phone Number) every time I click on a RadioButton, it prints its Phone Number, I'm able to print the value (From CustomiseTableViewCell Class) and see the value on console, but I need to send that value back to "VCInicio" so I can manipulate it from that Controller. I've seen lots of examples that suggest to use Protocols but I haven't been able to make them work.
EDIT: Because of the structure I'm using, I can't work with didSelectRowAt, hence I'm working with the Selection of the Radio Button instead of the selection of the cell.
What Fixed the Issue:
"CustomiseTableViewCellDelegate" TableView Custom Class (Child Class)
//Protocol Implementation
protocol CustomiseTableViewCellDelegate {
func onPhoneNumberClicked(_ cell: CustomiseTableViewCell, phoneNumber: String?)
}
class CustomiseTableViewCell: UITableViewCell {
var delegate: CustomiseTableViewCellDelegate?
var phone: String?
override func awakeFromNib() {
super.awakeFromNib()
...
}
//Here I get and send the phoneNumber
#objc func radioButtonTapped(_ radioButton: UIButton) {
...
phone = itemLabel.text!
self.delegate?.onPhoneNumberClicked(self, phoneNumber: phone!)
...
}
}
"VCInicio" View Controller
class VCInicio: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomiseTableViewCellDelegate {
func onPhoneNumberClicked(_ cell: CustomiseTableViewCell, phoneNumber: String?) {
//Here I print the phoneNumber
print("From VCInicio", phoneNumber)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "phoneCell", for: indexPath) as! CustomiseTableViewCell
cell.delegate = self
...
//cell data config
...
return cell
}
}
If you don't want to use custom protocols:
Assuming you have the numbers stored in some model in variable phoneNumbers: [String]
Make your VC table view delegate:
self.tableView.delegate = self
Extend ViewController to conform UITableViewDelegate protocol
class VCInicio: UIViewController, UITableViewDelegate {
//...
}
Implement func tableView(UITableView, didSelectRowAt: IndexPath) and use selected cell phone value to manipulate it later
class VCInicio: UIViewController, UITableViewDelegate {
//...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedPhoneNumber = self.model.phoneNumbers[indexPath.row]
// manipulate selectedPhoneNumber
}
}
With protocols:
Define cell delegate and use it in the method for click
protocol CustomiseTableViewCellDelegate: class {
func onPhoneNumberClicked(_ cell: CustomiseTableViewCell, phoneNumber: String?)
}
class CustomiseTableViewCell: UITableViewCell {
weak var delegate: CustomiseTableViewCellDelegate?
// ... rest of your cell class
func onPhoneClicked() {
// this is the function where you have code to print the number to console
print(self.phoneNumberLabel.text)
self.delegate?.onPhoneNumberClicked(self, self.phoneNumberLabel.text)
}
}
In your UITableViewDataSource method for creating cells make VCInicio as cell's delegate
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// assuming you have your cell here
cell.delegate = self
return cell
}
Make your VC conform to CustomiseTableViewCellDelegate protocol
class VCInicio: UIViewController, CustomiseTableViewCellDelegate {
//...
override func onPhoneNumberClicked(_ cell: CustomiseTableViewCell, phoneNumber: String?) {
// manipulate phoneNumber
}
}
This code below is right now for an ImageView with a gesture recognizer. Which is targeted to the handleAvatarTap. Which is posting a global notification. Any ViewController or View can listen to notifications, and once the chosen one is posted the controller can act on this.
In the example below is an example, passing the ImageView as an object for the notification.
// this is how I am creating a new notification name type
extension Notification.Name {
static let didTapAvatar = Notification.Name("didTapAvatar")
}
// this would go within the cell where you are printing the number
#objc func handleAvatarTap() {
NotificationCenter.default.post(name: .didTapAvatar, object: self)
}
// This would go in to the view controller viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleAvatarTap), name: .didTapAvatar, object: nil)
// This is the objc func within the view controller
#objc fileprivate func handleAvatarTap(notification: NSNotification) {
// checking the type of the posted notification object
guard let avatarView = notification.object as? AvatarView else { return }
}

How can I create a public function to show the indexPath of a selected cell in a UICollectionView

In my application I need a public function / public var in which I need to know the indexPath of a selected cell. Since Im a new coder I have some ideas how to make it but none of them has worked yet.
I just want, that I can access the selected indexPath from everywhere in the class. So I need some advice/help.
func selectedColor(){
let cell = gamePad.dequeueReusableCell(withReuseIdentifier: "coloredCell", for: IndexPath) as! UICollectionViewCell
let selectedCell = gamePad.indexPathForItem(at: CGPoint)
}
I agree with Dávid Pásztor, but help you for first time. You need implement delegate of UITableView. I think it's will be look like this:
class TableViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var selectedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
}
}
extension TableViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedIndexPath = indexPath
}
}
P.S. For UICollectionView it is same, but you need implement UICollectionViewDelegate and look like this:
class ViewController: UIViewController {
#IBOutlet var collectionView: UICollectionView!
var selectedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
}
}
P.S. If you don't need to know a moment when cell was tapped, then you can use indexPathForSelectedRow (#rmaddy)
indexPathsForSelectedItems on UICollectionView
https://developer.apple.com/documentation/uikit/uicollectionview/1618099-indexpathsforselecteditems

How do I call a different function for each TextField in a UITableView (Swift)?

I have a UITableView and my prototype cell consists of a label and a TextField. I also have a class MyClass that contains functions func1, func2, fun3, ... I have several ViewControllers that use the same tableViewCell prototype. Each viewController will have an instance of MyClass, called inst1, inst2, and inst3. When I enter text into FirstViewController's TableView I want each row to call a function from the instance of MyClass that corresponds to the row.
So when I enter text into row 1 on the FirstViewController I want to pass the data entered into the textField into func1 of inst1. When data is entered into row 2 of FirstViewController I want the data in the textfield to be passed into func2 of inst1. And so on and so forth down the rows.
I am very new to this and would really appreciate some help figuring out how to do this. Let me know if that doesn't make sense and I can try to rephrase it. I really need help with this. Thanks in advance!
*Updated question to show my code
Below is my Code:
FirstViewController.swift
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPath(for: cell) {
if (indexPath.hashValue == 0) {
inst1.func1(one: text)
}
if (indexPath.hashValue == 1) {
inst1.func2(two: text)
}
}
totalText.text = inst1.getMyTotal()
}
}
import UIKit
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let inst1 = MyClass()
#IBOutlet weak var totalText: UILabel!
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 11
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell") as! TableViewCell
let text = cell.cellData[indexPath.row]
cell.myTextField.tag = indexPath.row
cell.delegate = self
cell.myLabel.text = text
cell.myTextField.placeholder = text
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
TableViewCell.swift
import UIKit
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class TableViewCell: UITableViewCell {
weak var delegate: MyCellDelegate?
public var cellData: [String] = ["1","2","3","4","5","6","7","8","9","10","11"]
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
When I set a breakpoint in the FirstViewController extension it never runs that code.
In WillDisplayCell add the tag to the UITextField. Also create a protocol to notify the Corrosponding viewController and set itself as the delegate here.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
cell.textField.tag = indexPath.row
cell.delegate = self
}
The protocol in your cell class will look something like this
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class MyCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: MyCellDelegate?
override fun awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
//All the remaining code goes here
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
delegate?.MyCell(self, didEnterText: textField.text! )
return true
}
}
Now again in your FirstViewController which has conformed to be its delegate do this
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPathForCell(cell) {
// call whichever method you want to call based on index path
}
}