send indexpath to Firebase (like button) - ios

I have a table view and the cells are populated with data from Firebase. In each cell there is a like button, when I like a button in a particular cell, it captures the ID of that cell and creates a node in Firebase to let me know the button was clicked (liked). Before the button is clicked, it is white and after it is clicked it turns red. Then if it is clicked again (unliked) it turns white.
#IBAction func LikeClicked(_ sender: UIButton) -> Void {
let LikedRef = FIRDatabase.database().reference().child("Likes").child((self.loggedInUser?.uid)!)
let indexPath = self.selectedIndex
let post = self.posts![(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
let cell = TableView.cellForRow(at: indexPath!) as! ProfileTableViewCell
if cell.Like.currentImage == #imageLiteral(resourceName: "icons8-Hearts Filled-50 (2)"){
cell.Like.setImage(#imageLiteral(resourceName: "icons8-Heart-50"), for: .normal)
// cell.RedLike.isHidden = true
FIRDatabase.database().reference().child("Likes").child((self.loggedInUser?.uid)!).child(self.key!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}else{
}})
} else{
LikedRef.observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
var LikeStatus = postsDictionary[self.key!] as? String ?? ""
if self.key == LikeStatus
{
// cell.Like.isHidden = true
cell.Like.setImage(#imageLiteral(resourceName: "icons8-Hearts Filled-50 (2)"), for: .normal)
}
}})
LikedRef.updateChildValues([self.key!: self.key!])
}
}
cell.Like.addTarget(self, action: #selector(LikeClicked), for: UIControlEvents.touchUpInside)
cell.Like.tag = indexPath.row
print(indexPath.row)
cell.Like.isUserInteractionEnabled = true
My problem is when I like one button on a specific cell, all the like buttons in every cell turns red. but I only want the cell I click to turn red and when I leave the app and come back all the buttons are back to being white. I want whatever button the logged in user likes to remain red regardless of if the user exits the app or not.

Okay so I spent an hour or so to just give you an idea how you can do what you need to do. Liking and unliking from your custom UITableViewCell. I've explained in each line of code I made the details. I hope this helps you out. Let me know if you have questions. Remember this is just one of the many ways you can do your task.
MyViewController.swift
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MyCustomCellDelegate {
// This is the array of keys that we
var likedDataKeys = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Load here the 'Likes' stuff and store its in a datasource for reference or store as well its keys.
// If data is liked, store to likedDayaKeys the key of your data.
FirebaseCall {
if liked {
self.likedDataKeys.append(keyOfYourData)
}
}
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ....
// Set the image.
let dataKey = yourDatasource[indexPath.row] // get the key or whatever data you need
// Set the delegate and key
cell.delegate = self
cell.dataKey = dataKey
if likedDataKeys.contains(dataKey) {
cell.image = redImageLike
} else {
cell.image = whiteNormalLikeImage
}
return cell
}
// MARK: - MyCustomCellDelegate
func myCustomCell(userDidTapLikeWithDataKey dataKey: String) {
// So now we can get the dataKey of the cell that is being liked or unliked.
// Check from the self.likedDataKeys if the tapped cell is liked or not.
if self.likedDataKeys.contains(dataKey) {
// If it is there, then we should call the unlike Firebase.
// Also remove it from the self.likedIndexPath and reload the tableView to update the image.
let index = self.likedDataKeys.index(of: dataKey)
self.likedDataKeys.remove(at: index)
// Call now the unlike Firebase.
} else {
// If it is not there, then we should call the like Firebase.
// Also store it to the self.likedIndexPAth
}
}
}
MyCustomCell.swift
protocol MyCustomCellDelegate: NSObjectProtocol {
// This is the delegate that will help us send the dataKey reference to the viewController
// Whenever the user taps on the like button in the cell.
func myCustomCell(userDidTapLikeWithDataKey dataKey: String)
}
class MyCustomCell: UITableViewCell {
// This will be called in the viewController, pass here the self of the viewController
weak var delegate: MyCustomCellDelegate?
// Make sure to pass here the key from the cellForRow of the viewController's tableView delegate.
var dataKey = ""
#IBAction func LikeClicked(_ sender: UIButton) -> Void {
// Call the delegate to inform the viewController
self.delegate?.myCustomCell(userDidTapLikeWithDataKey: self.dataKey)
}
}

Related

UITableView reload after navigation controller back not showing value changes

I have a table view where I am getting data from Firebase. The table is in a view controller that is part of a navigation controller. When coming to the view from it's parent view, the table data always displays correctly. When I go to the detail view when I click a row and come back, the changes I have made to my data are not showing even though the data values are the new values.
I am loading the table in viewWillAppear so that I can reload the data when coming back from the detail view controller. I am using DispatchQueue.main.async since the UI code is in a completion handler from the Firebase Call. I can set breakpoints and see that the data is being updated from FB and the table is reloading all the rows.
The specific issue I am having is an image, which is hidden by default, should no longer be hidden based on a data value from FB. I can step in the debugger and see that the data says not to hide the image, the image.isHidden = false is called, yet the image doesn't appear. All of my UI calls are wrapped in DispatchQueue.main.async if they are called from a closure. The image will show if I come from the parent view controller to the VC with the tableview, but when going back from the detail VC it doesn't show.
According to the code it should be working when stepping through in the debugger, the data values are correct and the image is being set to hidden/not hidden correctly.
Here is the ViewController Class that has the TableView
override func viewDidLoad() {
super.viewDidLoad()
}
// We want the table to reload every time the page appears
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
RouteService.instance.getRouteByIdFromFirebase(id: route.id) { [weak self] (route) in
guard let self = self else {return}
self.route = route
// Initialize the ViewModel as the table view delegate
self.routeDetailsVM.initialize(route : route,routeDetailsVC : self)
self.segmentsTableView.dataSource = self.routeDetailsVM
self.segmentsTableView.delegate = self.routeDetailsVM
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.segmentsTableView.reloadData()
}
}
}
The UITableViewCell Class
class TravelSegmentTableViewCell: UITableViewCell {
static let cellHeight : CGFloat = 100
static let identifier = "TravelSegmentTableViewCell"
#IBOutlet weak var completedImage: UIImageView!
func initToModel(segment : Segment, routeDetailsVM : RouteDetailsVM) {
setCompletedImageVisible(segment: segment,completedImage : completedImage)
}
func setCompletedImageVisible(segment : Segment,completedImage : UIImageView) {
if (segment.status == .finished) {
// This is getting called correctly but when coming
// back from the details VC it's not showing the image
// When coming from parent VC it works
completedImage.isHidden = false
}
}
UITableViewDelegate Class
// Cell for Row at Index Path Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let segment = route.segments[indexPath.row]
let type = segment.type
switch (type) {
case .pickup :
if let cell = tableView.dequeueReusableCell(withIdentifier: PickupSegmentTableViewCell.identifier, for: indexPath) as? PickupSegmentTableViewCell {
cell.initToModel(segment: segment,routeDetailsVM: self)
return cell
} else {
let cell = PickupSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
case .delivery :
if let cell = tableView.dequeueReusableCell(withIdentifier: DeliverySegmentTableViewCell.identifier, for: indexPath) as? DeliverySegmentTableViewCell {
cell.initToModel(segment: segment,routeDetailsVM: self)
return cell
} else {
let cell = DeliverySegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
// This is the cell type in question
case .travel :
if let cell = tableView.dequeueReusableCell(withIdentifier: TravelSegmentTableViewCell.identifier, for: indexPath) as? TravelSegmentTableViewCell {
cell.initToModel(segment: segment, routeDetailsVM: self)
} else {
let cell = TravelSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
default :
return UITableViewCell()
}
print("RouteDetailsVM:cellForRowAt indexPath ERROR - Returning blank UITableViewCell for type = \(type)")
return UITableViewCell()
}
The reason for the behavior is if you look inside cell for row at index path and at the .travel switch, I never return the tableViewCell that I dequeued. The code would fall through to the bottom and return a default UITableViewCell instead of the TravelSegmentTableViewCell
Here is what the code should look like
....
case .travel :
if let cell = tableView.dequeueReusableCell(withIdentifier: TravelSegmentTableViewCell.identifier, for: indexPath) as? TravelSegmentTableViewCell {
cell.initToModel(segment: segment, routeDetailsVM: self)
// THIS IS WHAT WAS MISSING
return cell
} else {
let cell = TravelSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
default :
return UITableViewCell()
}
return UITableViewCell()
}

Swift: Set Variables for TableViewCell

I have a tableView and my tableViewCell has a isLiked variable and a likeButton. I would like to set my likeButton's tintColor accordingly to the state of isLiked. The way I have done it is that I retrieve a list of posts from Firebase that the user has liked, and then pass it into the tableView. If the postID tallies with the list, I would like to set the isLiked variable to true.
However, despite setting this logic in cellForRow, the isLiked variable in tableViewCell is not set accordingly. How can I get it set accordingly? My code as follows:
// in TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
for likedPosts in usersLikedPostsList {
if likedPosts == postID {
DispatchQueue.main.async {
cell.isLiked = true
}
} else {
cell.isLiked = false
}
}
return cell
}
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool?
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
print(isLiked)
if let _ = isLiked {
likeButton.tintColor = .red
} else {
likeButton.tintColor = .darkGray
}
}
}
From the print statement, isLiked variable is nil when the table is loaded. I have attempted with self.tableView.reloadData() but it does not set it accordingly as well.
Note that I would like to manipulate isLiked state in TableViewCell because I wanna add code to toggle its state within TableViewCell, so that users can like and unlike the post, and perform the updates to Firebase accordingly as well.
The cell awakeFromNib() method is executed only once, when the first time the cell is instanciated from storyboard or Nib file, so you need to update your cell every time this value isLiked change, try with his code
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool = false{
didSet{
likeButton.tintColor = .darkGray
if(isLiked)
{
likeButton.tintColor = .red
}
}
}
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Hope this helps you
let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
Try your above code in view did load and make postID as global variable.
and print to see if it returns anything or use .count to check it.
If it has count more than 1.Then try to use postID in method :cellForRowAt
Secondly, tableview reload will work only if numberOfRowsInSection have different value from last time.
Check whether this method is called or not when you reload table.

Properly delegate button action from custom Cell to delete rows in UITableView

Still very much a Swift noob, I have been looking around for a proper way/best practice to manage row deletions in my UITableView (which uses custom UserCells) based on tapping a UIButton inside the UserCell using delegation which seems to be the cleanest way to do it.
I followed this example: UITableViewCell Buttons with action
What I have
UserCell class
protocol UserCellDelegate {
func didPressButton(_ tag: Int)
}
class UserCell: UITableViewCell {
var delegate: UserCellDelegate?
let addButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add +", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(addButton)
addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true
addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
addButton.heightAnchor.constraint(equalToConstant: self.frame.height / 2).isActive = true
addButton.widthAnchor.constraint(equalToConstant: self.frame.width / 6).isActive = true
}
func buttonPressed(_ sender: UIButton) {
delegate?.didPressButton(sender.tag)
}
}
TableViewController class:
class AddFriendsScreenController: UITableViewController, UserCellDelegate {
let cellId = "cellId"
var users = [User]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
cell.delegate = self
cell.tag = indexPath.row
return cell
}
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
where the Users in users are appended with a call to the database in the view controller.
My issues
The button in each row of the Table View is clickable but does not do anything
The button seems to be clickable only when doing a "long press", i.e. finger stays on it for a ~0.5s time
Will this method guarantee that the indexPath is updated and will not fall out of scope ? I.e. if a row is deleted at index 0, will deleting the "new" row at index 0 work correctly or will this delete the row at index 1 ?
What I want
Being able to click the button in each row of the table, which would remove it from the tableview.
I must be getting something rather basic wrong and would really appreciate if a Swift knight could enlighten me.
Many thanks in advance.
There are at least 3 issues in your code:
In UserCell you should call:
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
once your cell has been instantiated (say, from your implementation of init(style:reuseIdentifier:)) so that self refers to an actual instance of UserCell.
In AddFriendsScreenController's tableView(_:cellForRowAt:) you are setting the tag of the cell itself (cell.tag = indexPath.row) but in your UserCell's buttonPressed(_:) you are using the tag of the button. You should modify that function to be:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
delegate?.didPressButton(self.tag)
}
As you guessed and as per Prema Janoti's answer you ought to reload you table view once you deleted a row as your cells' tags will be out of sync with their referring indexPaths. Ideally you should avoid relying on index paths to identify cells but that's another subject.
EDIT:
A simple solution to avoid tags being out of sync with index paths is to associate each cell with the User object they are supposed to represent:
First add a user property to your UserCell class:
class UserCell: UITableViewCell {
var user = User() // default with a dummy user
/* (...) */
}
Set this property to the correct User object from within tableView(_:cellForRowAt:):
//cell.tag = indexPath.row
cell.user = self.users[indexPath.row]
Modify the signature of your UserCellDelegate protocol method to pass the user property stored against the cell instead of its tag:
protocol UserCellDelegate {
//func didPressButton(_ tag: Int)
func didPressButtonFor(_ user: User)
}
Amend UserCell's buttonPressed(_:) action accordingly:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
//delegate?.didPressButton(self.tag)
delegate?.didPressButtonFor(self.user)
}
Finally, in your AddFriendsScreenController, identify the right row to delete based on the User position in the data source:
//func didPressButton(_ tag: Int) { /* (...) */ } // Scrap this.
func didPressButtonFor(_ user: User) {
if let index = users.index(where: { $0 === user }) {
let indexPath = IndexPath(row: index, section: 0)
users.remove(at: index)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Note the if let index = ... construct (optional binding) and the triple === (identity operator).
This downside of this approach is that it will create tight coupling between your User and UserCell classes. Best practice would dictate using a more complex MVVM pattern for example, but that really is another subject...
There is a lot of bad/old code on the web, even on SO. What you posted has "bad practice" written all over it. So first a few pointers:
Avoid an UITableViewController at all cost. Have a normal view controller with a table view on it
Delegates should always be weak unless you are 100% sure what you are doing
Be more specific when naming protocols and protocol methods
Keep everything private if possible, if not then use fileprivate. Only use the rest if you are 100% sure it is a value you want to expose.
Avoid using tags at all cost
The following is an example of responsible table view with a single cell type which has a button that removes the current cell when pressed. The whole code can be pasted into your initial ViewController file when creating a new project. In storyboard a table view is added constraint left, right, top, bottom and an outlet to the view controller. Also a cell is added in the table view with a button in it that has an outlet to the cell MyTableViewCell and its identifier is set to "MyTableViewCell".
The rest should be explained in the comments.
class ViewController: UIViewController {
#IBOutlet private weak var tableView: UITableView? // By default use private and optional. Always. For all outlets. Only expose it if you really need it outside
fileprivate var myItems: [String]? // Use any objects you need.
override func viewDidLoad() {
super.viewDidLoad()
// Attach table viw to self
tableView?.delegate = self
tableView?.dataSource = self
// First refresh and reload the data
refreshFromData() // This is to ensure no defaults are visible in the beginning
reloadData()
}
private func reloadData() {
myItems = nil
// Simulate a data fetch
let queue = DispatchQueue(label: "test") // Just for the async example
queue.async {
let items: [String] = (1...100).flatMap { "Item: \($0)" } // Just generate some string
Thread.sleep(forTimeInterval: 3.0) // Wait 3 seconds
DispatchQueue.main.async { // Go back to main thread
self.myItems = items // Assign data source to self
self.refreshFromData() // Now refresh the table view
}
}
}
private func refreshFromData() {
tableView?.reloadData()
tableView?.isHidden = myItems == nil
// Add other stuff that need updating here if needed
}
/// Will remove an item from the data source and update the array
///
/// - Parameter item: The item to remove
fileprivate func removeItem(item: String) {
if let index = myItems?.index(of: item) { // Get the index of the object
tableView?.beginUpdates() // Begin updates so the table view saves the current state
myItems = myItems?.filter { $0 != item } // Update our data source first
tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) // Do the table view cell modifications
tableView?.endUpdates() // Commit the modifications
}
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myItems?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell {
cell.item = myItems?[indexPath.row]
cell.delegate = self
return cell
} else {
return UITableViewCell()
}
}
}
// MARK: - MyTableViewCellDelegate
extension ViewController: MyTableViewCellDelegate {
func myTableViewCell(pressedMainButton sender: MyTableViewCell) {
guard let item = sender.item else {
return
}
// Delete the item if main button is pressed
removeItem(item: item)
}
}
protocol MyTableViewCellDelegate: class { // We need ": class" so the delegate can be marked as weak
/// Called on main button pressed
///
/// - Parameter sender: The sender cell
func myTableViewCell(pressedMainButton sender: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
#IBOutlet private weak var button: UIButton?
weak var delegate: MyTableViewCellDelegate? // Must be weak or we can have a retain cycle and create a memory leak
var item: String? {
didSet {
button?.setTitle(item, for: .normal)
}
}
#IBAction private func buttonPressed(_ sender: Any) {
delegate?.myTableViewCell(pressedMainButton: self)
}
}
In your case the String should be replaced by the User. Next to that you will have a few changes such as the didSet in the cell (button?.setTitle(item.name, for: .normal) for instance) and the filter method should use === or compare some id or something.
try this -
update didPressButton method like below -
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.reloadData()
}

How to access custom cell textfield values Swift 3.0

I have created a custom tableViewCell class for a prototype cells which is holding a text field. Inside ThirteenthViewController, I would like to reference the tableViewCell class so that I can access its doorTextField property in order to assign to it data retrieved from UserDefaults. How can I do this?
class ThirteenthViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate {
var options = [
Item(name:"Doorman",selected: false),
Item(name:"Lockbox",selected: false),
Item(name:"Hidden-Key",selected: false),
Item(name:"Other",selected: false)
]
let noteCell:NotesFieldUITableViewCell! = nil
#IBAction func nextButton(_ sender: Any) {
//save the value of textfield when button is touched
UserDefaults.standard.set(noteCell.doorTextField.text, forKey: textKey)
//if doorTextField is not empty assign value to FullData
guard let text = noteCell.doorTextField.text, text.isEmpty else {
FullData.finalEntryInstructions = noteCell.doorTextField.text!
return
}
FullData.finalEntryInstructions = "No"
}
override func viewDidLoad() {
let index:IndexPath = IndexPath(row:4,section:0)
let cell = tableView.cellForRow(at: index) as! NotesFieldUITableViewCell
self.tableView.delegate = self
self.tableView.dataSource = self
cell.doorTextField.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// configure the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
if indexPath.row < 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = options[indexPath.row].name
return cell
} else {
let othercell = tableView.dequeueReusableCell(withIdentifier: "textField") as! NotesFieldUITableViewCell
othercell.doorTextField.placeholder = "some text"
return othercell
}
}
}//end of class
class NotesFieldUITableViewCell: UITableViewCell {
#IBOutlet weak var doorTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
In order to access the UITextField in your cell, you need to know which row of the UITableView contains your cell. In your case, I believe the row is always the 4th one. So, you can create an IndexPath for the row and then, you can simply do something like this:
let ndx = IndexPath(row:3, section: 0)
let cell = table.cellForRow(at:ndx) as! NotesFieldUITableViewCell
let txt = cell.doorTextField.text
The above might not be totally syntactically correct since I didn't check for syntax, but I'm sure you can take it from there, right?
However, do note that in order for the above to work, the last row (row 4) has to be always visible. If you try to fetch rows which are not visible, you do run into issues with accessing them since UITableView reuses cells and instantiates cells for the visible rows of data.
Also, if you simply want to get the text that the user types and text input ends when they tap "Enter", you can always simply bypass accessing the table row at all and add a UITextFieldDelegate to your custom cell to send a notification out with the entered text so that you can listen for the notification and take some action.
But, as I mentioned above, this all depends on how you have things set up and what you are trying to achieve :)
Update:
Based on further information, it appears as if you want to do something with the text value when the nextButton method is called. If so, the following should (theoretically) do what you want:
#IBAction func nextButton(_ sender: Any) {
// Get the cell
let ndx = IndexPath(row:4, section: 0)
let cell = table.cellForRow(at:ndx) as! NotesFieldUITableViewCell
//save the value of textfield when button is touched
UserDefaults.standard.set(cell.doorTextField.text, forKey: textKey)
//if doorTextField is not empty assign value to FullData
guard let text = cell.doorTextField.text, text.isEmpty else {
FullData.finalEntryInstructions = cell.doorTextField.text!
return
}
FullData.finalEntryInstructions = "No"
}
You can create a tag for the doorTextField (for instance 111)
Now you can retrieve the value.
#IBAction func nextButton(_ sender: Any) {
//save the value of textfield when button is touched
guard let textField = self.tableViewview.viewWithTag(111) as! UITextField? else { return }
prit(textField.text)
.....
}

Swift: retrieving text from a UITextField in a custom UITableViewCell and putting it in an array

I'm making a very simple app where the user enters the number of people in the first Screen.
In the second screen it generates a number of UITableViewCell based on the number the user entered in the first screen. The UITableViewCell have a UITextField in them and I'm trying to store the data entered in those fields in an array once the user clicks to go to the third screen.
How can I do that? Thanks in advance!
Edit: I'm using the storyboard.
Here is what the code that calls for the custom UITableViewCell looks like for my UIViewController:
func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell: EditingCell = tableView.dequeueReusableCellWithIdentifier("Cell") as EditingCell
if indexPath.row % 2 == 0{
cell.backgroundColor = UIColor.purpleColor()
} else {
cell.backgroundColor = UIColor.orangeColor()
}
let person = arrayOfPeople[indexPath.row]
cell.setCell(person.name)
return cell
}
Here is what the code for the UITableViewCell looks like:
class EditingCell: UITableViewCell{
#IBOutlet weak var nameInput: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCell(name:String){
self.nameInput.placeholder = name
}
}
There is a problem with your approach if the number of rows in your table exceeds the number that can fit on screen. In that case, the cells that scroll off-screen will be re-used, and the contents of the nameInput textField will be lost. If you can be sure that this will never happen, use the following code (in the method that handles button taps) to compose your array:
var arrayOfNames : [String] = [String]()
for var i = 0; i<self.arrayOfPeople.count; i++ {
let indexPath = NSIndexPath(forRow:i, inSection:0)
let cell : EditingCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingCell?
if let item = cell?.nameInput.text {
arrayOfNames.append(item)
}
}
println("\(arrayOfNames)")
Alternatively....
However, if it is possible that cells will scroll off-screen, I suggest a different solution. Set the delegate for the nameInput text fields, and then use the delegate methods to grab the names as they are entered.
First, add variables to your view controller, to hold the array and the row number of the text field currently being edited.
var arrayOfNames : [String] = [String]()
var rowBeingEdited : Int? = nil
Then, in your cellForRowAtIndexPath method, add:
cell.nameInput.text = "" // just in case cells are re-used, this clears the old value
cell.nameInput.tag = indexPath.row
cell.nameInput.delegate = self
Then add two new functions, to catch when the text fields begin/end editing:
func textFieldDidEndEditing(textField: UITextField) {
let row = textField.tag
if row >= arrayOfNames.count {
for var addRow = arrayOfNames.count; addRow <= row; addRow++ {
arrayOfNames.append("") // this adds blank rows in case the user skips rows
}
}
arrayOfNames[row] = textField.text
rowBeingEdited = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
rowBeingEdited = textField.tag
}
When the user taps the button, they might still be editing one of the names. To cater for this, add the following to the method that handles the button taps:
if let row = rowBeingEdited {
let indexPath = NSIndexPath(forRow:row, inSection:0)
let cell : EditingTableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingTableViewCell?
cell?.nameTextField.resignFirstResponder()
}
This forces the textField to complete editing, and hence trigger the didEndEditing method, thereby saving the text to the array.
Here for new swift versions of answer
var arrayOfNames : [String] = [String]()
var i = 0
while i < taskArrForRead.count {
let indexPath = IndexPath(row: i, section: 0)
let cell : taslakDuzenlemeCell? = self.tableView.cellForRow(at: indexPath) as! taslakDuzenlemeCell?
if let item = cell?.taslakTextField.text {
arrayOfNames.append(item)
}
i = i + 1
}
print("\(arrayOfNames)")

Resources