Swift: Delegate of UITextField in UITableViewCell does not work - swift

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
}

Related

How to make changes to UITableViewCell from navigationBar?

How can I make layout changes to a UITableViewCell from the navigation bar of a UITableViewController? It feels like I've tried everything, but to no avail.
This is the controller:
class TableViewController: UITableViewController {
let tableViewCellID = "tableViewCellID"
//MARK: Loading view
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableViewCell.self, forCellReuseIdentifier: tableViewCellID)
navigationBar()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: tableViewCellID) as? TableViewCell else { return UITableViewCell() }
tableViewCell.firstLayout()
return tableViewCell
}
func navigationBar() {
let emptyButton = UIBarButtonItem(image: UIImage(named: "icon"), style: .done, target: self, action: #selector(buttonTapped))
navigationItem.rightBarButtonItems = [emptyButton]
navigationItem.largeTitleDisplayMode = .never
}
#objc func buttonTapped() {
let tableViewCell = TableViewCell()
tableViewCell.secondLayout()
}
}
This is the cell:
class TableViewCell: UITableViewCell {
let firstColorView: UIView = {
let coverView = UIView()
coverView.translatesAutoresizingMaskIntoConstraints = false
coverView.backgroundColor = .red
return coverView
}()
let secondColorView: UIView = {
let cameraViewClosure = UIView()
cameraViewClosure.translatesAutoresizingMaskIntoConstraints = false
cameraViewClosure.backgroundColor = .yellow
return cameraViewClosure
}()
func firstLayout() {
contentView.addSubview(firstColorView)
NSLayoutConstraint.activate([
firstColorView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1),
firstColorView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1),
])
}
func secondLayout() {
print("This prints, but no changes are made to the cell")
contentView.addSubview(secondColorView)
NSLayoutConstraint.activate([
secondColorView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5),
secondColorView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1),
secondColorView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
])
}
}
I have of course tried all these:
layoutIfNeeded()
setNeedsLayout()
setNeedsDisplay()
layoutSubviews()
setNeedsUpdateConstraints()
updateConstraints()
updateConstraintsIfNeeded()
... and I have tried them in all kinds combinations, but I still don't get the results I want.
Let me know if you need anymore code or information, please and thank you in advance for any help!
Try the following modifications.
#objc func buttonTapped() {
let cell = tableView.visibleCells.first as? TableViewCell
cell?.secondLayout()
}

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
}

How I can pin the tableHeaderView?

Tell me, please, how I can pin the tableHeaderView on the ViewController's screen through code? I use this code, but the tableViewHeader disappears on scrolling:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
createTable()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .grouped)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableHeaderView = "Test Header"
view.addSubview(tableViewTest)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
}
This is my second class:
import UIKit
class TestTableViewCell: UITableViewCell {
let testLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
testLabel.frame = CGRect(x: 60, y: 5, width: UIScreen.main.bounds.width - 80, height: 50)
testLabel.numberOfLines = 0
testLabel.sizeToFit()
addSubview(testLabel)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try to use the implicit construct of UITableView
lazy var tableViewTest = UITableView(frame: .zero, style: .plain)
But also you need to change your table header to section header which you can define in UITableViewDelegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = YourSectionHeaderView.reuseIdentifier
let headerView =
tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier)
return headerView
}
By the way, I also recommend you to use constraint instead of autoresizing mask
Thank you.
This work for me:
import UIKit
class TestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableViewTest = UITableView()
var segmentedControl = UISegmentedControl(items: ["Test1", "Test2", "Test3"])
override func viewDidLoad() {
super.viewDidLoad()
createTable()
configureSegmentedControl()
}
private func createTable() {
self.tableViewTest = UITableView(frame: view.bounds, style: .plain)
tableViewTest.register(TestTableViewCell.self, forCellReuseIdentifier: "Test")
self.tableViewTest.delegate = self
self.tableViewTest.dataSource = self
tableViewTest.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableViewTest.separatorInset.left = 10
tableViewTest.separatorInset.right = 10
tableViewTest.tableFooterView = UIView()
view.addSubview(tableViewTest)
}
private func configureSegmentedControl() {
segmentedControl.selectedSegmentIndex = 0
segmentedControl.frame = CGRect(x: 10, y: 150, width: UIScreen.main.bounds.width - 20.0, height: 40)
segmentedControl.layer.cornerRadius = 5.0
segmentedControl.backgroundColor = .blue
segmentedControl.selectedSegmentTintColor = .red
segmentedControl.tintColor = .white
segmentedControl.addTarget(self, action: #selector(changeSegment), for: .valueChanged)
}
#objc func changeSegment(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
print("Test1")
case 1:
print("Test2")
default:
print("Test3")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Test", for: indexPath) as! TestTableViewCell
cell.testLabel.text = "test label"
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return segmentedControl
}
}

Add button to uitableview cell programmatically

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
}

Put a UIButton in tableView

I made a code for added a array of UIView in my TableView, but when I add them, it doesn't show them row by row while I made a loop for it to add each item in my TableView but it adds them one above the other, another solution?
my view controller :
class mainViewController: UIViewController {
private var myArray: [UIView] = []
private var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
myTableView = UITableView(frame: CGRect(x: 50, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
myTableView.dataSource = self
myTableView.delegate = self
self.view.addSubview(myTableView)
let DoneBut: UIButton = UIButton(frame: CGRect(x: 50, y: 0, width: 150, height: 50))
DoneBut.setTitle("Done", for: .normal)
DoneBut.backgroundColor = UIColor.blue
let DoneBut2: UIButton = UIButton(frame: CGRect(x: 50, y: 0, width: 50, height: 50))
DoneBut2.setTitle("Done2", for: .normal)
DoneBut2.backgroundColor = UIColor.blue
let view1 = UIView()
view1.addSubview(DoneBut)
myArray.append(view1)
let view2 = UIView()
view2.addSubview(DoneBut2)
myArray.append(view2)
}
}
extension mainViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"MyCell", for: indexPath as IndexPath)
for array in myArray {
cell.contentView.addSubview(array)
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return myArray.count
}
}
extension mainViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
IndexPath) {
print("Num: \(indexPath.row)")
print("Value: \(myArray[indexPath.row])")
}
}
Your issue is that each time the cellForRowAt function is asking for a cell, you are looping through ALL the views and adding them to each cell. Instead you should be indexing in it using the indexPath. See the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
// check if there is a view at the indexPath
if indexPath.row < myArray.count {
// there is a view, add it to the cells contentView
cell.contentView.addSubview(myArray[indexPath.row])
} else {
print("no view at index")
}
return cell
}