I have a tableview consisting of 2 cells and each cell has a textfield. I'd like to hold the text in textfield in an another variable and append it into an array but it just save the second cell's textfield's text into the variable and doesn't append the array.
Here is cellForRowAt code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == queryTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: queryCellReuseIdentifier,
for: indexPath) as! queryCell
cell.selectionStyle = UITableViewCell.SelectionStyle.gray
queryTextField = UITextField(frame: CGRect(x: 20, y: 0, width: 300, height: 20))
queryTextField.delegate = self
queryTextField.placeholder = "Soruyu buraya giriniz"
queryTextField.font = UIFont.systemFont(ofSize: 15)
cell.addSubview(queryTextField)
return cell
}
Here is my related function :
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = queryTextField.text!
queryArray.append(temp)
print(temp)
}
if your project is non-storyboard approach, then I would say, it would be more easier and flexible to take control of each type and data and action since sometimes things not quite flexible with storyboard only.. . as you create, even a single UIElement, UI conforms to those properties, methods and actions which you define, means fully customized code - custom defined code base..
now to your question, according to what you did, probably, your textfield text is overwritten to blank, losing the previously entered value every time the cell is dequeued, cell is generated fresh again for use every time " dequeue cell " method is executed, if you want to persist your previous value, I would say, " remind the dequeued cell that it had some value before being re- dequeued again " means:
declare your global array or dict.
dequeue your cell and setup your cell properties
3 after cell is dequeued, do something like this, and your cells previous value is saved even after the cell is dequeued.
example, common approach:
struct DefaultInfo {
var string: String?
var type: SomeData? //optional
}
let array: [DefaultInfo] = []
var array = self.array[indexPath.row]
// after cell is dequeued:
switch array.type {
case .someCase:
let string = cell.textField.text
array.string = string
cell.textField.text = array.string
//print(array.string!)
// now you should be able to see your input after cell is
//renewed.. . try it.. .
default: break
}
It seems like you have an independant variable queryTextField which is overriden when you create the second cell.
Also, in textFieldDidEndEditing try accessing textField instead of queryTextField, like this:
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = textField.text!
queryArray.append(temp)
print(temp)
}
Responding to this:
I need every cell has its textfield and I need to be able to save
their texts into an array. After that I am going to send them to web
service. To sum up, they will be my parameters.
You don't need to have global queryTextField just for that. You can remove this variable, if that's its only goal.
Do you need to send web request on some trigger? Like button tap. I assume, yes.
Since theoretically not all your cells are visible at the same time (e.g. when thay do not fit the screen), it's bad idea to try and track texts in text fields. Instead, you need some kind of model to store all texts (paired with indeces, maybe). The simplest would be dictionary where key is cell identifier (e.g. indexPath) and value is the text.
In textFieldDidEndEditing you can report the changes to the view controller. For this you need to assign the cell as the delegate for its text field. And view controller - as the delegate for the cell. In textFieldDidEndEditing cell would be calling view controller delegate method to report the text change, passing (for example) self and text as parameters. View controller would then be able to find its index path and store the text in the model (dictionary).
On trigger (button click?) view controller will be able to build parameters from the model (dictionary).
Consider this pseudocode as a direction:
cellForRow {
...
cell = ...
let textField = ...
textField.delegate = cell
cell.addSubview(textField)
cell.delegate = self
}
In cell:
textFieldDidEndEditing(textField: UITextField) {
delegate?.report(text: textField.text ?? "", in: self)
}
In view controller:
report(text: String, in cell: UITableViewCell) {
let indexPath = tableView.indexPath(of: cell)
model[indexPath] = text
}
Related
I am working with DiffableDataSource inside of a collection view for the first time, but I am running into an error while trying to update a view.
So I am working with 2 custom cells. CustomCell1 is a cell that has a Label on the left and a UITextField on the right. CustomCell2 is a cell that has 2 column UIPickerView inside of it. CustomCell2 has a delegate that will let my main view controller know when it has been updated. This is all working fine.
So the problem is coming when I am trying to update the textfield in CustomCell1 with the value selected in the UIPickerView from CustomCell2. As a note, the textfield is being used for text entry in rows of the page. And the picker view is shown when a cell is clicked, and hidden when the cell is clicked again.
So in my delegate to update the cell, I have this code:
func updateSelectedCharacter(withCharacter character: String, indexPath: IndexPath) {
print(character)
DispatchQueue.main.async {
guard let charItem = self.dataSource.itemIdentifier(for: IndexPath(row: indexPath.row - 1, section: indexPath.section)), let parentId = charItem.parentId else { return }
self.updatePlayerData(forPlayerIndex: parentId, character: character)
var snap = self.dataSource.snapshot()
snap.reloadItems([charItem])
self.dataSource.apply(snap)
}
}
And in my cellForRowAt, I have the following code to update the text field in the character cell (CustomCell1). The gameInfo object is a struct that has information, including the character name I am trying to display, that I will eventually store off into CoreData from the entries in the cells.
guard let parentId = info.parentId else { return UICollectionViewListCell() }
let playerData = self.gameInfo.playerData[parentId]
var obj = TextEntryCellInformation(title: rowTitle, rowType: info.rowType)
obj.value = obj.value = playerData?.character
obj.isSelectable = false
return collectionView.dequeueConfiguredReusableCell(using: textEntryCell, for: indexPath, item: obj)
And here is the cell registration for that cell, where TextEntryCellInformation holds the row type, title for the label, and an optional value that can set textfield's text:
private func createTextEntryListCellRegistration() -> UICollectionView.CellRegistration<TextEntryCollectionViewCell, TextEntryCellInformation> {
return UICollectionView.CellRegistration<TextEntryCollectionViewCell, TextEntryCellInformation> { cell, indexPath, data in
var config = TextEntryContentConfiguration()
config.title = data.title
config.tag = data.rowType.rawValue
config.textChangedDelegate = self
config.isSelectable = data.isSelectable
config.customPlaceholder = "required"
if let val = data.value {
config.textValue = val
}
cell.contentConfiguration = config
}
}
So now, I think I have explained most of the code. Whenever I select the value in the picker view, the first time it will show the value correctly in the character cell. But if I change the value, the text seems to disappear from the character cell. However, I am getting the correct value from the picker view, verified with the print call at the start of the delegate function. I am also verifying that after I apply in the update delegate function, I am running through the cell registration and the correct value is being assigned to the config.textValue. I am not sure why then, if the config is being data is being set as expected with the correct character name, why the UI is not updating to show that information.
I included what I think is the relevant code and information. However, if more is needed, I will definitely update.
Thanks in advance for any help given!
Turns out I found the answer. When setting the configuration on the content view of the textfield custom cell (CustomCell1), I was doing check to conform to equatable:
static func == (lhs: TextEntryContentConfiguration, rhs: TextEntryContentConfiguration) -> Bool {
return lhs.title == rhs.title
}
I needed to be doing:
static func == (lhs: TextEntryContentConfiguration, rhs: TextEntryContentConfiguration) -> Bool {
return lhs.textValue == rhs.textValue
}
This checks the difference to know if I should be updating the config values or not.
Successes so far: I have a remote data source. Data gets pulled dynamically into a View Controller. The data is used to name a .title and .subtitle on each of the reusable custom cells. Also, each custom cell has a UISwitch, which I have been able to get functional for sending out both a “subscribe” signal for push notifications (for a given group identified by the cell’s title/subtitle) and an “unsubscribe” signal as well.
My one remaining issue: Whenever the user "revisits" the settings VC, while my code is "resetting" the UISwitches, it causes the following warnings in Xcode 9.2:
UISwitch.on must be used from main thread
UISwitch.setOn(_:animated:) must be used from main thread only
-[UISwitch setOn:animated:notifyingVisualElement:] must be used from main thread
The code below "works" -- however the desired result happens rather slowly (the UISwitches that are indeed supposed to be "on" take a good while to finally flip to "on").
More details:
What is needed: Whenever the VC is either shown or "re-shown," I need to "reset" the custom cell’s UISwitch to "on" if the user is subscribed to the given group, and to "off" if the user is not subscribed. Ideally, each time the VC is displayed, something should reach out and touch the OneSignal server and find out that user’s “subscribe state” for each group, using the OneSignal.getTags() function. I have that part working. This code is in the VC. But I need to do it the right way, to suit proper protocols regarding threading.
VC file, “ViewController_13_Settings.swift” holds a Table View with the reusable custom cell.
Table View file is named "CustomTableViewCell.swift"
The custom cell is called "customCell" (I know, my names are all really creative).
The custom cell (designed in XIB) has only three items inside it:
Title – A displayed “friendly name” of a “group” to be subscribed to or unsubscribed from. Set from the remote data source
Subtitle – A hidden “database name” of the aforementioned group. Hidden from the user. Set from the remote data source.
UISwitch - named "switchMinistryGroupList"
How do I properly set the UISwitch programmatically?
Here is the code in ViewController_13_Settings.swift that seems pertinent:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
// set cell's title and subtitle
cell.textLabelMinistryGroupList?.text = MinistryGroupArray[indexPath.row]
cell.textHiddenUserTagName?.text = OneSignalUserTagArray[indexPath.row]
// set the custom cell's UISwitch.
OneSignal.getTags({ tags in
print("tags - \(tags!)")
self.OneSignalUserTags = String(describing: tags)
print("OneSignalUserTags, from within the OneSignal func, = \(self.OneSignalUserTags)")
if self.OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
}, onFailure: { error in
print("Error getting tags - \(String(describing: error?.localizedDescription))")
// errorWithDomain - OneSignalError
// code - HTTP error code from the OneSignal server
// userInfo - JSON OneSignal responded with
})
viewWillAppear(true)
return cell
}
}
In the above portion of the VC code, this part (below) is what is functioning but apparently not in a way the uses threading properly:
if OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
It's not entirely clear what your code is doing, but there seems to be a few things that need sorting out, that will help you solve your problem.
1) Improve the naming of your objects. This helps others see what's going on when asking questions.
Don't call your cell CustomTableViewCell - call it, say, MinistryCell or something that represents the data its displaying. Rather than textLabelMinistryGroupList and textHiddenUserTagName tree ministryGroup and userTagName etc.
2) Let the cell populate itself. Make your IBOutlets in your cell private so you can't assign to them directly in your view controller. This is a bad habit!
3) Create an object (Ministry, say) that corresponds to the data you're assigning to the cell. Assign this to the cell and let the cell assign to its Outlets.
4) Never call viewWillAppear, or anything like it! These are called by the system.
You'll end up with something like this:
In your view controller
struct Ministry {
let group: String
let userTag: String
var tagExists: Bool?
}
You should create an array var ministries: [Ministry] and populate it at the start, rather than dealing with MinistryGroupArray and OneSignalUserTagArray separately.
In your cell
class MinistryCell: UITableViewCell {
#IBOutlet private weak var ministryGroup: UILabel!
#IBOutlet private weak var userTagName: UILabel!
#IBOutlet private weak var switch: UISwitch!
var ministry: Ministry? {
didSet {
ministryGroup.text = ministry?.group
userTagName.text = ministry?.userTag
if let tagExists = ministry?.tagExists {
switch.isEnabled = false
switch.isOn = tagExists
} else {
// We don't know the current state - disable the switch?
switch.isEnabled = false
}
}
}
}
Then you dataSource method will look like…
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! MinistryCell
let ministry = ministries[indexPath.row]
cell.ministry = ministry
if ministry.tagExists == nil {
OneSignal.getTags { tags in
// Success - so update the corresponding ministry.tagExists
// then reload the cell at this indexPath
}, onFailure: { error in
print("Error")
})
}
return cell
}
Okay, I'm going to try to break this down as simply as I am able. I have a tableView in a ViewController. I have two prototype cells for the table. I am reusing the cells multiple times to populate the table.
In one of the cells, I've added gesture recognizer to the label through which I'm making a textField visible on place of the label and hiding the label. Now I want the labels text to change to what I've entered in the textField when I'm done using the textField and hit the return key. So i implemented the UITextFieldDelegate protocol in the viewController. I've also added tags to each of the textFields in the cell so that I know what textField is returning and what row the textField is in.
Basically, what I want to know is if there is any way to get the indexPath if I already know the indexPath.row?
For the gesture recognizers, i was able to work around this issue by getting the indexPath from the tapped location:
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
let indexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(indexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
I need the indexPath so that I can refer to the elements contained within a cell. Can anyone offer any insights? Has anybody tried a similar approach? Is there any way I can access the cell by just using the row?
If you are able to get the indexPath inside the GestureMethod then you can create one instance property of type NSIndexPath store its value inside that Gesture's method and later used the indexPath inside textFieldShouldReturn delegate method, something like this.
var selectedIndexPath: NSIndexPath?
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
self.selectedIndexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(self.selectedIndexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
Now use this self.selectedIndexPath inside UITextFieldDelegate method.
Edit: From your question's comment you have told that you have just one Section so you can also create indexPath from that textField's tag this way.
func textFieldShouldReturn(textField: UITextField) -> Bool {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
//Or You can use self.selectedIndexPath also
}
In case of single or multiple sections, the below code will work
In your cellForRowAtIndexPath, set the tag as below:-
let tag = indexPath.section*100 + indexPath.row
cell.savedUserInput.tag = tag
cell.userDetailTextfield.tag = tag
In your textfield delegate method, get the indexPath as follows:-
func genderTapped(sender: UITapGestureRecognizer) {
let textfieldObject = sender as! UITextField
let sectionTag = textfieldObject.tag % 100
let rowTag = textfieldObject.tag / 100
let indexPath = NSIndexPath(forRow: rowTag.tag, inSection: sectionTag)
}
Disclaimer: This is not an answer to the literal question asked here, but it might provide an simpler solution to OP's goal.
Unless you need to do something in addition to what you described in your question it seems to me that a much easier solution would be not to use labels at all but in stead just use an UITextField and set it's enabled property to false when you want it to act like an label.
You can subclass the UITextField if you need the styling to change when the mode changes.
If you know the row number which you are accessing and the section in which the row is, then use this code
let indexPath = NSIndexPath(forRow: row, inSection: section)
For accessing the cell corresponding to this indexPath, use
let cell = tableView.cellForRowAtIndexPath(indexPath) as! tableViewCell
I have a custom UICollectionViewCell that I am attempting to pass a value to from my view controller. I'm able to pass an image to the cell, but anything else comes up nil upon initialization.
Relevant code in the View Controller:
override func viewDidLoad() {
self.collectionView!.registerClass(MyCustomCell.self, forCellWithReuseIdentifier: "Cell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! MyCustomCell
cell.someValue = 5
cell.imageView.image = UIImage(named: "placeholder.png")
return cell
}
In the custom cell class:
var someValue: Int!
var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView(frame: CGRectMake(0, 0, frame.width, frame.height))
contentView.addSubview(imageView)
let someValueLabel = UILabel()
someValueLabel.frame = CGRectMake(0, 75, 200, 30)
someValueLabel.text = "Value is: \(someValue)"
self.addSubview(someValueLabel)
}
The image is successfully passed from the UICollectionView and I am able to display it, but 'someValue' is always nil.
What am I doing wrong?
The init method is called much earlier than you think it is -- within the dequeue process -- when the cell object is constructed. Part of initialization process is to attach the UIViews designed in Storyboard. So that image works because the UIImageView is already in place as a container during the Storyboard (NIB) loading process, and you're just setting its internal image property later.
You have correctly set the value of someValue for all future use, during cell rendering and event handling. So, for example, if there's an #IBAction handler that runs after the cell is displayed and tapped on, it will indeed have access to someValue. That's where your test print should go. What are you ultimately using someValue for?
FOLLOWUP
So it's a simple error; you just need to set the text value in cellForRowAtIndexPath. You don't need a copy of model data in the cell (i.e. no need to have a someValue field in your cell), either. Just configure the UI dynamically from your (properly separated) model data:
instead of:
cell.someValue = 5
You just need, e.g.:
cell.someValueLabel.text = "\(indexPath.row)" // or where ever you're getting your underlying model data from
It's a misconception to use init for any of this. The only responsibility of init for table cells is to allocate memory. A cell is a completely dynamic, temporary object, and all of its properties that reflect Application data must be set in the cellForRowAtIndexPath method. The visual rendering of the cell waits for the cellForRowAtIndexPath method to finish, so there's no timing problem.
Init method is called when the UICollectionView is instantiated. You're logging "someValue" in the init method and it's too early. Image is rendered since you're working to the ImageView directly that has already been instantiated. Try to log imageView.image in the init method, it should be nil too (or maybe not nil because the cell is reused).
You should make your job in custom variables setters and getters, where you're sure that they're not nil.
var someValue: Int!{
didSet {
print("Passed value is: \(newValue)")
}
}
You are setting the value of someValue after the cell has been initialized.
You are calling print("Passed value is: \(someValue)") during the initialization process.
Set a break point on the init method of your cell class. You should see it pass through there before you assign the value 5 to that variable.
i have a custom uitableviewcell with a label and a textfield now i want to pass the data from the textfield when it is changed to the uitableviewcontroller.
The tableview is populated with data from a sqlite database that is wrapped with fmdb and the number of tableviewcells is different in every db i use in the app. How many fields there are and what name they have is stored in the db, the population of the table works fine.
I managed to get a "EditingDidEnd" event from the textfield in the cells to my view controller, the event is used to activate a toolbar button for saving changes. I did this via the cell.fieldData.addTarget(self, action: "SaveButtonOn:", forControlEvents: UIControlEvents.EditingDidEnd)
on creating the Cells in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath).
My problem is now that i need to get the data from the textfields inside the cells. I have found some code and tutorials for passing parameters to the via addTarget() but only for buttons and i don't know if that is usable for textfields.
If you need more information just ask, i hope you can decipher what i need to do.
Thanks for any answers
Adarkas
Use delegation with your UITextField:
import UIKit
class MyViewController: UIViewController, UITextFieldDelegate {
var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField = UITextField(frame: CGRectMake(0, 0, 100, 30))
myTextField.delegate = self
}
func textFieldDidEndEditing(textField: UITextField) {
// Do stuff here.
}
}
If properly set, any textField you have calls the delegate-method textFieldDidEndEditing and from there on you can do with its content (textField.text) what you want.
To get value from a UITextFeild in a specific cell of UITableView:-
Make sure you set a tag for your UITextFeild first, it's better you set tag with indexpath.row like cell.textFld.tag=indexpath.row
Now write the following code to retrieve value from textfeild:-
let indexPath = NSIndexPath(forRow:row, inSection:0) //just provide the row number from where you want to fetch the textfeild
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
(cell.contentView.viewWithTag(2) as UITextFeild).text // access the value of textfeild by its tag with this way