I have a custom UITableViewCell where I've added a UITextField, I'd like to perform some validation on the textfield and set a hasError property on the cell to maybe add a error label to the cell and change the textfield back color.
I'm not using a storyboard and creating my custom cell / UITableViewCell programatically.
I'm using textFieldDidEndEditing in my UIViewController to detect text changes and have no access to the cell, to set my hasError property.
I guess I could loop around the views / textfields by a tag to field the textfield then gain it's parent.
Or maybe I should implement my own version of textFieldDidEndEditing which then fires another event which has the cell and the textfield as parameters.
But I'm not sure if that's the best approach or how to fire an event.
You can use Protocol. https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
A simple example:
cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ....
cell.delegate = self
return cell
}
Your UITableViewCell subclass:
class Cell: UITableViewCell, UITextFieldDelegate {
// MARK: - Properties
weak var delegate: CellDelegate?
....
// MARK: - Functions
func textFieldDidEndEditing(_ textField: UITextField) {
self.delegate?.cell(self, textFieldDidEndDiting: textField)
}
}
Your Protocol
protocol CellDelegate: class {
func cell(_ cell: Cell, textFieldDidEndDiting textField: UITextField)
}
Then finally, conform your controller to that protocol like so:
class ViewController: UIViewController, CellDelegate {
func cell(_ cell: Cell, textFieldDidEndDiting textField: UITextField) {
}
}
Related
I've got a UITableViewController with two custom cells - one contains a UITextField (for the user to input a title) and the other contains a UITextView (for the user to input a description). Whenever these change, I want to update my memory object (which is a struct with two variables - memoryTitle and memoryDescription).
The memoryTitle seems simple enough - on my ViewController I have the following:
#IBAction func memoryTitleChanged(_ sender: UITextField) {
memory.memoryTitle = sender.text ?? ""
}
The UITextView has confused me slightly though. There's two issues I'm having - I can't create an action in the same way I can for the UITextField, so my next thought was to make the ViewController the delegate and use textViewDidChange to update memory.memoryDescription but that brings me to my second problem.
In order to make the UITextView cell resize dynamically, I used the following tutorial which works perfectly (https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01) to make my custom cell this:
class DetailTextTableViewCell: UITableViewCell, UITextViewDelegate {
//Found below method for resizing UITextView and cell - https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01
#IBOutlet weak var memoryDescriptionTextView: UITextView!
var textChanged: ((String) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
memoryDescriptionTextView.delegate = self
memoryDescriptionTextView.backgroundColor = UIColor.red
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//Found below method for resizing UITextView and cell - https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01
func textChanged(action: #escaping (String) -> Void) {
self.textChanged = action
}
func textViewDidChange(_ textView: UITextView) {
textChanged?(textView.text)
}
}
Now I'm stuck with DetailTextTableViewCell being the UITextView's delegate, so I'm not sure how to make it update my memory object in the ViewController when the text changes. If anyone has any ideas or guidance it'd be much appreciated!
Thank you in advance.
First, you don't need textChanged method
func textChanged(action: #escaping (String) -> Void) {
Then, what you need, is assigning your textChanged closure variable (which is good approach btw) in controller's cellForRowAt for each certain cell.
Inside closure declare, that when text view did change, certain item's (from table view data source array) property will be assigned with String parameter of closure and if you need, then reload certain cell for this IndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.textChanged = { text in
self.dataSourceArray[indexPath.row].stringProperty = text
// tableView.reloadRows(at: [indexPath], with: .none)
// if you want to reload row, move calling closure
// to `textViewDidEndEditing` instead
}
...
}
declare this protocol above your cell DetailTextTableViewCell
protocol CellDelegate {
func textViewChanged(textView : UITextView)
}
add a delegate var in your DetailTextTableViewCell
var delegate : CellDelegate?
In the cell for row of your tableView assign self to delegate property of cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
}
In your DetailTextTableViewCell add this line inside textViewDidChange
func textViewDidChange(_ textView: UITextView) {
textChanged?(textView.text)
delegate?.textViewChanged(textView)
}
Now implement the delegate function in your view controller
func textViewChanged(textView: UITextView) {
}
Inside cellForRowAt do
let cell = ///
cell.memoryDescriptionTextView.tag = indexPath.row
cell.memoryDescriptionTextView.delegate = self
and implement the delegate methods inside the vc
Try to enable user interaction property of text view inside cellForRowAt method.
cell.memoryDescriptionTextView.delegate = self
cell.memoryDescriptionTextView.isUserInteractionEnabled = true
I'm looking to get a UIButton to add its title value to the currently selected UITextField that's contained within a UITableViewCell.
I have a row of buttons with common phrases that a user might use, such as "#CompanyName". I have set the common phrases as the title of the buttons. Just below the row of buttons I have a UITableView with cells each containing several static labels and a text field. I want to allow the user to press one of the buttons above the table view to add the button's title value to the text field that is currently being edited.
I have managed to do this as a test using a text field and buttons both being outside of the table view using:
#IBAction func buttonAction(_ sender: AnyObject) {
buttonTitle = sender.titleLabel!.text!
testOutlet.text = "\(testOutlet.text!) \(buttonTitle)"
Now my question is how would I make this "testOutlet.text" dynamic so it only knows the text field that's being edited. I've looked into textFieldDidBeginEditing but couldn't figure it out. I have also tried defining the indexPath.
You need to know which UITextField is currently being edited. To do this, you can use the following code:
class ViewController: UIViewController {
// code ...
#IBAction func buttonAction(_ sender: AnyObject) {
buttonTitle = sender.titleLabel!.text!
oActiveTextField?.text = "\(oActiveTextField?.text ?? "") \(buttonTitle)"
}
fileprivate var oActiveTextField: UITextField?
}
extension ViewController: UITableViewDataSource {
// code ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: yourIdentifier, for: indexPath) as! YourTableViewCell
cell.textField.delegate = self
// TODO: configure cell
return cell
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
oActiveTextField = textField
}
func textFieldDidEndEditing(_ textField: UITextField) {
oActiveTextField = nil
}
}
I have a text field in a tableView. I need to get the position of textfield but the problem is there are multiple section in it. I am able to get only one thing section or row using textfield.tag but I need both.
You can find the parent UIResponder of any class by walking up the UIResponder chain; both UITextField and UITableViewCell inherit from UIView, which inherits from UIResponder, so to get the parent tableViewCell of your textfield you can call this function on your textfield:
extension UIResponder {
func findParentTableViewCell () -> UITableViewCell? {
var parent: UIResponder = self
while let next = parent.next {
if let tableViewCell = parent as? UITableViewCell {
return tableViewCell
}
parent = next
}
return nil
}
}
Then once you have the tableViewCell, you just ask the tableView for its index path with tableView.indexPAth(for:)
You never need to use the tag field:
guard let cell = textField.findParentTableViewCell (),
let indexPath = tableView.indexPath(for: cell) else {
print("This textfield is not in the tableview!")
}
print("The indexPath is \(indexPath)")
You can use a variation of a previous answer that I wrote.
Use a delegate protocol between the cell and the tableview. This allows you to keep the text field delegate in the cell subclass, which enables you to assign the touch text field delegate to the prototype cell in Interface Builder, while still keeping the business logic in the view controller.
It also avoids the potentially fragile approach of navigating the view hierarchy or the use of the tag property, which has issues when cells indexes change (as a result of insertion, deletion or reordering), and which doesn't work where you need to know a section number as well as a row number, as is the case here.
CellSubclass.swift
protocol CellSubclassDelegate: class {
func textFieldUpdatedInCell(_ cell: CellSubclass)
}
class CellSubclass: UITableViewCell {
#IBOutlet var someTextField: UITextField!
var delegate: CellSubclassDelegate?
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
self.delegate?.textFieldUpdatedInCell(self)
return yes
}
ViewController.swift
class MyViewController: UIViewController, CellSubclassDelegate {
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass
cell.delegate = self
// Other cell setup
}
// MARK: CellSubclassDelegate
func textFieldUpdatedInCell(_ cell: CellSubclass) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
// Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
return
}
// Do whatever you need to do with the indexPath
print("Text field updated on row \(indexPath.row) of section \(indexPath.section")
}
}
You can also see Jacob King's answer using a closure rather than a delegate pattern in the same question.
I want to extend UIButton class to make my own radio button.
class UIRadioButton : UIButton {
var checked = CheckedEnum.unchecked
enum CheckedEnum{
case checked , unchecked
}
}
It's inner class in Viewcontroller. But when I want to make this button sends the action to view Controller, as I usually do, it doesn't work. It's my button connections window:
enter image description here
And it's usually button connections window:
enter image description here
In my opinion, you should implement a delegate of your UITableViewCell and call its method when button receives touch event. Example:
protocol CellButtonDelegate: class {
func cellButton(cell: UITableViewCell, didTouch button: UIButton)
}
class Cell: UITableViewCell {
weak var delegate: CellButtonDelegate?
#IBAction func buttonTouched(sender: UIButton) {
delegate?.cellButton(cell: self, didTouch button: sender)
}
}
Then you make your view controller a delegate of each table view cell and handle this event.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView. dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
// configure cell
cell.delegate = self
}
}
extension ViewController: CellButtonDelegate {
func cellButton(cell: UITableViewCell, didTouch button: UIButton) {
// handle button touch event, save indexPath of the cell to alter representation of table view after reloading data etc.
}
}
Hope this will help.
I have a subclass, CustomCell, which inherits from my parent class, CreateEvent. The subclass describes different cells for the table view cell, which is on the CreateEvent View controller. In one specific cell, I have a textfield but I am having trouble getting the value from that textfield when a user enters into the textfield. I am also having trouble dismissing the keyboard with outside touches and pressing the return key, but I am primarily focused on getting the text from the textfield. I am familiar with doing these functionalities on a normal swift file but because this is a subclass, I'm not sure what to do. What I've tried is to use:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
entranceFeeTextField.resignFirstResponder()
return true
}
.. but this does not do the trick.
you need to give UiTextfeld delegate in UITableview Delegate method and your CustomCell looks like
class CustomCell: UITableViewCell {
#IBOutlet var textField: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
and one more thing to implement UITextfield protocol in you view controller.and your controller looks like
class CreateEvent: UIViewController,UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate {
//MARK: - Content TableView Methods
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
cell.textField.delegate = self // like delegate
return cell
}
//MARK: - UITextField Methods
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
i hope this will help