Can't return a value from UITextField to UILabel in UITableViewCell as subviews - ios

I made a UITextfield to receive the data from user.
I want to convert a value from UITextField to UILabel.
I did it in simple UIView, which has only two object, UITextField and UILabel.
This is the code that works.
class ViewController: UIViewController, UITextFieldDelegate {
let inputNumber = UITextField(frame: CGRect(x: 150.0, y: 100.0, width: 200.0, height: 50.0))
let outputNumber = UILabel(frame: CGRect(x: 150.0, y: 200.0, width: 200.0, height: 50.0))
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override func viewDidLoad() {
super.viewDidLoad()
calculatePrice()
}
func calculatePrice () {
priceInputLabel.keyboardType = .numberPad
priceInputLabel.clearButtonMode = .whileEditing
self.view.addSubview(priceInputLabel)
toolBarKeyBoard.sizeToFit()
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
priceInputLabel.inputAccessoryView = toolBarKeyBoard
}
#objc func donePressed() {
view.endEditing(true)
result = inputNumber.text!
let convertedNumber = (result as NSString).doubleValue
if Int(inputNumber.text!) == nil {
outputNumber.text = String("Nil")
} else {
outputNumber.text = String(Int(convertedNumber * 0.85))
}
}
}
But in other case, down below, the problem is UITextField and UILabel are in the UITableViewCell as subviews.
I made a 3 swift files. 2 files are UITableViewCell subclasses, and 1 file is a UITableView class.
1. FruitTableViewCell : UITableViewCell subclass
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitTextField.endEditing(true)
}
}
2. AnotherFruitTableViewCell : UITableViewCell subclass
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}
3. TableViewController : UITableViewController class
class TableViewController: UITableViewController, UITextFieldDelegate {
let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
}
The fruitsTextField and fruitsTextLabel is not in the same class like in the first example code.
So, I cannot call both instances and calculate a value in ViewController class. Of course, cannot return a calculated value.
And, I'm not sure I can return after touching a done button to get a value from UITextField to UILabel, because the cells which is the super view of subview(UITextField and UILabel) are reproduced. I'm confusing touching a done button occurs dequeueing cells again.
How can I return a value from UITextField to UILabel in UITableViewCell?
Thanks!

If I understand correctly you want to change some parameter of one cell based on action in another cell (actually the fact that these cells are of different classes is not important to that matter). ViewController will be in that case intermediary, so you need to make cells to communicate with ViewController. For communication between two objects one usually uses delegate or closure pattern.
In that case I would use closure. So, when you instantiate the cell with TextField ViewController tells Cell what to do when Done is pressed. To achieve that:
add var didEntered: ((_ text: String)->())? to FruitTableViewCell
add didEntered?(fruitsTextField.text ?? "") to #objc func donePressed()
add (the code updates the second row based on textfield value - just for a example)
cell.didEntered = {text in
self.fruitsComponents[1] = text
self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: UITableViewRowAnimation.none)}
to override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
On the way I corrected some mistakes to make it work, so the complete code is below.
Upd 1
import UIKit
class ViewController: UITableViewController, UITextFieldDelegate {
var fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
var fruitsLabels: [String] = ["", "", "", ""]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.didEntered = {text in
self.fruitsLabels = Array(repeating: text, count: 4)
self.tableView.reloadData()
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.fruitsTextLabel.text = fruitsLabels[indexPath.row]
return cell
}
}
}
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
var result : String!
var didEntered: ((_ text: String)->())?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.backgroundColor = .yellow
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitsTextField.endEditing(true)
didEntered?(fruitsTextField.text ?? "")
}
}
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}

You can use UITextField delegate methods to achieve this in your TableViewController.
In your TableViewController cellForRowAt method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row] //"I am confused why it is here"
cell.fruitsTextField.delegate = self
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
Now add UITextField Delegate methods:
extension TableViewController : UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
print(textField.text!)
let cell = self.tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as! AnotherFruitTableViewCell
cell.textLabel.text = textField.text!
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Hope this helps.

Related

Call contextMenuConfigurationForRowAt on Click instead of Long Press

I took my lead from this tutorial: https://kylebashour.com/posts/context-menu-guide
I thought I had everything, yet when I click on the row, nothing happens
UPDATE: Thanks a comment I learned that contextMenuConfigurationForRowAt is called on a long press. Is there a way to call this on a click? I wanted to give users several options for contacting the people listed. But buttons in UITableViewCell doesn't seem to work, so a context menu is the best I can think of, but a long press isn't obvious for interacting with this table view.
import UIKit
class PeerSupportVC: UIViewController {
#IBOutlet weak var peerSupportTableView: UITableView!
var supportArray: NSArray = [];
fileprivate var configuration = Configuration.sharedInstance;
override func viewDidLoad() {
super.viewDidLoad()
self.peerSupportTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.peerSupportTableView.delegate = self;
self.peerSupportTableView.dataSource = self;
self.peerSupportTableView.rowHeight = 200.0
getPeerSupport();
}
func getPeerSupport(){
self.supportArray = ["One","Two","Three"]
DispatchQueue.main.async(execute: {
self.peerSupportTableView.reloadData()
});
}
}
extension PeerSupportVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let item = self.supportArray[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let phoneAction = UIAction(title: "Phone Call", image: UIImage(systemName: "person.fill")) { (action) in
DispatchQueue.main.async(execute: {
print("Wants to Phone Call");
});
}
let textAction = UIAction(title: "Text Messsage", image: UIImage(systemName: "person.badge.plus")) { (action) in
DispatchQueue.main.async(execute: {
print("Wants to Text Message");
});
}
return UIMenu(title: "Contact Options", children: [phoneAction, textAction])
}
}
}
extension PeerSupportVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Count of Support Array:",self.supportArray.count);
return self.supportArray.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath);
for viewToRemove in cell.subviews {
viewToRemove.removeFromSuperview();
}
let width = self.peerSupportTableView.frame.width;
if let result = self.supportArray[indexPath.row] as? String{
print(result);
let NameLabel = UILabel(frame: CGRect(x: 180, y: 0, width: width-155, height: 30));
NameLabel.textColor = .black;
NameLabel.text = " "+(result);
NameLabel.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight(rawValue: 400));
NameLabel.adjustsFontSizeToFitWidth = true;
cell.addSubview(NameLabel);
}
return cell;
}
}
I dumbed the code down to share, but this lesser version still doesn't work.
What I want to happen is a menu to pop up on the line that was selected and give the user options on how to contact the person they selected. Any help on displaying the menu would be greatly appreciated.
For reference, here's your code, modified to use a reusable cell:
// simple cell class, based on your code
class PeerCell: UITableViewCell {
let nameLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.font = .systemFont(ofSize: 12, weight: UIFont.Weight(rawValue: 400))
contentView.addSubview(nameLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: g.topAnchor),
nameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 180.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
nameLabel.heightAnchor.constraint(equalToConstant: 30.0),
])
}
}
class PeerSupportVC: UIViewController {
#IBOutlet var peerSupportTableView: UITableView!
var supportArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// register our custom cell
self.peerSupportTableView.register(PeerCell.self, forCellReuseIdentifier: "Cell")
self.peerSupportTableView.delegate = self;
self.peerSupportTableView.dataSource = self;
self.peerSupportTableView.rowHeight = 200.0
getPeerSupport();
}
func getPeerSupport(){
self.supportArray = ["One","Two","Three"]
}
}
extension PeerSupportVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let item = self.supportArray[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let phoneAction = UIAction(title: "Phone Call", image: UIImage(systemName: "person.fill")) { (action) in
print("Wants to Phone Call");
}
let textAction = UIAction(title: "Text Messsage", image: UIImage(systemName: "person.badge.plus")) { (action) in
print("Wants to Text Message");
}
return UIMenu(title: "Contact Options", children: [phoneAction, textAction])
}
}
}
extension PeerSupportVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Count of Support Array:",self.supportArray.count);
return self.supportArray.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PeerCell
let result = self.supportArray[indexPath.row]
print(result)
cell.nameLabel.text = result
return cell;
}
}
As I said in my comment, though, your contextMenuConfigurationForRowAt was working as-is -- just need to long-press on the cell to trigger the call.

Adding double tap gesture recognizer to UIImageView in an UITableViewCell Swift 4+

(Edited with working solution)
So I'm trying to add a double tap gesture to an UIImageView I created in a custom UITableViewCell but can't seem to get it working.
Here is my custom UITableViewCell:
protocol CustomCellDelegate: class {
func didTapImage()
}
class CustomCell: UITableViewCell {
//change let to lazy var
lazy var userImage: UIImageView = {
let newView = UIIMageView()
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
newView.translatesAutoresizingMaskIntoConstraints = false
return newView
}
weak var delegate: CustomCellDelegate?
#objc func myFunc() {
delegate?.didTapImage()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subTitle, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
//changed addSubView(userImage) to self.contentView.addSubView(userImage)
self.contentView.addSubView(userImage)
NSLayoutConstraint.activate([
userImage.centerYAnchor.constraint(equalTo: self.centerYAnchor),
userImage.leftAnchor.constraint(equalTo: self.leftAnchor),
userImage.widthAnchor.constraint(equalToConstant: 48),
userImage.heightAnchor.constraint(equalToConstant: 48),
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Here is my custom UITableViewController:
class customTableViewController: UITableViewController, CustomCellDelegate {
fileprivate let cellId = "cellId"
func didTapImage() {
print("Tapped Image")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
}
Any ideas as to why this isn't working? What am I doing wrong? Also how do I avoid having the same tap gestures recognizer added multiple times as cells are dequeue?
You may need
userImage.translatesAutoresizingMaskIntoConstraints = false
as you create constraints programmatically
lazy var userImage: UIImageView = {
let newView = UIIMageView()
userImage.translatesAutoresizingMaskIntoConstraints = false
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
return newView
}()
also make it a lazy var not a computed property for being 1 instance every access , add the imageView to
self.contentView.addSubView(userImage)
and set the constraints with it

didEndEditingRowAt not called with custom tableview cell

I have a tableview (SettingsViewController) which I use as a user info view where user info is displayed (name, email, phone #, etc). This is similar to the standard iOS contact page.
Each cell has a textfield which is stretched accross the size of the cell, so that once in "edit" mode the user can update his/her info.
I also have a custom Cell (SettingsCell) which is where i setup the cell with the textfield etc.
SettingsViewController (excluded a lot of tabelview setup code):
class SettingsViewController: UITableViewController{
let cellId = "cellId"
var apiController: APIController?
var firstName: String?
var lastName: String?
var email: String?
var phoneNumber: String?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .mainWhite()
tableView = UITableView(frame: CGRect.zero, style: .grouped)
tableView.register(SettingsCell.self, forCellReuseIdentifier: cellId)
setupNavigation()
}
fileprivate func setupNavigation() {
editButtonItem.action = #selector(showEditing)
editButtonItem.title = "Edit"
editButtonItem.tintColor = .mainWhite()
self.navigationItem.rightBarButtonItem = editButtonItem
}
#objc func showEditing(sender: UIBarButtonItem)
{
if(self.tableView.isEditing == false)
{
self.tableView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Save"
self.tableView.reloadData()
}
else
{
self.tableView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Edit"
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! SettingsCell
if self.tableView.isEditing == true {
cell.textField.isEnabled = true
if indexPath.section == 2 {
cell.textField.keyboardType = .phonePad
}
} else {
cell.textField.isEnabled = false
}
cell.selectionStyle = .none
filloutUserInfo(indexPath: indexPath, cell: cell)
return cell
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
//THIS NEVER GETTING EXECUTED
override func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
print("editing done for row \(indexPath?.item)")
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
}
Settings Cell:
class SettingsCell: UITableViewCell, UITextFieldDelegate {
let textField: UITextField = {
let tf = UITextField()
tf.isEnabled = false
return tf
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
addSubview(textField)
textField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: 0, height: 0)
textField.addDoneButtonOnKeyboard()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem I've now encountered is that after I have gone into edit mode, and changed the text of a given cell, the tableview doesn't actually recognize this. The didEndEditingRowAt never gets called and that print statement is never displayed. I am suspecting that is has something to do with the textfield not being connected to the tableviewcontroller in any way, but I'm not sure how to fix this.
I need to be able to know when a user has finished editing in order to display an alert for improper formatting and disabling the save button.
You need to implement a callback to listen the textField endEditing event from SettingsCell to your ViewController.
To achieve this, here is the updated SettingsCell
class SettingsCell: UITableViewCell, UITextFieldDelegate {
let textField: UITextField = {
let tf = UITextField()
tf.isEnabled = false
tf.addDoneButtonOnKeyboard()
return tf
}()
public var onEndEditing: ((String?) -> Void)?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
textField.removeFromSuperview()
addSubview(textField)
textField.delegate = self
textField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: 0, height: 0)
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.onEndEditing?(textField.text)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now update cellForRowAt to listen the endEditing event as below,
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! SettingsCell
if self.tableView.isEditing == true {
cell.textField.isEnabled = true
cell.onEndEditing = { text in
print("Cell Editing finished with text: \(text)")
}
if indexPath.section == 2 {
cell.textField.keyboardType = .phonePad
}
} else {
cell.textField.isEnabled = false
}
cell.selectionStyle = .none
filloutUserInfo(indexPath: indexPath, cell: cell)
return cell
}

UITableview scrolling is not responding properly?

booktable.frame = CGRect(x: 0, y: booktopview.bounds.height, width: screenWidth, height: screenHeight-booktopview.bounds.height-tabbarView.bounds.height)
booktable.register(UITableViewCell.self, forCellReuseIdentifier: "mycell")
booktable.dataSource = self
booktable.delegate = self
booktable.separatorColor = UIColor.lightGray
booktable.backgroundColor = UIColor.clear
booktable.separatorStyle = .singleLine
bookview.addSubview(booktable)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(tableView == booktable)
{
let cell1 = booktable.dequeueReusableCell(withIdentifier: "mycell")
for object in (cell1?.contentView.subviews)!
{
object.removeFromSuperview();
}
let img :UIImageView = UIImageView()
let lbl : UILabel = UILabel()
img.frame = CGRect(x: 15, y: 15, width: 80, height: 130)
img.image = imgarray[indexPath.row]
img.layer.borderWidth = 1.0
img.layer.borderColor = UIColor.lightGray.cgColor
cell1?.contentView.addSubview(img)
imgheight = img.bounds.height
lbl.frame = CGRect(x: img.bounds.width + 40, y: (imgheight+40-80)/2, width: booktable.bounds.width-img.bounds.width + 40 - 100, height: 80)
lbl.text = imgname[indexPath.row]
lbl.numberOfLines = 0
lbl.textAlignment = .left
lbl.font = UIFont(name: "Arial", size: 23)
lbl.textColor = UIColor.black
cell1?.selectionStyle = .none
cell1?.contentView.addSubview(lbl)
return cell1!
}
The code shown above is for book table, which sometimes scrolls like normal and sometimes not scrolling at all. I am doing all the code programatically. I have tested this on both simulators and devices but still the problem exists. Any help is appreciated...
Create Custom UITableViewCell, let's say it is ListTableCell
class ListTableCell: UITableViewCell {
#IBOutlet weak var lblTemp: UILabel!
#IBOutlet weak var imgTemp: UIImage!
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
}
}
I've created UITableViewCell with xib like this and bind IBOutlets
Let's say we have struct Model and array like this
struct Model {
let image : UIImage
let name: String
}
for i in 0...10 {
let model = Model(image: #imageLiteral(resourceName: "Cat03"), name: "Temp \(i)")
array.append(model)
}
Now on ViewController viewDidLoad() method,
tableView.register(UINib(nibName: "ListTableCell", bundle: nil), forCellReuseIdentifier: "ListTableCell")
Implement UITableViewDataSource methods like this,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListTableCell") as! ListTableCell
let model = array[indexPath.row]
cell.lblTemp.text = model.name
cell.imgTemp.image = model.image
return cell
}
FYI
For different tableviews, you can create different custom cell the same way and cellForRowAt indexPath and numberOfRowsInSection method will change appropriately.
Let me know in case of any queries.
UPDATE
Follow this and this to create CustomTableCell programmatically

UITableViewCell behaves differently in iOS 11

I have done a UITableViewCell programmatically and it worked just fine with iOS 10. But after updating with iOS 11 and XCode 9, it behaves differently. The layout looks scrambled as below.
But if I tap on the cell then it rearranges and looks fine as below.
Here the code for UITableViewCell
import UIKit
import SnapKit
class AboutCell: UITableViewCell {
let roleLabel : UILabel = {
var tablelabel = UILabel()
tablelabel.font = UIFont (name: "Avenir Next Medium", size: 22)
tablelabel.textAlignment = .center
tablelabel.clipsToBounds = true
tablelabel.translatesAutoresizingMaskIntoConstraints = false
return tablelabel
}()
let nameLabel : UILabel = {
let tablelabel = UILabel()
tablelabel.font = UIFont (name: "Avenir Next Medium", size: 16)
tablelabel.textAlignment = .center
tablelabel.clipsToBounds = true
tablelabel.translatesAutoresizingMaskIntoConstraints = false
return tablelabel
}()
let webUrlLabel : UILabel = {
let tablelabel = UILabel()
tablelabel.font = UIFont (name: "Avenir Next Medium", size: 16)
tablelabel.textAlignment = .center
tablelabel.clipsToBounds = true
tablelabel.translatesAutoresizingMaskIntoConstraints = false
return tablelabel
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
override func layoutSubviews() {
super.layoutSubviews()
roleLabel.frame = CGRect(x: 0, y: 10, width: self.contentView.bounds.size.width-20, height: 25)
nameLabel.frame = CGRect(x: 0, y: roleLabel.frame.origin.y+25, width: self.bounds.size.width-20, height: 25)
webUrlLabel.frame = CGRect(x: 0, y: nameLabel.frame.origin.y+25, width: self.bounds.size.width-20, height: 25)
}
func setupViews(){
contentView.addSubview(roleLabel)
contentView.addSubview(nameLabel)
contentView.addSubview(webUrlLabel)
}
func setValuesForCell(contributor : Contributors){
roleLabel.text = contributor.contributorRole
nameLabel.text = contributor.contributorName
webUrlLabel.text = contributor.contributorWeb
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
and the extension I wrote for TableView delegate and datasource
extension AboutController : UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contributorList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : AboutCell = tableView.dequeueReusableCell(withIdentifier: self.cellid, for: indexPath) as! AboutCell
cell.selectionStyle = .default
let contributor : Contributors = contributorList[indexPath.row]
cell.setValuesForCell(contributor: contributor)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
tableView.deselectRow(at: indexPath, animated: true)
}
}
and the ViewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100.0
tableView.rowHeight = 100
tableView.register(AboutCell.self, forCellReuseIdentifier: self.cellid)
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.top.right.bottom.left.equalToSuperview()
}
let mayu = Contributors(contibutorRole: "Developer", contributorName: "J Mayooresan", contributorWeb: "http://jaymayu.com")
let janie = Contributors(contibutorRole: "Voice Artist", contributorName: "M Jananie", contributorWeb: "http://jaymayu.com")
let arjun = Contributors(contibutorRole: "Aathichudi Content", contributorName: "Arjunkumar", contributorWeb: "http://laymansite.com")
let artist = Contributors(contibutorRole: "Auvaiyar Art", contributorName: "Alvin", contributorWeb: "https://www.fiverr.com/alvincadiz18")
contributorList = [mayu, arjun, janie, artist]
tableView.delegate = self
tableView.dataSource = self
self.tableView.reloadData()
}
Since you're laying out your tableView using autolayout, you also need to ensure translatesAutoresizingMaskIntoConstraints is set to false.
SnapKit should be setting the tableView's translatesAutoresizingMaskIntoConstraints to false for you already.
Since you're laying out the cells manually (using frames in layoutSubviews). Setting the cells subview's translatesAutoresizingMaskIntoConstraints to false is likely not needed.
See Apple Docs here for translatesAutoresizingMaskIntoConstraints

Resources