Add button to uitableview cell programmatically - swift

I just want my code to produce a button in every table view cell that has text. When the button is press just have it say hi. Each cell should have a button in it. I want this to be done all and code and do not use the storyboard at all. The class should remain a uiview controller and not be changed.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let myArray: NSArray = ["First","Second","Third"]
var myTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
myTableView.dataSource = self
myTableView.delegate = self
self.view.addSubview(myTableView)
myTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
myTableView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.90),
myTableView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1),
myTableView.topAnchor.constraint(equalTo: view.topAnchor),
myTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
])
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
print("Value: \(myArray[indexPath.row])")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
cell.textLabel!.text = "\(myArray[indexPath.row])"
return cell
}
}

Create a subclass of UITableViewcell -
class MyCell: UITableViewCell {
var buttonTapCallback: () -> () = { }
let button: UIButton = {
let btn = UIButton()
btn.setTitle("Button", for: .normal)
btn.backgroundColor = .systemPink
btn.titleLabel?.font = UIFont.systemFont(ofSize: 14)
return btn
}()
let label: UILabel = {
let lbl = UILabel()
lbl.font = UIFont.systemFont(ofSize: 16)
lbl.textColor = .systemPink
return lbl
}()
#objc func didTapButton() {
buttonTapCallback()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//Add button
contentView.addSubview(button)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
//Set constraints as per your requirements
button.translatesAutoresizingMaskIntoConstraints = false
button.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
button.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
//Add label
contentView.addSubview(label)
//Set constraints as per your requirements
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: button.trailingAnchor, constant: 20).isActive = true
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now in your view controller register this cell -
myTableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
Now load this cell using cellForRowAt method -
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.label.text = ""
cell.buttonTapCallback = {
cell.label.text = "Hi"
}
return cell
}

You could add a UIButton to UITableViewCell by just adding it in the cellForRow method, like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
let button = UIButton()
button.setTitle("\(myArray[indexPath.row])", for: .normal)
button.center = cell.center
cell.addSubview(button)
return cell
}
Although the above method works, it is not recommended. You should probably create a custom class for UITableViewCell and use it as your cell, like this:
class MyCustomCell: UITableViewCell {
let button = UIButton()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(button)
// do layout setup here
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
myTableView.register(MyCustomCell.self, forCellReuseIdentifier: "MyCell")
//...
}
//...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCustomCell
cell.button.setTitle("\(myArray[indexPath.row])", for: .normal)
return cell
}

Related

Getting id by clicking on tableView

By clicking on the red area I get a comment id. But I also want to get the id if I click on the blue button. How can I do that?
Right now I use this to detect a tap on a row. But tapping on button should run some other code.
extension FirstTabSecondViewComment: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
FirstTabSecondViewComment.subComment = table[indexPath.row].commentId ?? ""
print(FirstTabSecondViewComment.subComment)
self.performSegue(withIdentifier: "CommentDetail", sender: Any?.self)
}
}
After you have register your custom cell declare it in cellForRowAt and add target in button cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! MyCell // your custom cell
// add target in buoon cell
cell.YourButtonCell.addTarget(self, action: #selector(submit(_:)), for: .touchUpInside)
return cell
}
after that add submit func:
#objc fileprivate func submit(_ sender: UIButton) {
var superview = sender.superview
while let view = superview, !(view is UITableViewCell) {
superview = view.superview
}
guard let cell = superview as? UITableViewCell else {
print("button is not contained in a table view cell")
return
}
guard let indexPath = tableView.indexPath(for: cell) else {
print("failed to get index path for cell containing button")
return
}
// We've got the index path for the cell that contains the button, now do something with it.
print("button is in index \(indexPath.row)")
}
This is a full code example, copy and paste in a new project and run:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.register(MyCell.self, forCellReuseIdentifier: cellId)
}
#objc fileprivate func submit(_ sender: UIButton) {
var superview = sender.superview
while let view = superview, !(view is UITableViewCell) {
superview = view.superview
}
guard let cell = superview as? UITableViewCell else {
print("button is not contained in a table view cell")
return
}
guard let indexPath = tableView.indexPath(for: cell) else {
print("failed to get index path for cell containing button")
return
}
// We've got the index path for the cell that contains the button, now do something with it.
print("button is in index \(indexPath.row)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
// add target in buoon cell
cell.cancelButton.addTarget(self, action: #selector(submit(_:)), for: .touchUpInside)
return cell
}
}
class MyCell: UITableViewCell {
let cancelButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .white
b.setTitle("get Index", for: .normal)
b.layer.cornerRadius = 10
b.clipsToBounds = true
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.tintColor = .black
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .darkGray
contentView.addSubview(cancelButton)
cancelButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
cancelButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
cancelButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
cancelButton.widthAnchor.constraint(equalToConstant: 300).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

UITableViewCell textLabel not covering the full width of the cell

I tried all the popular suggestions like:
tableView.cellLayoutMarginsFollowReadableWidth = false
tableView.contentInset = .zero
tableView.separatorInset = .zero
but still im getting some spacing between the textlabel and the parent cell like below:
Cell creation code is shown below:
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = eligibleArray[indexPath.row]
cell.layoutMargins = .zero
cell.separatorInset = .zero
cell.textLabel?.textColor = .secondaryLabel
cell.textLabel?.backgroundColor = .systemOrange
cell.textLabel?.numberOfLines = 0
cell.backgroundColor = .systemPink
Please help with suggestions
This is the max that you can do with default textLabel cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if cell.responds(to: #selector(setter: UITableViewCell.separatorInset)) {
cell.separatorInset = .zero
}
if (cell.responds(to: #selector(setter: UIView.preservesSuperviewLayoutMargins))) {
cell.preservesSuperviewLayoutMargins = false
}
if (cell.responds(to: #selector(setter: UIView.layoutMargins))) {
cell.layoutMargins = UIEdgeInsets.zero
}
if tableView.responds(to: #selector(setter: UITableViewCell.separatorInset)) {
tableView.separatorInset = .zero
}
}
and this is the result
otherwise you can create a custom cell to have total control:
class YourController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(YourCell.self, forCellReuseIdentifier: "cellId")
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! YourCell
cell.myLabel.text = "System will compute elegibility based on the current LTV of the loanscheme selected"
cell.myLabel.numberOfLines = 0
cell.myLabel.backgroundColor = .orange
cell.backgroundColor = .systemPink
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
your cell:
class YourCell: UITableViewCell {
let myLabel: UILabel = {
let l = UILabel()
l.backgroundColor = .orange
l.textColor = .black
l.numberOfLines = 0
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(myLabel)
myLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
myLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
myLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
myLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and this is the result

Why UITableViewController with Generic data source does not call title for header?

I have superclass with generic data source 'items':
This class simply uses table view and show items.
class ViewController<T>: UIViewController, UITableViewDataSource, UITableViewDelegate {
var items: [T] = []
var tableView = UITableView(frame: .zero, style: .plain)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.setupTableView()
self.placeTableView()
}
private func setupTableView() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.delegate = self
}
func placeTableView() {
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = String(describing: item)
return cell
}
}
Then I subclass it by new class 'MyViewController'.
class MyViewController: ViewController<String> {
override func viewDidLoad() {
super.viewDidLoad()
self.items = ["1", "2", "3"]
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Header"
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "Footer"
}
}
The question is why tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) is not called ?
The data source is set.
But if I remove generic in superclass like
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var items: [String] = []
var tableView = UITableView(frame: .zero, style: .plain)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.setupTableView()
self.placeTableView()
}
private func setupTableView() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.delegate = self
}
func placeTableView() {
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = String(describing: item)
return cell
}
}
It perfectly call tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) in 'MyViewController'.
Why is that ?
Have you set the hight for the table header ?
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
10
}

Swift: Delegate of UITextField in UITableViewCell does not work

I'm trying to set the delegate of my titleTF to my UITableViewCell or my UITableView. This is how I set up the textField inside my UITableViewCell class.
class AddAppointmentTableViewCell: UITableViewCell, UITextFieldDelegate, UITextViewDelegate {
var view = UIView()
var titleTF = UITextField()
func addTitle() -> UIView {
view = UIView(frame: CGRect(x: 0, y: 0, width: frame.width, height:
NSAttributedString(string: "Enter Title", attributes: [.font: UIFont.boldSystemFont(ofSize: 24),
.foregroundColor: UIColor.lightGray]).size().height+30))
titleTF.translatesAutoresizingMaskIntoConstraints = false
titleTF.textAlignment = .left
titleTF.delegate = self
titleTF.attributedText = NSAttributedString(string: "Enter Title", attributes: [.font: UIFont.boldSystemFont(ofSize: 24),
.foregroundColor: UIColor.lightGray])
titleTF.inputAccessoryView = toolBar
view.addSubview(titleTF)
titleTF.topAnchor.constraint(equalTo: view.topAnchor, constant: 15).isActive = true
titleTF.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15).isActive = true
titleTF.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
titleTF.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15)
return view
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
}
override func draw(_ rect: CGRect) {
addSubview(view)
}
}
This is inside my UITableView class:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return rows[indexPath.row].frame.height
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AddAppointmentTableViewCell
cell.view = rows[indexPath.row]
return cell
}
And rows is equals [cell.addTitle(), cell.addNote()]
I don't understand why the textFieldDidBeginEditing method inside my UITableViewCell isn't called.
You have to call the delegate inside the awakeFromNib (had the same problem):
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
You need to add this line
{
textField.delegate = self
}

Programmatically perform segue to new View Controller from UIbutton within a cell? (without storyboard)

I would like to perform a segue to a new VC by clicking on a UIButton that is found with in a cell (a Users Cell)
class TableViewController: UITableViewController {
var userScreenVC: usersScreenVC?
var HomePageVc: HomePageVC?
var signInVC: SignInVC?
var ref = FIRDatabase.database().reference()
let cellId = "cellId"
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserCellLargePicture.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCellLargePicture
cell.selectionStyle = .none
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
let user = users[indexPath.row]
cell.ProfileButton.setImage(#imageLiteral(resourceName: "Grape"), for: .normal)
return cell
}//func tableview cellForRowAt
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 234
}
class UserCellLargePicture: UITableViewCell {
var ProfileButton: UIButton = {
let button = UIButton()
//label.text = "TEST TEST TEST"
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(ProfileButton)
ProfileButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -58).isActive = true
ProfileButton.widthAnchor.constraint(equalToConstant: 54).isActive = true
ProfileButton.heightAnchor.constraint(equalToConstant: 54).isActive = true
ProfileButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}