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
Related
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
}
I have a collection view in which each cell possess the ability to be interacted with by the user. Each cell has a like button and a number of likes label. When the button is pressed, the button should turn cyan, and the label (which holds the number of likes) should increment. This setup currently works. However, when I scroll through the collection view and scroll back, the button reverts to its original color (white) and the label decrements down to its original value. I have heard of an ostensibly helpful method called prepareForReuse(), but perhaps I'm not using it correctly. Here is my code:
Here is the array which holds all the cells
var objects = [LikableObject]()
Here is the class definition for these objects
class LikableObject {
var numOfLikes: Int?
var isLikedByUser: Bool?
init(numOfLikes: Int, isLikedByUser: Bool) {
self.numOfLikes = numOfLikes
self.isLikedByUser = isLikedByUser
}
}
Mind you, there is more functionality present in this object, but they are irrelevant for the purposes of this question. One important thing to be noted is that the data for each cell are grabbed using an API. I'm using Alamofire to make requests to an API that will bring back the information for the numOfLikes and isLikedByUser properties for each cell.
Here is how I load up each cell using the collection view's delegate method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ObjectCell", for: indexPath) as! ObjectCell
cell.configureCell(
isLikedByUser: objects[indexPath.row].isLikedByUser!,
numOfLikes: objects[indexPath.row].numOfLikes!,
)
return cell
}
The ObjectCell class has these three fields:
var isLikedByUser: Bool?
#IBOutlet weak var numOfLikes: UILabel!
#IBOutlet weak var likeBtn: UIButton!
And that configureCell() method, which belongs to the cell class, is here:
public func configureCell(numOfLikes: Int, isLikedByUser: Bool) {
self.isLikedByUser = isLikedByUser
self.numOfLikes.text = String(numOfLikes)
if isLikedByUser {
self.likeBtn.setFATitleColor(color: UIColor.cyan, forState: .normal)
} else {
self.likeBtn.setFATitleColor(color: UIColor.white, forState: .normal)
}
}
And lastly, the prepareForReuse() method is here:
override func prepareForReuse() {
if isLikedByUser! {
self.likeBtn.setTitleColor(UIColor.cyan, for: .normal)
} else {
self.likeBtn.setTitleColor(UIColor.white, for: .normal)
}
}
This doesn't work. And even if it did, I still don't know a way to keep the numOfLikes label from decrementing, or if it should anyway. I'm speculating that a big part of this problem is that I'm not using the prepareForReuse() method correctly... Any help is appreciated, thank you.
prepareForReuse is not the place to modify the cell, as the name states, you "only" have to prepare it for reuse. if you changed something (for example isHidden property of a view), you just have to change them back to initial state.
What you should do though, you can implement didSet for isLikedByUser inside the cell, and apply your modifications to likeBtn in there. (this is of-course the fast solution)
Long solution: It's an anti-pattern that your cell has a property named isLikedByUser, TableViewCell is a View and in all architectures, Views should be as dumb as they can about business logic. the right way is to apply these modifications in configure-cell method which is implemented in ViewController.
If you feel you'll reuse this cell in different viewControllers a lot, at least defined it by a protocol and talk to your cell through that protocol. This way you'll have a more reusable and maintainable code.
Currently all of this is good , the only missing part is cell reusing , you have to reflect the changes in the number of likes to your model array
class ObjectCell:UICollectionViewCell {
var myObject:LikableObject!
}
In cellForRowAt
cell.myObject = objects[indexPath.row]
Now inside cell custom class you have the object reflect any change to it , sure you can use delegate / callback or any observation technique
The prepareForResuse isn't needed here.
You do need to update the model underlying the tableview. One way to verify this is with mock data that is pre-liked and see if that data displays properly.
I have a custom UITableViewCell that contains a UISwitch and a UILabel. When the switch has been tapped, I would like to know which cell has had their switch activated/deactivated.
I have the following code in my cellForRowAtIndexPathMethod to add the target to my cell's UISwitch:
[cell.notifSwitch addTarget:self action:#selector(switchChangedFromCell:) forControlEvents:UIControlEventValueChanged];
Here is the code for the selector:
- (void)switchChangedFromCell:(id)sender {
HydrationNotificationTableViewCell *cell = (HydrationNotificationTableViewCell *)sender;
if ([cell.notifSwitch isOn]) {
NSLog(#"Notification for %# has been activated", cell.notifLabel.text);
}
else {
NSLog(#"Deactivated notification %#", cell.notifLabel.text);
}
}
Right off the bat I believe that it is wrong of me to cast the sender as a cell, since the sender really is the UISwitch from the cell. I would like to know how I can pass the cell itself as well so I know which cell has been changed.
One solution is to set a different tag for each UISwitch in my cellForRowAtIndexPath method, but I was wondering if there was a cleaner solution.
The bad news is that you can't do that... exactly.
The good news is that in your IBAction the UISwitch is the sender, and you can use that to figure out which cell contains the UISwitch.
I have a project on Github called TableViewExtension that shows how to do it.
The only real trick is this extension to UITableView:
public extension UITableView {
/**
This method returns the indexPath of the cell that contains the specified view
- Parameter view: The view to find.
- Returns: The indexPath of the cell containing the view, or nil if it can't be found
*/
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
If you add that extension you can then make your IBAction use the sender to figure out which control triggered the tap, and call that method to figure out which cell contains that control:
#IBAction func controllTriggered(_ sender: UISwitch) {
guard let indexPath = tableView.indexPathForView(sender) else { return }
//Now you have the indexPath of the cell containing `sender`
}
Edit:
Note that in your code you are trying to get the cell, and then query the different subviews of the cell to get state data. Don't do that. You should be storing your state data in your model object. The cell is for displaying information and interacting with the user.
If the user taps a switch the IBAction will have the switch as it's sender parameter. You can get the value of the Switch from sender.
I believe you can only pass the sender in a selector method like that.
You could consider having the UISwitch method be in your custom UITableViewCell class, and create a delegate protocol for your custom class that passes the cell to your ViewController.
I came up with another solution but this is also a bit ugly. I casted the cell to the sender's superview's superview (UISwitch >> Content View >> Cell)
HydrationNotificationTableViewCell *cell = (HydrationNotificationTableViewCell *)([senderSwitch superview].superview);
Hopefully there's a cleaner solution out there.
I have a UITableView containing dynamic cells added by user. Each cell has one UILabel and one UISwitch. The default state of switch is ON. I save these values in a CoreData model (UILabel as String & UISwitch as Bool) whenever user adds it.
I would like to update/store the state of switch corresponding to value of label in the same cell in CoreData model whenever user toggles the switch. I have added tags (switch.tag = indexPath.row) to each switch in row and can access it by using sender.tag in my cellChanged function. But I am not able to fetch label of that row in which switch has been toggled. If I can somehow fetch value of label corresponding to the switch toggled then I can run a for loop to look for that label value in CoreData model and then subsequently set state of UISwitch. Since I am using Swift language, I will appreciate if you can explain code using the same.
This worked for me:
func switchTapped(sender:UISwitch) {
let switchPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(switchPosition)
}
A better pattern can be creating a protocol in cell. something like
#protocol MyCellDelegate{
func cellSwitchChanged(forCell: MyCell, toValue: Bool, withLabel: String?)
}
class MyCell: UITableVeiwCell{
weak veer delegate: MyCellDelegate
.
.
.
#IBaction handleSwitchChanged(sender: UISwitch){
self.delegate?.cellSwitchChanged(forCell: self, toValue: sender.value, withLabel: self.lblName.text)
}
}
And in you cell for row an indexPath, assign you vc to be delegate of the cell. and override this method.
So every time the switch changes, the view controller will get the cell reference, switch value and the text label. you can then do your core data update operation here.
I have implemented an in-line UIDatePicker in a UITableView just like they appear in other iOS apps, for example when creating a new event in Calendar.
While using the app with VoiceOver, the date picker is still accessible after the user selected a date and then closed the date picker by double tapping when on the table view cell. When the date picker hides, VoiceOver highlights the appropriate cell, but if the user then swipes to the right it will select the now hidden UIDatePicker and allow them to interact with it. I expected it to select the next table view cell like it does in Calendar.
I have read on other SO questions that hidden views are still accessible, and in order to prevent that from occurring, you can set the property accessibilityElementsHidden to YES on the container view after you hide the element to let VoiceOver know it's no longer on screen. But this did not work for me. I also read you should post a layout change notification so VoiceOver knows to update to the current UI, but that too didn't work and the hidden view is still accessible.
How can I prevent my hidden UIDatePicker from being accessible? Note that when the table loads, the hidden date picker is not accessible. So only after it appears then hides is it still accessible.
This is my code to hide the date picker when the user taps the cell:
self.datePicker.hidden = YES;
self.datePicker.alpha = 0.0f;
self.datePickerCell.accessibilityElementsHidden = YES;
self.datePickerCell.contentView.accessibilityElementsHidden = YES;
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
And in heightForRowAtIndexPath the datePickerCell height is changed to 0 upon hide. Note that the datePicker is a subview of the datePickerCell.
I tried to replicate your scenario on my own, and came up with something that works well with accessibility. The most important decision in my opinion was to use an NSLayoutConstraint for the height of the Date Picker.
This is an overview of the Storyboard I designed:
Static Tableview Storyboard
And this is what I did in the ViewController:
#IBOutlet weak var pickerHeightConstraint: NSLayoutConstraint!
var showPicker = false
#IBOutlet weak var datePickerView: UIDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
pickerHeightConstraint.constant = 0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
showPicker = !showPicker
datePickerView.alpha = showPicker ? 1.0 : 0.0
pickerHeightConstraint.constant = showPicker ? 224 : 0
tableView.beginUpdates()
tableView.endUpdates()
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 1 {
return pickerHeightConstraint.constant
}
return UITableViewAutomaticDimension
}
Note that I didn't had to mess around with UIAccessibility properties such as accessibilityElementsHidden. Just use the constant property of the Layout Constraint to manipulate the date picker view element.
You can find a project where I tested this here. A Gif can be found in the README so you can quickly see a short demo - my reputation is not enough for more links.
self.datePicker.isAccessibilityElement = FALSE;
That should hide it from Accessibility.
remove the datePicker or use another cell without datepicker
I would suggest you check a variable as accessible, so:
#property(nonatomic,assign) BOOL isAccessible;
and in viewDidLoad set property to accessible = NO;
self.isAccessible = NO;
and in UITableViewDelegate :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if(self.isAccessible)
{
//Do your date picker functionality
}
//For else u dont need cuz it will do no function if isAccessible is set to NO;
}