Swift - Making a Button for UITableViewCell with .addTarget - swift

I'm trying to make two different buttons for each cell that I create in my table view. One of the buttons is a + button that will increment a label. In my testing however I cannot get the function to work. My current error says
Argument of #selector does not refer to an '#objc' method, property, or initializer
I feel like I'm implementing the .addTarget method completely wrong but I am new. Here is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! AddItemCell
cell.setCell(item: item)
let itemAmount = cell.itemAmount as UILabel?
cell.addButton.addTarget(self, action: #selector(addItem(sender:cell.addButton,forLabel:itemAmount!)), for: .touchUpInside)
}
#objc func addItem(sender: UIButton, forLabel label:UILabel) {
print("Add Button Clicked")
}

You are using selector syntax incorrectly:
action: #selector(addItem(sender:cell.addButton,forLabel:itemAmount!))
Just say:
action: #selector(addItem)
Then, however, you will face a new problem. You think that somehow you can cause this button to call something called addItem(sender:forLabel:). You can't. Change the declaration of addItem to addItem(_ sender:UIButton). That is the only kind of function a button tap can call.
You will thus have the sender (the button), but you must figure out from there what the label is. (And this should be easy, because, knowing the button, you know the cell, and knowing the cell, you know the label.) You cannot pass the label as a parameter in response to the button tap — but you don't need to.

You need to create callback function in you cell
class AddItemCell: UITableViewCell {
var buttonClickCallback:(() -> Void)?
#IBAction func onButtonClick(_ sender:Any) {
buttonClickCallback?()
}
}
and assign buttonClickCallback in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! AddItemCell
cell.setCell(item: item)
let itemAmount = cell.itemAmount as UILabel?
cell.buttonClickCallback = {
self.addItem(sender:cell.addButton,forLabel:itemAmount!)
}
}

Related

UIContextMenu not appearing when UITableViewCell also contains a UIButton

I am creating a simple to do list app and the UITableViewCell has a checkmark button in it along with a title. When clicking the checkmark button, the done status changes and the cell UI updates.
I also would like to utilise the long press gesture within the UITableViewCell to have a ContextMenu appear. Everything seems to be working, however when a user long presses over the checkmark button, the ContextMenu doesn't appear.
I assume there are competing gestures (ie the UIButton and the cell's Long Press), but I'm having trouble figuring out how to support both at the same time. I've created the test code below to simplify the issue. In storyboard the cell has a single button on it. When long pressed, the context menu does not appear (as I need it to).
I know there's a solution somewhere!
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "cellTest", for: indexPath)
}
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let actions = [UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive, handler: { action in
return
})]
return UIContextMenuConfiguration(actionProvider: { _ in
return UIMenu(options: .displayInline, children: actions)
})
}
}

UITextField can't become first responder in UITableView

Context
Basic list. When user press '+', code creates a new item with default text that user can change.
Problem
I want to focus the new item as soon as user press '+' so that user can type desired name. I try to achieve this with this code:
func focus() {
title.becomeFirstResponder()
title.selectAll(nil)
}
But becomeFirstResponder() always returns false.
How can I give focus to UITextField in my UITableviewCell after creation ?
Here is full code of UITableViewController for reference:
import UIKit
class ViewController: UITableViewController{
var model: [String] = ["Existing item"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemUI", for: indexPath) as! ItemUI
cell.update(with: model[indexPath.item])
return cell
}
#IBAction func createItem(_ sender: UIBarButtonItem) {
let indexPath = IndexPath(item: model.count, section: 0)
model.append("new item")
tableView.reloadData()
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
let cell = self.tableView(self.tableView, cellForRowAt: indexPath) as! ItemUI
cell.focus()
}
}
}
class ItemUI: UITableViewCell{
#IBOutlet var title: UITextField!
func update(with: String) {
title.text = with
}
func focus() {
title.becomeFirstResponder()
title.selectAll(nil)
}
}
Ok I found the problem!
I was using method self.tableView(self.tableView, cellForRowAt: indexPath) instead of self.tableView.cellForRow(at: indexPath).
Here is what the documentation has to say:
"Never call this method yourself. If you want to retrieve cells from your table, call the table view's cellForRow(at:) method instead."
You can add a boolean in your view controller to keep track of adding item i.e isAddingItem with default value false and when you add new item simply update isAddingItem to true. In tableview cellForRowAt method check for last cell of tableview and if isAddingItem is true then selected all text of textfield and make it first responder.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemUI", for: indexPath) as! ItemUI
cell.update(with: model[indexPath.item])
if isAddingItem && indexPath.item == model.count {
// make your textfield first here and select text here
}
return cell
}
Also check if you have set textfield delegate.

Swift Newbie Argument labels '(_:, IndexPath:)' do not match any available overloads

When I call the tableView function I get the error in the title. My question is why won't the function take the two arguments even though they are of the requested type? Again I'm a newbie so please forgive me if the answer is obvious.
class TableViewController: UITableViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! UITableViewCell
let name = Array(shopItems.keys) [indexPath.row]
let cost = Array(shopItems.values) [indexPath.row]
cell.textLabel?.text = name + String(cost)
return cell
}
}
When I call the function like this:
"TableViewController.tableView(shopTableView, IndexPath: NSIndexPath)" I get the error: "Argument labels '(_:, IndexPath:)' do not match any available overloads"
Try use
let name = shopItems.keys[indexPath.row]
Instead of
let name = Array(shopItems.keys) [indexPath.row]
Better don't use force wrap, when it's not nesesery.
Try change
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! UITableViewCell
to
guard let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as? UITableViewCell else {
return UITableViewCell()
}
EDIT: As #Sh_Khan say replace
func tableView(_ tableView: UITableView, cellForRowAt indexPath: NSIndexPath) -> UITableViewCell {
to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
There is an easy and quick way to figure out yourself the proper overload
Select the entire method and press ⌘/ to comment out the code.
On the top level of the class type cellForRow. The first item in the code completion list is the proper method.
Press Return to insert the correct method.
Comment in the wrong method by selecting it again and pressing ⌘/.
Copy and paste the body of the wrong method into the correct one.
Delete the rest of the wrong method.

Swift tableview cell doesn't save the state with different identifier

I am very confused on the reuse of the cells.
I have a table, each cell is a cell with a switch on it. If I toggle a switch I set the background color of that cell to a different color. However every time I scroll these changes don't persist.
I am subclassing UITalbeViewCell to create my own custom cell. Each cell has a different identifier. However when I scroll through the table, whatever changes I made to the cell still doesn't save. I've read similar questions but none of them worked.. Some suggested subclass which I did, some suggested use different identifier which I also did...
Here is the code of my tableview.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let key = Array(dataSource[indexPath.section].keys)[indexPath.row]
let cell = CellWithSwitch.init(style: .subtitle, reuseIdentifier: key)
cell.awakeFromNib()
let val = Array(dataSource[indexPath.section].values)[indexPath.row]
cell.switchView?.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
if let index = key.firstIndex(of: "."){
cell.textLabel?.text = String(key.suffix(from: key.index(index, offsetBy: 1)))
}else{
cell.textLabel?.text = key;
}
cell.switchView?.setOn(val, animated: true)
return cell
}
You can change array value in switchChange action
lets i take array for switch as below:
var arrSwitch = [false,false,false,false,false,false,false,false,false,false]
Below is my cellForRowAt Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! customCell
cell. switchView.setOn(self.arrSwitch[indexPath.row], animated: false)
cell. switchView.tag = indexPath.row
cell. switchView.addTarget(self, action: #selector(self.onSwitchTap(_:)), for: .valueChanged)
return cell
}
Here is my onSwitchTap Action
#IBAction func onSwitchTap(_ sender: UISwitch) {
self.arrSwitch[sender.tag] = !self.arrSwitch[sender.tag]
}
Now on scroll it will persist last changes you have done.

How to change the title of a Button in a TableView (Swift)

I am trying to make a button when pressed to call a phone number. In the TableView the button's title is supposed to be changed with the contact dictionary I added
Here is the Button:
#IBAction func phoneLabel(sender: UIButton) {
let url:NSURL = NSURL(string: "tel://contacts.phone")!
UIApplication.shared.openURL(url as URL)
}
and this is the tableview where I'm trying to set the button tile to the contact phone numbers
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath) as! customCell
let contact = contacts.contactWithIndex((indexPath as NSIndexPath).row)
print(contact)
cell.phoneLabel.setTitle("\(phone)", for:UIControlState)
return cell
}
There is an error occurring saying that the button has no value of setTitle. Any help would be awesome!
Assuming it's a button and not a label (looking at your sender definition), you must specify UIControlState.normal, not UIControlState.
https://developer.apple.com/reference/uikit/uicontrolstate/1618233-normal