Getting Ordered TextField Data From Dynamically Created Cells in Swift (3.1) Table View - swift

I am trying to figure out how to get textfield data from cells in a tableview in an ordered fashion. So far I am able to type into each textfield a retrieve the type data. To do so in the correct order though the user must edit the first cell...last cell, any other way and the data isn't in the correct order.
For Example:
I have the program create 5 cells with a textfield,
textfield1: I typed here second
textfield2: I typed here fourth
textfield3: I typed here first
textfield4: I typed here fifth
textfield5: I typed here third
the way I currently have it my dataArray would look identical to this one, because it is being stored based on when it is typed in and not the order of the cells.
I would like to type the above example, but my data come out like this:
textfield1: I typed here first
textfield2: I typed here second
textfield3: I typed here third
textfield4: I typed here fourth
textfield5: I typed here fifth
Here is my textfield editing code:
#IBAction func TitleEditBegin(_ sender: UITextField) {
}
#IBAction func TitleEditEnd(_ sender: UITextField) {
print(sender.tag) // Debug
titleArray.append(sender.text!)
}
I know for the time being that any other changes will be appended to the titleArray, but I want to solve the ordering issue first.
Thanks!
EDIT: I forgot to add in how I am creating the cells, the code is below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = TitleSessionTableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath) as! TitleSessionCell
cell.SessionTitleLabel.text = "Title"
// cell.SessionTitleField.text = "Default"
cell.SessionTitleField.tag = indexPath.row
cell.SessionTitleField.delegate = self
print(indexPath.row) // Debug
return cell
}
EDIT 2: Adding where I define the text fields.
import Foundation
import UIKit
class TitleSessionCell: UITableViewCell{
#IBOutlet weak var SessionTitleField: UITextField!
#IBOutlet weak var SessionTitleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}

The easiest way would be to use a dictionary I believe. I would assign all of the textFields a different tag. So for textField1 you would say textField1.tag = 1. I don't see the creation of your text fields, so it is hard to show the best way of adding that in, but then after handling that, I would create the dictionary as a class variable.
var textFieldDictionary: [Int: String] = [:]
and then add in the text to it like so:
if sender.text != nil && sender.text != "" {
textFieldDictionary[sender.tag] = sender.text
}
then when you want to retrieve the information, do something like this:
for i in 0..<biggestTextFieldNumber {
if let text = textFieldDictionary[i] {
//do something with text
print(text)
}
}
or you could just grab the specific number values out whenever you needed them by using:
textFieldDictionary[numberYouWant]
I hope this helps!

Related

Protocol Doesn't Send Value to Other VC

That is my footerView called FooterTableViewCell. I have this protocol called SurveyAnswerTableViewCellDelegate. It's parent is AddQuestionViewController.
When I tap on the footerView I trigger #IBActtion.
#objc protocol SurveyAnswerTableViewCellDelegate: AnyObject {
func textSaved(_ text: String)
}
class FooterTableViewCell: UITableViewHeaderFooterView {
var parentVC: AddQuestionViewController!
#IBAction func addNewTapped(_ sender: Any) {
print("tapped")
let newTag = model.tag + 1
parentVC.addNewAnswer()
}
This button action triggers AddQuestionViewController
class AddQuestionViewController: SurveyAnswerViewDelegate, UITextFieldDelegate, UITableViewDelegate, SurveyAnswerTableViewCellDelegate {
var answers: [SurveyAnswerModel] = []
var savedText : String = ""
static var delegate: SurveyAnswerTableViewCellDelegate?
I try creating an empty string and append a new answer to my array. But this text here is always "".
func addNewAnswer() {
let newAnswer = SurveyAnswerModel(answer: savedText, tag: 0)
self.answers.append(newAnswer)
self.tableView.reloadData()
}
func textSaved(_ text: String) {
savedText = text
}
The textfield I try to read is inside SurveyAnswerTableViewCell while setting up the cell inside the tableview I call setup function.
class SurveyAnswerTableViewCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
weak var delegate: SurveyAnswerTableViewCellDelegate?
var parentVC: AddQuestionViewController!
func setup() {
if let text = self.textField.text {
self.delegate?.textSaved(textField.text!)
}
}
extension AddQuestionViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SurveyAnswerTableViewCell
cell.parentVC = self
cell.setup()
return cell
}
How can I successfully send that text to AddQuestionViewController so it appends a new answer with correct string
There are a few things keeping this from working.
You are calling SurveyAnswerTableViewCell's setup() function directly after dequeuing the cell for reuse. It has not yet (re)appeared on the screen at that point, so the user has not had a chance to enter anything into the text field.
You don't currently set the delegate property of SurveyAnswerTableViewCell to anything, so even if the textfield had valid input, the delegate would be nil and delegate?.textSaved(textField.text!) wouldn't do anything.
Both of the previous points mean that the value of AddQuestionViewController .savedText never gets updated from the empty string. So when addNewAnswer() tries to read it, it will always see that empty string.
Rather than reading the text field when the cell is dequeued, it would make more sense to save the text field value when the user is done typing.
To do that, conform the cell to UITextFieldDelegate and implement the textFieldDidEndEditing(_:) method. From within that method you can then call the delegate method you already have to save the text. Make sure the delegate property on the cell has been set by the VC, or else this won't do anything!
The VC itself should not have a delegate property of type SurveyAnswerTableViewCellDelegate. It serves as the delegate, rather than having one. If this doesn't quite make sense, I would recommend reviewing some online resources on the delegate pattern.
So make sure the ViewController conforms to SurveyAnswerTableViewCellDelegate and then set the cell's delegate value to the VC. The cellForRowAt function should then look something like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SurveyAnswerTableViewCell
cell.delegate = self
return cell
}
As a side note, neither the footer nor the cell should have a reference to the parent view controller. as a general rule it is good to avoid subviews being aware of their parent views. Things get unnecessarily complicated when there is two-way knowledge sharing between components, and it makes the subview much less reusable. I would recommend making a delegate for the footer as well, and removing the parentVC property from both the footer and the cell.
Here's what it looks like is happening:
Button tapped
addNewTapped(_:) invoked
addNewAnswer() invoked
newAnswer is appended to answers
tableView.reloadData() invoked
Cells are regenerated with new/empty textfields (so delegate.textSaved is never invoked)
so I'm not sure what you're trying to do, but here's what I figure are a couple possible routes:
store UITextFields separately and add them into table cells so they're not removed by a table reload
conform AddQuestionViewController to UITextFieldDelegate and set it as the textfields' delegate to observe textfield texts changing (and if you're only using 1 textfield, you could set savedText there)

How to check if a tableview contains certain text?

I have an app that allows users to save different quotes. The data gets saved in a user account using Firebase. If a user wants to see what quotes they saved, they can do so in the app as well. They simply click a button, which goes to a separate view controller and displays all saved quotes in a table view. The issue I'm having is that the same quote can be saved multiple times. So, I would like to be able to have the app check the content of the tableview and, if a quote is already saved in the table, change the save button to a saved button. Any thoughts on how to go about this?
This is what I use to save to Firebase:
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesViewController: UIViewController {
var ref: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
configureItems()
ref = Database.database().reference()
#IBAction func saveButton(_ sender: UIButton) {
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").childByAutoId().setValue(quotesLabel.text!)
}
}
To retrieve data:
import UIKit
import FirebaseDatabase
import FirebaseAuth
class SavedQuotesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var ref: DatabaseReference?
var databaseHandle: DatabaseHandle?
var postData = [String]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
ref = Database.database().reference()
databaseHandle = ref?.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").observe(.childAdded, with: { (snapshot) in
let post = snapshot.value as? String
if let actualPost = post {
self.postData.append(actualPost)
self.tableView.reloadData()
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return postData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell")
cell?.textLabel?.text = postData[indexPath.row]
cell?.textLabel?.numberOfLines = 0
return cell!
}
}
As jnpdx said in their comment, you are thinking about this wrong. The table view is a View object. It doesn't store data, it presents it to the user and provides an interface for the user to interact with that data.
You want a model of some kind to store your data. Your Firebase database might be that model, or you might load your records into a model object in memory. I assume you've already made those decisions. (I haven't used Firebase, so I don't know if it's fast enough to serve up data for a table view or not. I suspect not. In order to support smooth scrolling, you want to be able to serve up cells as fast as possible.)
You need a way to tell if your quote is unique. As Claude suggested in his comment, you might want to set up your quote string in Firebase to be hashed/indexed. If you do that testing for uniqueness will be really fast. When the user enters a new quote, you could query the database with the quote string (or it's hash) and see if a record already exists with that hash. If it does, you could update the UI to tell the user that the quote already exists.

Text is jumbled in custom cell

I'm trying to read data from CoreData into a custom cell. This is just a test app before I try moving to the real app that I've been working on. The data is there - I can print it to the console and see it. For some reason, even with constraints, all of the data is laid on top of each other in the cell. Can anyone see what's going on with this?
I've created constraints to keep the cells where they should be, but when the data is loaded from my 'show data' button, the data is laid on top of each other.
Here is my custom cell class:
import UIKit
class CustomCellClass: UITableViewCell {
#IBOutlet weak var txtNameLabel: UILabel!
#IBOutlet weak var txtAgeLabel: UILabel!
}
Here is the ShowData class: (partial)
class ShowData: UIViewController, NSFetchedResultsControllerDelegate {
#IBOutlet weak var personTableView: UITableView!
let appDelegate = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var personData = [Person]()
// Read the data
override func viewDidLoad() {
super.viewDidLoad()
personTableView.delegate = self
personTableView.dataSource = self
loadItems()
}
func loadItems() {
let request : NSFetchRequest<Person> = Person.fetchRequest()
do {
personData = try appDelegate.fetch(request)
} catch {
print("couldn't load")
}
}
}
extension ShowData : UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return personData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let person = personData[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "personNameAge", for: indexPath) as! CustomCellClass
cell.txtNameLabel?.text = person.name
cell.txtAgeLabel?.text = String(person.age)
return cell
}
Here is a screenshot of the tableview while running:
Edit:
I just deleted the app from the simulator and tried to rerun - now there isn't any data in the cells.
Just to clarify for other readers, as can be seen in your screenshot, the rows in your table are separated as expected but the different fields in each cell, what one might call the columns, are on top of one another.
You say that you have created constraints to keep the cells where they should be, I'm not sure what you mean by that. What you need is constraints for the fields within each cell – what I call intra-cell constraints. Either you have not added these constraints, or there is a mistake in them which causes all fields to be drawn at the left.
To show you what I mean, let's use the example of a little workout app of mine which has, in each table cell, from left to right, a Perform button, an Edit button, a Name field and a Duration field. The screenshot below shows, in the big yellow box, the intra-cell constraints. If you are using a storyboard, the problem with your app must be in that area. If you are not using a storyboard, the problem must be in the equivalent code (or lack of it).
Just to let everyone know. The issue is resolved. I removed the table view cell from the project, readded, and readded the constraints. Everything is working now. I'm not sure where the problem was, but I noticed I had weird wrapping happening. I moved one of the labels to the other side of the cell and constrained it to the right side, and the other to the left side. When I ran the app, the text appeared to word wrap. I decided to delete the cell and readd and relink my outlets. It worked the first time...

How do I get the row number of the cell where the UITextView was edited?

I have a UITextView inside each cell of a UITableView
I am using Core Data to save data which is typed in the UITextView
I would like to save the text typed in UITextView once the user is done editing it
I have added UITextViewDelegate to my TableViewCell class
I am using Notifications to post the new text to the TableViewController
I am able to get the new text to the TableViewController but I don't know how to get the row number of the cell that contained the textview wherein the text was typed. I need to know the row number (or the object in that row) to update the correct NSManagedObject.
What I have Tried:
I was thinking about using tags but since I need to constantly add and delete rows it wouldn't be the best approach
I have tried using DidSelectedRowAtIndexPath but it doesn't get triggered while the
user taps the UITextView (UITextView covers up to 80% of the one cell)
In the TableViewCell class, I have:
func textViewDidEndEditing(_ textView: UITextView) {
// Post notification
let userInfo = [ "text" : textView.text]
NotificationCenter.default.post(
name: UITextView.textDidEndEditingNotification,
object: nil,
userInfo: userInfo as [AnyHashable : Any])
}
In the TableViewController, I have:
//Subscribe
NotificationCenter.default.addObserver(self,
selector: #selector(textEditingEnded(notification:)),
name: UITextView.textDidEndEditingNotification,
object: nil)
#objc func textEditingEnded(notification:Notification) {
guard let text = notification.userInfo?["text"] as? String else {return}
print ("text: \(text)")
}
Don't hesitate to ask for more details.
I'll appreciate every bit of help I can get!
Create a property of the NSManagedObject type in the table view cell.
In Interface Builder connect the delegate of the text view to the cell.
In the controller pass the appropriate data source item in cellForRowAt to the cell.
Delete the observer and instead of posting a notification change the attribute in the NSManagedObject instance directly and save the context.
As NSManagedObject instances are reference types the changes will persist.
I hope you can have variable inside your UITableViewCell subclass for certain item
var item: Item?
then in cellForRowAt set certain item for certain cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
...
cell.item = array[indexPath.row]
...
}
now you can implement UITextViewDelegate to your cell subclass and you can use method textViewDidEndEditing for handling when user is done with typing
class YourCell: UITableViewCell {
...
var item: Item?
...
override func awakeFromNib() {
yourTextView.delegate = self
}
}
extension YourCell: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
... // here save text and here you can use variable `item`
}
}

Swift3 Multiple buttons dynamic coding to trigger event

I have multiple buttons. Each button contains a language label.
I want to make it so that when users tap on the button, the selected language label changes its value according to the button tapped.
The selected language outlet is called SelectedLangText.
A simple solution would be to create multiple Action outlets for each button and set the value of SelectedLangText label. However, if there would be 100 buttons, that would be bad coding.
I'm not sure how to approach this situation in Swift 3 coming from web development.
I prefer using the delegate design pattern when it comes to solving an issue like that for it I find it to be a much cleaner approach than just a mass amount of #IBActions
1- Create a Language class
import Foundation
class Language {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
2- Create the custom cell in the storyboard or nib and then add the appropriate outlets and actions. And then you create a Delegate protocol for it
import UIKit
protocol CustomCellDelegate: class {
func customCell(newLanguageSelected language: Language)
}
class CustomCell: UITableViewCell {
var language: Language!
#IBOutlet weak var languageTextLabel: UILabel!
weak var delegate: CustomCellDelegate?
func setupCustomCell(withLanguage language: Language){
self.language = language
self.languageTextLabel.text = self.language.name
}
#IBAction func buttonPressed(sender: UIButton){
delegate?.customCell(newLanguageSelected: self.language)
}
}
3- Finally add the implementation in the cellForRow method of the UITableViewDataSource and add the implementation of the delegate in the UITableViewController class
import UIKit
class YourTableViewController: UITableViewController{
var languages: [Language] = []
//implement the other methods in the dataSource
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuse", for: indexPath) as! CustomCell
cell.delegate = self
cell.setupCustomCell(withLanguage: languages[indexPath.row])
return cell
}
}
extension YourTableViewController: CustomCellDelegate{
func customCell(newLanguageSelected language: Language) {
//Do what you will with the language
}
}
Good luck
You can add Tags to each button and Set same IBAction for all buttons.
First in your method get the language based on the button.
Now use loop(go with for loop as you will need proper index for each button) and get all buttons from its tag and set language tag.
A bit complex at glance, but will solve your problem and good solution in my eye.
for index in 101...103 {
let myBtn = self.view.viewWithTag(index) as! UIButton
myBtn.setTitle("localisedtitle string", for: .normal)
}