I have a table view controller with a custom cell which contains a text field - it's a form basically.
i want to automatically go to the next text field when users press "return" on their keyboard but for some reason my solution doesn't work.
In TableViewController, I do:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomCell
cell?.box.tag = indexPath.row
In my custom table view cell, I have
override func awakeFromNib() {
super.awakeFromNib()
box.delegate = self
...
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.viewWithTag(textField.tag+1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
The issue is that textField.superview?.viewWithTag(textField.tag+1) is always nil. I don't know why because I clearly set the tag and also mark it as a delegate. thank you.
Adding some clarity and more suggestions to the valid answer by #jawadAli, as I feel you are still new to iOS development.
You are trying to get the tableView from the textField. But you will not get it by referring to the superview of textField. Because the view hierarchy would be like this:
UITableView > UITableViewCell > contentView > Your text field.
There can also be some more views in the view hierarchy, so you need to keep traversing through the superview chain till you get the UITableView. And #jawadAli has posted the code on how to get it.
But overall that is an incorrect approach. You should use delegation. I.e. your cell should call a method when it has resigned as first responder. And your table view controller will receive that call.
Then your view controller has to get the next cell and make it the first responder.
And if this doesn't make any sense to you, then I would very strongly suggest that you learn about Delegation. It's ubiquitous in iOS' libraries.
EDIT:
Approach to use delegation.
Create a protocol, let's say CellDelegate that has a function like func didFinishDataCapture(forCell: UITableViewCell).
The cell will have a delegate property of type CellDelegate.
The controller will conform to CellDelegate and will set itself as the cell's delegate in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
Now in your cell, when you are done with the text field (which you would know as cell would be the text field's delegate), you call your own delegate's function i.e. delegate.didFinishDataCapture(forCell: self).
In your implementation of didFinishDataCapture in the controller, you will know which cell has finished with the data capture and can put the logic on what to do next.
It should be nil as textField.superview is your cell class ... and your cell class does not have the view with required Tag .. so it will return nil..
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
guard let view = self.superview as? T else {
return self.superview?.lookForSuperviewOfType(type: type)
}
return view
}
}
Get tableView through this extension like this
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
your function will become
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
if let cell = tableView?.cellForRow(at: IndexPath(row: textField.tag+1, section: 0)) as? CustomCell {
cell.box.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
Related
I have a table view of custom table view cells. Each of the cells has a text field where I can fill in some numeric data. I have set up a delegate method i.e. textFieldDidEndEditing which after editing will add the value entered into a text field into a swift hashtable.
However, I see that some other text field which belongs to another completely different table view cell also now has the same value that I entered.
In order to solve this problem, I tried to add other delegate based text field methods thinking that they should solve the problem at hand. One of the methods I used was the textFieldDidChange method and in that method, I wrote the check that if the text field tag was not the same as the tag of the deliberately edited text field, then I clear the text field out.
func textFieldDidChange(_ textField: UITextField) {
if textField.tag != self.service!.id {
print("CLEARING AFFECTED TEXTFIELD")
textField.text = ""
}
}
I probably used the method the wrong way as it did not have any effect on the problem.
I am adding the code snippets which are involved in the problem at hand:-
BookingServiceChargeViewCell.swift
import UIKit
import PineKit
import SwiftMoment
class BookingServiceChargeViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
//Other variables
let price = TextField(placeholder: "Price")
//Other methods
func layoutContent() {
//function to set the layout of the cell
self.price.font = Config.Font.get(.Regular, size: 13)
self.price.delegate = self
self.price.setValue(UIColor.init(colorLiteralRed: 71/255, green: 72/255, blue: 73/255, alpha: 1.0), forKeyPath: "_placeholderLabel.textColor")
self.price.keyboardType = UIKeyboardType.numberPad
self.price.addDoneButtonOnKeyboard()
self.price.setBottomBorder()
self.price.snp.makeConstraints { (make) in
make.centerY.equalTo(cover)
make.width.equalTo(75)
make.right.equalTo(content.snp.right).offset(-40)
}
}
func configure(_ service: Service, subServices: [Service], index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.subServices = subServices
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
self.price.tag = self.service!.id
self.table.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.subServices.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BookingSubServicesChargeViewCell
cell.configure(self.subServices[indexPath.row], index: indexPath.row, parentView: self.parentView!)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.parentView!.serviceAndCharges[textField.tag] = Int(textField.text!)
print(self.parentView!.serviceAndCharges)
}
}
I am uploading a couple of screenshots showing the problem:-
As you can see, I have entered a numeric value to the text field that is in the 'Daycare' cell
In this screenshot the text field in the 'Walking' cell should not have been edited at all, it should have remained blank like all other text fields except the one I just edited.
How can I solve this problem?
This problem arises when you dequeue your cell with the same identifier. To solve this problem, you have to save the values written in the cell in some data set and while reloading table view in cellForRow method, use that data set to configure your cell.
You can do that by making a delegate in your CustomCell and make your custom cell a delegate of the text field present in your cell and when text field did end editing you can pass those values to your controller via delegate and save those values in the datasource. While loading your tableview use that datasource to configure your cell
I currently have an app which uses UIPickerViews to allow users to select which answer they want for a text field (to avoid spelling mistakes etc).
However, I have found that the UIPickerView isn't really what I want to use because I haven't had great feedback from it when testing.
I have done some research into how to use a UITableView for text field inputs instead, so when the user clicks the Textfield, the user segues to a UITableView with the same options which would be provided by the UIPickerView. Once they click the cell with the option they are looking for it would segue back to the form with the result chosen inside the text field. I thought this would be a better user experience as I could also implement the search to help users narrow down the option they require quicker.
I have been trying to get this to work for a while now, but I'm quite new at coding and haven't been able to crack it yet. I would just like advice on how to approach this? I'm using Swift and the Storyboard to create my app.
Would I need to create a separate ViewController with a UITableView that loads the options and then move the value back to the form once the cell is clicked?
One approach would be to use a table view in separate view controller. lets call it choiceVC and pass data which text field was tapped.
Then send the data back to your form to show what user has selected.
Follow these steps
Detect user tap on text field and segue to choiceVC by implementing this delegate function of UITextField
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
//push your choiceVC and pass data
var textFeildTapped = ""
//note: chooseGameTextField should be text field's outlet
if textField == chooseGameTextField{
textFeildTapped = "games"
}else if textField == chooseActiviyTextField {
textFeildTapped = "activiy"
}
//first set identifier of your view controller by going to identity inspector and setting the value StoryBoard ID
if let controller = storyboard?.instantiateViewController(withIdentifier: "choiceVC") as? ProductDetailVc{
//pass which text field was tapped
controller.choicesToShow = textFeildTapped
navigationController?.pushViewController(controller, animated: true)
}
return true
}
Note: choiceVC should have a variable "choicesToShow" of type string
in viewdidload of choiceVC check the variable
if choicesToShow == "games" {
//populate your table view with games.
}else {
//check for other cases and proceed accordingly
//activiy, console etc
}
Implement didSelect delegate method of UITableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Pass data back to your form using **delegate pattern**. see link in notes below
}
Notes:
See screen shot of setting storyBoard ID
see how to implement table view if you dont know
https://stackoverflow.com/a/33234181/7698092
See how to pass data backward using delegate pattern
https://medium.com/#mayooresan/passing-data-between-viewcontrollers-via-delegate-protocols-4ecde4b167de
If you are looking for an alternative for picker view to select options you can use dropdown like controls Eg.
DropDown
RSSelectionMenu
I hope these libraries can solve your issues, best of luck
I hope this code work for you. it's wroking for me.
First view controller for textfiled form where you want to open tableview.for that use textfield Delegate.
First View Controller
func doSomething(text: UITextField, with data: String) {
text.text = data
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let objGametableVC = self.storyboard?.instantiateViewController(withIdentifier: "GametableVC") as! GametableVC;
objGametableVC.delegate = self
objGametableVC.selectedTextField = textField
if textField == txtActivity{
objGametableVC.tblData.removeAll()
objGametableVC.tblData = ["act1","act2"]
}
else if textField == txtGameName{
objGametableVC.tblData.removeAll()
objGametableVC.tblData = ["gam1","game2"]
}
textField.resignFirstResponder()
self.navigationController?.pushViewController(objGametableVC, animated: true);
}
Second view Controller For tableview show and pass data from second to first controller
Second view Controller
var delegate: Delegate?
var tblData: [String] = [String]()
var selectedTextField:UITextField? = nil
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tblData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tblcell = tableView.dequeueReusableCell(withIdentifier: "gamelblCell", for: indexPath) as! gamelblCell
tblcell.lblName.text = tblData[indexPath.row]
return tblcell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tblcell = tableView.cellForRow(at: indexPath) as! gamelblCell
let data = tblcell.lblName.text
delegate?.doSomething(text: selectedTextField!, with: data!)
self.navigationController?.popViewController(animated: true)
}
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'm making a UIView extension that returns a possible UITableViewCell if that particular instance of UIView is indeed a subview of a UITableViewCell.
The idea is later I can pass that UITableViewCell reference to UITableView's indexPath(for:) method to get the cell's index path.
So if my table view cells contain UITextField, I'm able to identify which cell that text field comes from when UITextFieldDelegate's textFieldDidEndEditing(_ textField: UITextField) method is called.
So far this is what I came up with:
extension UIView {
var tableViewCell: UITableViewCell? {
get {
var view = self
while let superview = view.superview {
if let tableViewCell = superview as? UITableViewCell {
return tableViewCell
}
view = superview
}
return nil
}
}
}
I have 2 questions:
Since I'm new to programming with Swift, may I know if there is a better (Swiftier?) way to write this?
Is this a good way of identifying the index path of a UITableViewCell which contains a UITextField that is being edited? Is there a better way?
I'm actually new both to Swift and Stack Overflow, so sorry if I do something wrong (please be more forgiving) and I wish for your guidance. Thank you.
The cleanest method to do this is simply tag your cell textField.
For example, you can also tag with indexPath.row.
Then in the UITextFieldDelegate method textFieldDidBeginEditing(_:) simply check the tag of the textField that begins editing and you can simply create an NSIndexPath from it.
However, if you have multiple sections and UITextFields in different secitons, you need both section and rowfor the NSIndexPath to be correct.
Depending on how many textFields you have in the tableView, the solution could be to create an NSDictionary to keep a reference to the section and row.
This is an interesting way of figuring out the indexPath, but a safer way might be to use textView's delegate method and figure out the indexPath of the tableViewCell in relation to the tableView.
class TableViewCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
}
class ViewController: UIViewController {
var indexPath: IndexPath?
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else { return UITableViewCell() }
cell.textField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
let point = textField.convert(.zero, to: tableView)
indexPath = tableView.indexPathForRow(at: point)
}
}
These are the alternatives :
1. Find the cell based on point.
func textFieldDidEndEditing(_ sender: UITextField) {
let tableViewTouchPoint:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: tableViewTouchPoint)
}
Create an extension of Tableview for the same by
extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let tableViewTouchPoint:CGPoint = view.convert(CGPointZero, to:self)
let indexPath = self.indexPathForRow(at: tableViewTouchPoint)
return indexPath
}
}
Use it by
let indexPath = self.tableView.indexPathForView(textField)
Based on subview tag. In CellForRowATIndex, set the tags to view equal to indexPath.row. Then, to get back the IndexPath use :
let index = sender.tag
let indePath = IndexPath(row: index, section: 0)
What property can I access to find out?
I was learning some swift from this tutorial, and I decided to give myself some problems to do. One was that in the tutorial, to edit a name, there is a UIAlert. I want to get rid of that and instead, have UITextField in place of the UILabel, so the user could simply tap on a name, and edit it. I would use the textFieldDidEndEditing(textField: UITextField) function to update the model, which is a dictionary of names and picture filenames.
I set the view controller as the UITextFieldDelegate, I put in the function, but now I'm stuck, because although the text was updated just fine in one of the cells, I don't know how to tell which cell it happened in.
In this case, you probably can consider to subclass a UITextField to refer a Dictionary item.
class DictionaryTextField: UITextField {
var item: [String : AnyObject]?
}
Also create a subclass of UITableViewCell to hold above DictionaryTextField as an IBOutlet property.
class TextFieldTableViewCell: UITableViewCell {
#IBOutlet weak var textField: DictionaryTextField!
}
After finished above setting, an item Dictionary of datasource can be set in func tableView(:, cellForRowAtIndexPath: ).
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! TextFieldTableViewCell
cell.textField.delegate = self
let item = items[indexPath.row]
cell.textField.item = item
cell.textField.text = "TextField \(item["name"]!)"
return cell
}
Later, in the UITextFiedDelegate, cast the textField as DictionaryTextField. Then the item can be retrieved directly.
func textFieldDidBeginEditing(textField: UITextField) {
guard let textField = textField as? DictionaryTextField else {
return
}
print("Did begin editing: \(textField.item)")
}
The revised codes can be downloaded again with this link: https://db.tt/8j9ENf7b