Making button clickable UITableView Xcode - ios

I made a custom tableview cell with a nib file in my project with a button. How do I make it so that when the button is clicked, I can execute a simple task, like printing hi? I saw other posts that are similar, but don't completely understand them.
Thanks in Advance....

You should add target to your button like this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "ShipmentDeliveryFactorTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! ShipmentDeliveryFactorTableViewCell
let object = factorArray[indexPath.row]
cell.detailButton1.addTarget(self, action: #selector(showDetail(_:)), for: .touchUpInside)
cell.detailButton1.tag = indexPath.row
return cell
}
#objc func showDetail(_ sender: UIButton) {
let selectedCell = factorArray[sender.tag]
print("selectedCell.id")
}

Related

How to configure a button containing an image in TableViewCell.swift file, and implement showing different images in each table view cell?

I am trying to show a button as an image of restaurant food and a button below that as a text label containing the name of that restaurant all in a custom table view cell. So when you scroll down the table view, you see pictures of different restaurant food and the name of the restaurant below the picture. I have a XIB file I’m using for this.
At the moment, I’m using a UIImage for the pictures of the restaurants, and this is working, but I’m trying to instead use a button with an image, so that I can make an #IBAction after the picture of the food that is a button is clicked.
I have configured a button showing restaurant names in the TableViewCell.swift file as shown below (the labels show when ran):
TableViewCell.swift code:
#IBOutlet var labelButton: UIButton!
//For retaining title in a property for IBAction use.
private var title: String = ""
func configureLabelButton(with title: String) {
self.title = title //For retaining title in a property for IBAction use.
labelButton.setTitle(title, for: .normal)
}
and implemented showing different restaurant names in each table view cell from an array with the restaurant names as strings in the view controller as shown below.
ViewController.swift code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureLabelButton(with: myData[indexPath.row])
return cell
}
I am trying to do the same thing for showing an image in a button. I have an array of images I'm using for this same purpose called myImages. Here is the code I have so far, but it is not working when run.
TableViewCell.swift code:
#IBOutlet var imageButton: UIButton!
//For retaining image in a property for IBAction use.
private var image: UIImage = UIImage()
func configureImageButton(with image: UIImage) {
self.image = image //For retaining image in a property for IBAction use.
imageButton.setImage(image, for: .normal)
}
ViewController.swift code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureImageButton(with: myImages[indexPath.row]!)
cell.imageButton.contentMode = .scaleAspectFill
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 185
}
I think my error is somewhere in TableViewCell.swift. I think I do not have the correct code there and potentially in ViewController.swift.
I think there is a problem in the UIImage array declaration, the issue might be it not getting an image from the array.
try with this one
var arrImg : [UIImage] = [UIImage(named: "res-1")!,UIImage(named: "res-2")!,UIImage(named: "res-3")!]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureImageButton(with: arrImg[indexPath.row])
cell.btnResturant.contentMode = .scaleAspectFill
cell.configureLabel(with: arrRes[indexPath.row])
cell.layoutIfNeeded()
cell.selectionStyle = .none
cell.layoutIfNeeded()
return cell
}
Found the answer to my question. The syntax in TableView.swift was wrong. This may have been due to a Swift update.
Was:
imageButton.setImage(image, for: .normal)
Should be:
imageButton.setBackgroundImage(image, for: .normal)

UITapGestureRecognizer won't work for custom UIView class

I have a custom UIView class which creates a checkbox. This UIView is in a custom table view cell.
I have this code in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TransactionsTableViewCell
cell.isUserInteractionEnabled = true
cell.addSubview(cell.test)
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.recurringChange(_:)))
cell.test.isUserInteractionEnabled = true
cell.test.addGestureRecognizer(gesture)
}
I have have this function in the ViewController class
#objc func recurringChange(_ sender:
UITapGestureRecognizer) {
print("test")
}
When test view is tapped, it does not print test. I tried this with a normal UIView (not custom), and it worked exactly as expected.
If this helps, here is a link to the custom class: https://github.com/vladislav-k/VKCheckbox
I have tested the code in Xcode 10 and there is no problem.
You may check " cell.isUserInteractionEnabled = true"
and "cell.addSubview(test)" is added to cell before you add TapGesture.

Tableview button.tag throw lldb

i don't know what happen, i set the button.tag with the table row and when it reach row > 1, it will throw lldb. it works if the button.tag <= 1
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cells")! as UITableViewCell
let alertBtn = cell.viewWithTag(1) as! UIButton;
alertBtn.tag = indexPath.row
alertBtn.addTarget(self, action: Selector(("showAlert:")), for: UIControlEvents.touchUpInside)
return cell
}
Application crash on this line, because it fails to find a view with tag 1, the tag is updating in every cell with row value.
let alertBtn = cell.viewWithTag(1) as! UIButton
remove this line and Take #IBOutlet for alertBtn From UITableViewCell instead of refreshing with tag.
Swift 3X...
You are replacing your tag so first tag items are getting nil so replace this code ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cells")! as UITableViewCell
let alertBtn = cell.viewWithTag(1) as! UIButton
alertBtn.addTarget(self, action: #selcetor(showAlert(sender:))), for: .touchUpInside)
return cell
}
func showAlert(sender:UIButton) {
let point = sender.convert(CGPoint.zero, to: self.tableview)
let indexpath = self.tableview.indexPathForRow(at: point)
}
Try to do custom UITableViewCell.
Declare protocol and delegate for Your new class class. Wire up a action and call delegate
protocol MyCellDelegate: class {
func buttonPressed(for cell: MyCell)
}
class MyCell:UITableViewCell {
weak var delegate: MyCellDelegate?
#IBAction func buttonPressed(sender: Any){
self.delegate?.buttonPressed(for: self)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
.......
cell.delegate = self
........
}
Remember to add new protocol implementation to Your VC. You can add prepareForReuse method and reset delegate to nil when cell is reused.
If you want to get indexPath of cell containing tapped button you can use function similar to this matching your requirement.
func showAlert(sender: AnyObject) {
if let cell = sender.superview?.superview as? UITableViewCell{ // do check your viewchierarchy in your case
let indexPath = itemTable.indexPath(for: cell)
}
print(indexPath)// you can use this indexpath to get index of tapped button
}
Remove this line from cellForRowAtIndexPath alertBtn.tag = indexPath.row
If you can use Custom Cell for this purpose you can get indexpath of selected button as you were getting previously.
Create CustomCell and create IBOutlet for your button and labels etc. You can access subviews of your cell in cellForRowAtIndexPath and assign tag to your button. If you have any queries regarding CustomCell do let me know.

Swift 3 button.addTarget not working on all table cells

I have created an app to allow users to store various voice recordings against reviews. When I display this a table and the data is populated with the following code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = (indexPath as NSIndexPath).item
let cell = tableView.dequeueReusableCell(withIdentifier: "voiceRecordingCell", for: indexPath) as! VoiceRecordingTableViewCell
let voiceRecording = self.voiceRecordings[row] as! NSDictionary
let isoFormatter = DateFormatter()
isoFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'.000Z'"
let createdAt = isoFormatter.date(from: voiceRecording["created_at"] as! String)
self.recordingIndexPaths.insert(indexPath, at: row)
cell.recording = voiceRecording
cell.date.text = getDateFormatter("dd-MM-y").string(from: createdAt!)
cell.time.text = getDateFormatter("HH:mm").string(from: createdAt!)
cell.length.text = voiceRecording["length"] as? String
cell.location.text = voiceRecording["location"] as? String
let audioPlayerController = self.storyboard?.instantiateViewController(withIdentifier: "AudioPlayerController") as! AudioPlayerController
audioPlayerController.voiceRecording = voiceRecording
cell.audioPlayer.addSubview(audioPlayerController.view)
self.addChildViewController(audioPlayerController)
audioPlayerController.didMove(toParentViewController: self)
cell.deleteRecordingButton.tag = row
cell.deleteRecordingButton.addTarget(self, action: #selector(deleteRecordingPressed(_:)), for: .touchUpInside)
return cell
}
The cells all appear to be rendering correctly however for the cells that are not initially rendered with the page, the ones I have to scroll down to view, when I click on the buttons either on the audio player controls or the deleteRecordingButton nothing happens, its as though the addTarget is not being set. The code to set the buttons is being called and doesn't create an error, its just not applying to those button.
The buttons that are initially displayed on the screen have the correct actions and all work perfectly so I know that works.
I'm really at a loss as to what is causing this. I did try searching google and stackoverflow but I've not found anything. Any assistance with this would be greatly received.
--------------- UPDATE -----------
I just tried putting some breakpoints in
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
That also only works get called on the top 2 cells or the top one if in landscape!
Since the cell gets reused all the time the reference get lost.
Try something like this:
class ButtonTableViewCell: UITableViewCell {
typealias TapClosure = () -> Void
var buttonTapped: TapClosure?
#IBAction func buttonTouchUpInside(sender: UIButton) {
buttonTapped?()
}
}
extension TestViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! ButtonTableViewCell
cell.buttonTapped = {
print("Button tapped")
}
return cell
}
}
And another tipp. Never init an DateFormatter in cellForRowAtIndexPath. Instead create it in viewDidLoad or in a static struct for reuse.
Finally I have figured this out...
This was happening because I was using a scrollview with to scroll up and down the table without using the tables native scroll functionality.
When using the tables scroll functionality the buttons are applied the actions as they are brought into the view. This is handled by the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { method. However when you do it the way I was it initially renders all the cells and the ones that are off the screen don't work!
Its a little bit annoying working in this way but at least I now know about it.

Value of type '(UIButton) -> ()' has no member 'setTitle' in tableView

I have been creating an idle clicking game/application. The way you purchase upgrades is by clicking a button within a table cell. What I can't figure out, is how do I set a title for a button (I want to have price listed on each button). When I try I get the error, "Value of type '(UIButton) -> ()' has no member 'setTitle'" I'm using Xcode 8.2, and Swift 3.
Custom Cell Controller:
#IBAction func upgradePurchase(_ sender: UIButton) {
if GlobalVariables.sharedManager.balance >= GlobalVariables.sharedManager.UpgradesPrice[GlobalVariables.sharedManager.UpgradesCurrent] {
}
}
TableView Controller:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let row = indexPath.row
let showPrice = "Buy: $\(GlobalVariables.sharedManager.UpgradesPrice[row])"
cell.upgradeIdentifier.text = GlobalVariables.sharedManager.UpgradesName[row]
cell.upgradeDescription.text = GlobalVariables.sharedManager.UpgradesDesc[row]
cell.upgradePurchase.setTitle("\(showPrice)", for: .Normal)
GlobalVariables.sharedManager.UpgradesCurrent = GlobalVariables.sharedManager.UpgradesPrice[row]
return cell
}
By mistake or accidentally You are trying to set the title for button Action not to the UIButton instance. Here upgradePurchase is IBAction not the IBOutlet.
You need to set the title to your IBOutlet, so make one if it is not then set the title.
cell.btnupgradePurchase.setTitle("\(showPrice)", for: .normal)
Note: In Swift 3 it is .normal not .Normal.

Resources