textFieldDidEndEditing is not called in swift - swift

I have a textfield in my tableview cell. When I am entering something and click on the submit button, I want to save the text in a struct to use it in other view controller. But the problem is , when I am clicking the submit button for the first time, my textFieldDidEndEditing function is not calling so I am unable to assign the value to the struct.
My code is-
func textFieldDidEndEditing(_ textField: UITextField)
{
let position: CGPoint = textField.convert(CGPoint.zero, to: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: position)
{
if let cell: InsuranceInformationTableViewCell = self.tableView.cellForRow(at: indexPath) as? InsuranceInformationTableViewCell{
if textField == cell.policyHolderFullNameTextField{
insuranceModel.policuHolderFullName = textField.text
}
}
}
}
SubmitButtonAction-
#IBAction func SubmitButtonAction(_ sender: UIButton) {
if (insuranceModel.policuHolderFullName == nil) {
showAlert(withTitle: "Data Missing", withMessage: "Policy holder fullName field cannot be empty")
return
}
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(insuranceModel) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "SavedInsuranceInformation")
self.navigationController?.pushViewController(StockAndBillPreviewViewController.instantiateStockAndBillPreviewViewController(), animated: true)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
case TableSection.insuranceType.rawValue:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "InsuranceInformationTableViewCell", for: indexPath) as? InsuranceInformationTableViewCell else {
return UITableViewCell()
}
cell.policyHolderFullNameTextField.delegate = self
}
When I am clicking on the submit button it is showing me the error message.
Here i don't want to use "UITextField, shouldChangeCharactersIn range". Becausei have lots of textfields in my tableview cell as it is a medical form.
How to resolve this issue ? Can anyone help me?

1.one of most common reason this might happen is that you might not be assigning the text view delegate method.
So just add "UITextViewDelegate" as conformance to your table View Cell and then set the delegate in storyboard.
2. if you have done the first method and it is still not working then you can follow the most basic way to get whatever there is in text field when you click your submit button.
at anypoint in your code you can access the text in textfield through "textfield.text",
just create an outlet of your text field and use youroutletname.text in submit button to save in struct.

I am not sure that textFieldDidEndEditing is calling in this case. I use the following approach:
(1) define custom cell with UITextView and make cell delegate for UITextViewDelegate textViewDidChange method
(2) create custom cell delegate protocol to send text changes down to TableViewController
(3) make TableViewController delegate for custom cell and record all changes for UITextView text inside the controller

Related

How to make sure a line of code is executed after another is completed?

I have a viewControl called PostViewController which has a UITableView of posts. I also have a class called PostCell which defines the UITableViewCell. I made a button function in PostCell called likeButtonClicked to favour a post similar to twitter.
#IBAction func likesButtonClicked(_ sender: Any) { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "likeButtonClicked"), object: nil, userInfo: ["cell":self, "likesButton":likesButton!]) }
This is to pass the cell indexPath and the button name to PostViewController. I need indexPath to increase the likes by 1 and the button name to change its image to pink when post is favoured.
I then subscribed to the notification in viewDidLoad of PostViewController.
NotificationCenter.default.addObserver(self, selector: #selector(postLiked), name: NSNotification.Name(rawValue: "likeButtonClicked"), object: nil)
I then wrote this function in the same viewController
#objc func postLiked(notification: Notification){
if let cell = notification.userInfo?["cell"] as? UITableViewCell{
let likesButton = notification.userInfo?["likesButton"] as? SpringButton
if let indexPath = postsTableView.indexPath(for: cell){
let post = posts[indexPath.row]
postId = post.id
PostAPI.getPostById(postId: postId) { post in
//Check if the same post were already favoured.
if !self.liked || self.oldPostId != self.postId{
self.newLikes = post.likes + 1
self.liked = true
self.oldPostId = self.postId
}else{
self.newLikes = self.newLikes - 1
self.liked = false
}
PostAPI.favourPost(postId: self.postId, likes: self.newLikes) {
PostAPI.getPostById(postId: self.postId) { postResponse in
let post = postResponse
self.posts[indexPath.row] = post
let cellNumber = IndexPath(row: indexPath.row, section: indexPath.section)
self.reloadRowData(cellNumber: cellNumber){
if !self.liked{
likesButton?.tintColor = .systemPink
}else{
likesButton?.tintColor = .darkGray
}
}
}
}
}
}
}
}
func reloadRowData(cellNumber: IndexPath, completion: #escaping () -> ()) {
self.postsTableView.reloadRows(at: [cellNumber], with: .none)
completion()
}
Please tell me why the last 4 lines of postLiked function is executed before reloadRowData function, which causes the button to change its color to pink then returns immediately to gray when it should stay pink.
Any help will be most appreciated.
Thank you.
I expect the specific problem is your call to reloadRows. As the docs note:
Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.
So this is likely creating an entirely new cell, and then the later code modifies the old cell that is being removed from the table. (So you see the change, and then the cell is removed and replaced.)
I would start by getting rid of the entire reloadRowData call.
Generally, though, this code is fragile, and I'd redesign it. The cell should take care of setting the tint colors itself based on the data. You generally shouldn't be reaching into a cell and manipulating its subviews. This will cause you a problem when cells are recycled (for example, when this scrolls off screen). All the configuration should be done in cellForRow(at:), and the cell should observe its Post and update itself when there are changes.
Data should live in the Model. The View should observe the Model and react. The Model should not reach into the View and manipulate anything.
As a side: your reloadRowData looks async, but it's not. There's no reason for a completion handler. It could just call return.
A table view's .reloadRows(at:...) (and .reloadData()) functions are async processes.
So your reloadRowData() func is returning before the table view actually reloads the row(s).
This is a rather unusual approach - both in using NotificationCenter for your cells to communicate with the controller, and in trying to change the button's tint color by holding a reference to the button.
The tint color really should be set in cellForRowAt, based on your data source.
Edit
My description of table view data reloading being asynchronous was misleading.
The point I wanted to make was this...
If I change my data, call .reloadData(), and then change my data again:
// set myData values
myData = ["You", "Say", "Hello"]
// call .reloadData here
tableView.reloadData()
// change myData values
myData = ["I", "Say", "Goodbye"]
the table view will not display "You Say Hello" even though we call .reloadData() when those are the values of myData.
Here's a complete, simple example to demonstrate:
class TestTableViewController: UITableViewController {
var myData: [String] = ["Some", "Song", "Lyrics"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "c")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath)
c.textLabel?.text = myData[indexPath.row]
return c
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// call my func when a row is selected
self.myReload()
}
func myReload() {
// set myData values
myData = ["You", "Say", "Hello"]
// call .reloadData here
tableView.reloadData()
// change myData values
myData = ["I", "Say", "Goodbye"]
// table view doesn't reload itself we exit this func
// so we'll get
// I
// Say
// Goodbye
}
}
So, among the other issues described by Rob Napier in his answer, your original code was trying to change the tint color of an object in a cell before the table reloaded its data.

Delegate method, textViewDidChangeSelection not triggered everytime

I have a UITextView embedded in a UITableView Cell, CustomCell. If I tap the return key when my curser is at the end of the text, I want to create a new cell below that cell, but if I tap return when I am in the middle of the textView, I want to get rest of the words after the curser and create a cell with that text in the textView.
To do that I need to know curser position in textView which I am getting with the help of the UITextViewDelegate method textViewDidChangeSelection(_:).
It works only when I tap on different location on the same cell but if I move to a different cell and tap again on a previously tapped cell(in the textview)at the same position, the delegate method doesn't get triggered.
Let me show the case with an exapmle-
I type some texts for testing. Here, after the 5th line, I decide to tap on the 2nd line just before the word "juice" like -
The delegate method, textViewDidChangeSelection(_:) gets triggered.
Now, I click on the 3rd line after the whole text "Skim Milk" like-
and again the delegate method, textViewDidChangeSelection(_:) gets called.
But now when I move back to the 2nd line at the same position, meaning before the word “juice”, the delegate method doesn’t get triggered. Again if I tap on the 3rd line at the same position meaning after the word “milk”, the delegate method doesn't get called.
I think, the reason is in those cells, I tapped on the sane position, and as the selection didn’t get changed, the delegate method didn’t get called.
Following is the code for the delegate method which is in the CustomCell(UITableViewCell) class-
func textViewDidChangeSelection(_ textView: UITextView){
if let range = textView.selectedTextRange, let indexPath = currentIndexPath{
let curserStartPosition = textView.offset(from: textView.beginningOfDocument, to: range.start)
setActiveCell?(indexPath, curserStartPosition)
}
}
The following is the code for the Controller where new cells are created when tapped the return key-
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var cellCount = 1
var initialCellText: String?
var activeCell: CustomCell?
var cursorPosition: Int?
#IBOutlet weak var myTableView: UITableView!{
didSet{
myTableView.delegate = self
myTableView.dataSource = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
func goToNextCell(_ indexPath:IndexPath){
let nextCelIndexPath = IndexPath(row: indexPath.row + 1, section: 0)
if let text = activeCell?.myTextView.text, let cursorPos = cursorPosition, cursorPos < text.count-1 {
let fromIndex = text.index(text.startIndex, offsetBy: cursorPos)
let toIndex = text.index(text.endIndex, offsetBy: -1)
if fromIndex < toIndex {
let truncatedText = text[fromIndex...toIndex]
self.initialCellText = String(truncatedText)
}
}
cellCount += 1
myTableView.beginUpdates()
myTableView.insertRows(at: [nextCelIndexPath], with: .none)
myTableView.endUpdates()
initialCellText = nil
cursorPosition = nil
if let cell = self.myTableView.cellForRow(at: nextCelIndexPath) as? CustomCell{
self.activeCell = cell
self.activeCell?.myTextView.becomeFirstResponder()
}
}
func setActiveCell(on indexPath: IndexPath, at curserPos: Int){
if let tappedOnCell = myTableView.cellForRow(at: indexPath) as? CustomCell{
self.activeCell = tappedOnCell
self.cursorPosition = curserPos
}
}
}
// MARK: - UITableViewDelegate and UITableViewDataSource
extension MyViewController{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomCell{
cell.myTextField.becomeFirstResponder()
cell.returnKeyTapped = goToNextCell(_:)
cell.setActiveCell = setActiveCell(on:at:)
return cell
}
return UITableViewCell()
}
}
To create the following cell, I need to know where the cursor position is on a particular cell everytime. So, I need the delegate method to be triggered everytime.
Can anyone have any suggestions as to how can I solve my problem.
If I understand you right, you basically want to know the curser position after you have typed into a UITextView.
Now, if you type into a UITextView, that text view becomes the first responder, and begins an editing session. This is announced by a UITextView.textDidBeginEditingNotification for which you can add an observer, see here.
In the function called by the notification, you could compute the cursor position as you do in textViewDidChangeSelection

Swipe to Like on UITableViewCell not working as expected - Swift

I'm using a UISwipeGestureRecognizer to detect a swipe for a cell in a UITableViewCell, similar to THIS LINK which will allow the user to 'Like' a photo.
The problem is that I dont quite understand how to change the Like value for that specific post - and it doesn't have an indexPath like other 'built-in' methods. I also don't understand how it knows to use the cell that is showing predominantly on the screen, since there might be more than one cell that has not yet been "dequeued"?:
#objc func mySwipeAction (swipe: UISwipeGestureRecognizer) {
switch swipe.direction.rawValue {
case 1:
print ("the PostID you selected to LIKE is ...")
case 2:
print ("the PostID you selected to Undo your LIKE is ...")
default:
break
}
}
and my tableView looks like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postTopContributions", for: indexPath) as! PostTopContributions
let postImage = postImageArray [indexPath.row]
let imageURL = postImage.postImageURL
cell.delegate = self
cell.postSingleImage.loadImageUsingCacheWithUrlString(imageURL)
cell.postSingleLikes.text = "\(postImageArray [indexPath.row].contributionPhotoLikes)"
cell.postSingleImage.isUserInteractionEnabled = true
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.mySwipeAction(swipe:)))
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.mySwipeAction(swipe:)))
leftSwipe.direction = UISwipeGestureRecognizerDirection.left
rightSwipe.direction = UISwipeGestureRecognizerDirection.right
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
let selectedCell = self.postImageArray [indexPath.row]
return cell
}
I don't want to use the native TableView row swipe left to delete methods - for various UX purposes in this specific case.
You can try
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
cell.postSingleImage.tag = indexPath.row
Don't recommend adding gestures inside cellForRowAt , you may add
them inside init for programmatic cells or awakeFromNib for xib /
prototype cells
#objc func mySwipeAction (swipe: UISwipeGestureRecognizer) {
let index = swipe.view.tag
let selectedCell = self.postImageArray[index]
switch swipe.direction.rawValue {
case 1:
print ("the PostID you selected to LIKE is ...")
// edit dataSource array
case 2:
print ("the PostID you selected to Undo your LIKE is ...")
// edit dataSource array
default:
break
// reload table IndexPath
}
}
You can pass your indexpath as a parameter in your selector. and then add the like at yourArray[indexpath.row]
You can set the tag of the cell image that you are adding the GestureRecognizer to the indexPath row of the cell itself:
cell.postSingleImage.tag = indexPath.row
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
Then, you can determine which cell triggered the GestureRecognizer by getting the view's tag that triggered the swipe gesture:
#objc func mySwipeAction (gesture: UISwipeGestureRecognizer) {
let indexPathRow = gesture.view.tag
let indexPath = IndexPath(row: indexPathRow, section: 0) // assuming this is a 1 column table not a collection view
if let cell = tableView.cellForRow(at: indexPath) as? PostTopContributions {
// ... and then do what you would like with the PostTopContributions cell object
print ("the PostID you selected to LIKE is ... " + cell.id)
}
}
Hope that this helped!

How to add a subview to a UITableViewCell

I'm making a to-do planner using a table view. When the user presses enter after typing in a task, a button should appear to the left of the task.
The textFieldShouldReturn method in my ViewController class adds the button but not always in the top left corner of the screen. I have it set up like this:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
self.view.addSubview(cell.cellButton) // puts a button in a wrong location, button doesn't respond to any action
cell.addSubview(cell.cellButton)
tableViewData.append(textField.text!)
lastItem = tableViewData.count
print(tableViewData)
print(lastItem)
self.tableView.reloadData() //update row count and array count
textField.resignFirstResponder()
return true
}
self.view.addSubview(cell.cellButton)
adds a button to the view but the button doesn't appear in the right place, (i tried moving the location using storyboard, but it doesn't do anything). Plus the button doesn't respond to its appropriate click action, which is declared in the subclass.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
cell.textField.delegate = self
...
return cell
}
Any help is appreciated. Let me know if I am not too clear
You're not grabbing the right cell in this line:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
You're basically telling the system to just give you a cell. You need to figure out what text field actually returned, and grabbing the cell that contains that text field and then adding your subview to the right cell. The way todo this would be to tag your textFields with the row number.
It works in the first example because you are in cellForRow, so you do have the right cell.
Would it be easier if you aligned the button in your .xib or storyboard file, and then kept it hidden until you needed it to appear?

Setup inputView for different UITextFields in UITableView

I have UITableView with UITextFields in custom cells.
For UITextField I use custom inputView:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! Item
cell.price.text = ""
cell.price.delegate = self
return cell
}
func textFieldDidBeginEditing(textField: UITextField) {
textField.inputView = keyboardView
itemPrice = textField
}
func numberWasTapped(number: String!) {
itemPrice.insertText(number!)
}
With delegate method:
#IBAction func keyTaped(sender: UIButton) {
switch sender.tag {
case 112:
if !isCommaWasPressed {
isCommaWasPressed = true
sender.enabled = false
self.delegate?.numberWasTapped(sender.titleLabel!.text)
}
break
default:
self.delegate?.numberWasTapped(sender.titleLabel!.text)
}
My custom inputView is the calculator keyboard with the all logic in the Keyboard class: it controls if comma was pressed and how many numbers was entered after the comma.
For example, if comma was pressed, then commaButton.enabled = false in Keyboard class.
I couldn't figure out how to set the commaButton.enabled = true in Keyboard class when I'm switching to another UITextField.
I tried textField.reloadInputViews() in
textFieldDidBeginEditing(_:) method but it doesn't work.
I think, I should get the information from UITextField and pass it to Keyboard's class logic. But I don't know how to do it.
I found the solution.
I moved keyboard logic to main ViewController and no need to pass textField data to Keyboard class.