I am implementing a Question & Answer program using Swift with Firebase. I want users to be able to like the answers of questions. My database structure for handling likes is:
answerLikes
answerID
userID : true
answers
...
posts
...
users
...
I tried to implement my program according to this data structure. You can see the code in my TableViewController:
#IBAction func likeButtonClicked(_ sender: UIButton) {
if let indexPath = self.tableView.indexPathForSelectedRow {
ref = Database.database().reference()
print(indexPath.row)
ref.child("answerLikes").child(answers[indexPath.row].id).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
if value?[Auth.auth().currentUser?.uid] == nil {
sender.setImage(UIImage(named: "filledHeart.png"), for: .normal)
self.ref.child("answerLikes").child(self.answers[indexPath.row].id).updateChildValues([(Auth.auth().currentUser?.uid)! : true])
} else {
sender.setImage(UIImage(named: "emptyHeart.png"), for: .normal)
self.ref.child("answerLikes").child(self.answers[indexPath.row].id).removeValue()
}
})
}
}
My problem is that in this function definition, I cannot know that "the tapped like button is in which cell?". We handle this issue in the table view function by using indexPath. So I tried to use it in this code too, however, my code works only if the user clicks the cell and then clicks the like button.
Can anyone help me with this please? I am really having serious problem with this "Like Post" feature. Thank you.
First way
If you are using a custom cell you can use a protocol:
protocol CustomCellDelegate: class {
func likeButtonClicked(cell: YourCell)
}
class YourCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func likeButtonTapped(sender: AnyObject){
delegate?.likeButtonClicked(self)
}
}
Then add the delegate to your ViewController and in cellForRowAtIndexPath set it for your cell:
cell.delegate = self
And finally you can use it in this way:
func likeButtonClicked(cell: YourCell) {
if let indexPath = self.tableView.indexPath(for: cell) {
//....
}
}
Second way
You can get the index with your button position:
#IBAction func likeButtonClicked(_ sender: UIButton) {
var buttonPosition = sender.convertPoint(.zero, to: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: buttonPosition) {
//.....
}
}
Third way
In cellForRowAtIndexPath you can use your button tag:
likeButton.tag = indexPath.row
And then:
#IBAction func likeButtonClicked(_ sender: UIButton) {
let cellRow = sender.tag
//...
}
Related
I want to implement UITableView Where I want to have 3 buttons in each UITableViewCell. I want to perform a diffeent action for each button. How can I identify which button is bring pressed and then get the object(index row ) of the cell that was selected?
UIViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let show=shows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell") as!
ShowCell
cell.setShow(show: show)
return cell
}
UITableViewCell
#IBOutlet weak var graphButton: FlatButton!
#IBOutlet weak var buyButton: FlatButton!
#IBOutlet weak var reviewButton: FlatButton!
func setShow(show :StubHubEvent ){
let url = URL(string: show.imageurl)!
showImageView.af_setImage(withURL: url)
showImageView.contentMode = .scaleAspectFill
showImageView.clipsToBounds = true
nameLabel.text = show.title
dateLabel.text = show.time
implement your button action in UIviewcontroller not a UITableViewCell, create the target in inside the cellforRow as well as add the Tag for each target for identify which button was user pressed.for E.g
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let show=shows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell") as!
ShowCell
cell.graphButton.tag = indexPath.row
cell.buyButton.tag = indexPath.row
cell.reviewButton.tag = indexPath.row
cell.graphButton?.addTarget(self, action: #selector(self.graphButtonClicked(_:)), for: .touchUpInside)
cell.buyButton?.addTarget(self, action: #selector(self.buyButtonClicked(_:)), for: .touchUpInside)
cell.reviewButton?.addTarget(self, action: #selector(self.reviewButtonClicked(_:)), for: .touchUpInside)
cell.setShow(show: show)
return cell
}
and handle the action as like
#objc func buyButton( _ sender: UIButton) {
print("buyButton Action Found the index of \(sender.tag)")
}
#objc func graphButtonClicked( _ sender: UIButton) {
print("graphButtonClicked Action Found the index of \(sender.tag)")
}
#objc func reviewButtonClicked( _ sender: UIButton) {
print("reviewButtonClicked Action Found the index of \(sender.tag)")
}
Option 2
if you want to perform in your button action in UItableviewcell class using delegate pattern, then refer this duplicate answer
there are two ways to get button click execution in the ViewController from TableViewCell
Use Delegate pattern
Use blocks as callbacks and handle block execution in the cellForRow method
Add addTarget(:) to add a target method for the button click
Details:
The first approach is best among all the three mentioned approaches, in this, you need to create a delegate which redirects your user actions from cell to view controller. Check below code example.
The second approach is similar to the first one, it just redirects the same method calls using the blocks instead of Delegate methods and protocol.
The third approach is not good, as it is tightly coupled with the indexPath.row value, in the software development industry
Cohesion should be high, Coupling should be low.
Code of first Approach:
//MARK:- Model - StubHubEvent
class StubHubEvent {
//you model class implementation
}
//MARK:- Protocol - ShowCellUIInteractionDelegate - used to redirect user actions from cell to viewController
protocol ShowCellUIInteractionDelegate: AnyObject {
func showCell(cell: ShowCell, didTapBuyFor show: StubHubEvent)
func showCell(cell: ShowCell, didTapGraphFor show: StubHubEvent)
func showCell(cell: ShowCell, didTapReviewFor show: StubHubEvent)
}
//MARK:- Cell- ShowCell
class ShowCell: UITableViewCell {
var show: StubHubEvent!
weak var delegateUIInteraction: ShowCellUIInteractionDelegate?
func setShow(show :StubHubEvent ){
self.show = show
//your other setup
}
//Bind these three action from cell to buttons as a .touchUpInside event
#IBAction func buttonBuyDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapBuyFor: self.show)
}
#IBAction func buttonGraphDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapGraphFor: self.show)
}
#IBAction func buttonReviewDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapReviewFor: self.show)
}
}
//MARK:- ViewController - ShowListingViewController
class ShowListingViewController: UIViewController {
//you ShowListingViewController implementation
}
//MARK:- Extension - ShowCellUIInteractionDelegate
extension ShowListingViewController: ShowCellUIInteractionDelegate {
//execute your logic for the show model object
func showCell(cell: ShowCell, didTapBuyFor show: StubHubEvent){
}
func showCell(cell: ShowCell, didTapGraphFor show: StubHubEvent){
}
func showCell(cell: ShowCell, didTapReviewFor show: StubHubEvent){
}
}
I have a UIMenuController with a "Delete" menu item on top of a collection view cell which is displayed when the user long presses on a cell with section 1:
#IBAction func handleLongPressOnCell(_ sender: UILongPressGestureRecognizer) {
let p = sender.location(in: collectionView)
guard sender.state == .began, let indexPath = self.collectionView.indexPathForItem(at: p), let cell = self.collectionView.cellForItem(at: indexPath) else { return }
if indexPath.section == 1 {
let frameInSuperView = collectionView.convert(cell.frame, to: view)
let deleteItem = UIMenuItem(title: "Delete", action: #selector(deleteCell))
UIMenuController.shared.menuItems = [deleteItem]
UIMenuController.shared.setTargetRect(frameInSuperView, in: view)
becomeFirstResponder()
UIMenuController.shared.setMenuVisible(true, animated: true)
}
}
How do I pass the index path of the cell to the function below? I need this information to delete the object from the server.
#objc internal func deleteCell(sender: UIMenuItem) {
print("delete menu item tapped! print index path of selected collection view cell?")
}
As #mkeremkeskin pointed out, there's an answer to this where he linked.. but that answer is in Objective-C, here you'll find a Swift 4 version.
You can subclass the UIMenuItem and add the indexPath to it!
I had to remove some code for it to work in my playground, but you get the idea :)
class CustomMenuItem: UIMenuItem {
var indexPath: IndexPath?
convenience init(title: String, action: Selector, indexPath: IndexPath? = nil) {
self.init(title: title, action: action)
self.indexPath = indexPath
}
}
class ViewController {
func handleLongPressOnCell(_ sender: UILongPressGestureRecognizer) {
let indexPath = IndexPath(item: 0, section: 1)
if indexPath.section == 1 {
let deleteItem = CustomMenuItem(title: "Delete", action: #selector(deleteCell), indexPath: indexPath)
UIMenuController.shared.menuItems = [deleteItem]
UIMenuController.shared.setMenuVisible(true, animated: true)
}
}
#objc internal func deleteCell(sender: CustomMenuItem) {
guard let indexPath = sender.indexPath else { return }
// Delete item based on indexPath
}
}
You cannot directly pass the info along with the selector action; instead you should store index path in a member variable which you set in your long-press handler and consume in your delete handler.
private var indexPathForDeleting: IndexPath? = nil
Don't forget to do your housekeeping and clear the variable when it's no longer needed.
You can subclass menu item to get the necessary object.
An example has been answered here:
Pass value through UIMenuItem of UIMenuController
I solved this type of issue in this way:
let menuController = UIMenuController.shared
menuController.accessibilityHint = String(indexPath.row)
#objc func deleteCell(_ sender: UIMenuController) {
print("delete menu item tapped! index path? \(sender.accessibilityHint)")
}
I had using swift 4. Hope it will help.
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)
}
}
In my project thus far, I have successfully implemented a tableview whose cells populate postings from FIR database.
I can't figure out what code to add so that when the message user button is pushed, the poster's userID specific to that cell is identified and extracted. If I could get that far, I can extract the rest of the other user's info so that I can set up a chat that includes the receiver of the message's userID, userFirstName, and profile pic.
I would guess that tagging the button is a first step, but I'm not sure how that gets me the userID specific to that posting.
I don't even have code to show because I'm clueless in how to do this....
Put the button in cell. Also make a custom class of cell and put the userId or user object in that cell. When you tap the button you can get the userId etc from that cell.
Or if you want to get that event from cell to viewcontroller you can pass delegate to view viewcontroller. Some thing like this
//cellforrowatindexpath
cell.delegate = self
cell.indexPath = indexPath
// Your cell class
protocol CellDelegate: class {
func didTapCell(index: IndexPath)
}
#IBAction func buttonAction(_ sender: AnyObject) {
self.delegate?.didTapCell(index: indexPath)
}
//tag the cell button
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("HealerProfileCell", forIndexPath: indexPath) as! yourTVCell
cell.detailsButton.tag = indexPath.row
cell.detailsButton.addTarget(self, action: #selector(HealersTableViewController.performHealerDetailsInfoSegue(_:)), forControlEvents: .TouchUpInside)
return cell
}
//make a function
func performHealerDetailsInfoSegue(sender: AnyObject?) {
performSegueWithIdentifier("your segue identifier", sender: sender)
}
//use prepare for segue method
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let object = yourArray[(sender!.tag)]
if segue.identifier == "your segue identifier" {
let dvc = segue.destinationViewController as! YourVC
dvc.object = object
}
}
Add a target to your button in the TableView cellForItemAtIndexPath Method.
cell.yourButton.addTarget(self, action: #selector(CellButtonClicked), for: .touchUpInside)
Then add this function that is called when the button is pushed...
func CellButtonClicked(sender: UIButton) {
print("Clicked")
guard let cellInAction = sender.superview as? YourCell else { return }
guard let din = yourTableView?.indexPath(for: cellInAction) else { return }
// retrieve the specific values in the cell from your array
let specificCellData = yourArray[(indexPath as NSIndexPath).row]
}
I have a tableview with a variable number of cells representing students that correspond to their particular instructor. They are custom cells with a button that triggers a segue to a new VC, bringing up detailed information on the student whose cell it was. My question is:
What is the best practice in swift for identifying which button was pressed?
Once i know the index path, I can identify which student's information needs to be passed to the next VC. There is a great answer for objective C in the post below, but I'm not sure how to translate to Swift. Any help would be much appreciated.
Detecting which UIButton was pressed in a UITableView
If your code allows, I'd recommend you set the UIButton tag equal to the indexPath.row, so when its action is triggered, you can pull the tag and thus row out of the button data during the triggered method. For example, in cellForRowAtIndexPath you can set the tag:
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
then in buttonClicked:, you can fetch the tag and thus the row:
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Otherwise, if that isn't conducive to your code for some reason, the Swift translation of this Objective-C answer you linked to:
- (void)checkButtonTapped:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
if (indexPath != nil)
{
...
}
}
is:
func checkButtonTapped(sender:AnyObject) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
...
}
}
Swift 3.0 Solution
cell.btnRequest.tag = indexPath.row
cell.btnRequest.addTarget(self,action:#selector(buttonClicked(sender:)), for: .touchUpInside)
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Updated for Swift 3
If the only thing you want to do is trigger a segue on a touch, it would be against best practice to do so via a UIButton. You can simply use UIKit's built in handler for selecting a cell, i.e. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). You could implement it doing something like the following:
Create a custom UITableViewCell
class StudentCell: UITableViewCell {
// Declare properties you need for a student in a custom cell.
var student: SuperSpecialStudentObject!
// Other code here...
}
When you load your UITableView, pass the data into the cell from you data model:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StudentCell", for: indexPath) as! StudentCell
cell.student = superSpecialDataSource[indexPath.row]
return cell
}
Then use didSelectRow atIndexPath to detect when a cell has been selected, access the cell and it's data, and pass the value in as a parameter to performSegue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! StudentCell
if let dataToSend = cell.student {
performSegue(withIdentifier: "DestinationView", sender: dataToSend)
}
}
And finally in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DestinationView" {
let destination = segue.destination as! DestinationViewController
if let dataToSend = sender as? SuperSpecialStudentObject {
destination.student = dataToSend
}
}
}
Alternatively if you want them to only select a part of the cell instead of when they touch anywhere inside the cell, you could add an accessory item onto your cell such as the detail accessory item (looks like the circle with an "i" inside of it) and use override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) instead.
Another possible solution would be using dispatch_block_t. If you do it with Storyboard you first have to create a member variable in your custom UITableViewCell class.
var tapBlock: dispatch_block_t?
Then you have to create an IBAction and call the tapBlock.
#IBAction func didTouchButton(sender: AnyObject) {
if let tapBlock = self.tapBlock {
tapBlock()
}
}
In your view controller with the UITableView you can simply react to the button events like this
let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell
cell.tapBlock = {
println("Button tapped")
}
However you have to be aware when accessing self inside the block, to not create a retain cycle. Be sure to access it as [weak self].
Swift 3
# cellForRowAt indexPath
cell.Btn.addTarget(self, action: #selector(self.BtnAction(_:)), for: .touchUpInside)
Then
func BtnAction(_ sender: Any)
{
let btn = sender as? UIButton
}
It's never a good idea to use tags to identify cells and indexPaths, eventually you'll end up with a wrong indexPath and consequently the wrong cell and information.
I suggest you try the code bellow (Working with UICollectionView, didn't tested it with a TableView, but it probably will work just fine):
SWIFT 4
#objc func buttonClicked(_ sender: UIButton) {
if let tableView = tableViewNameObj {
let point = tableView.convert(sender.center, from: sender.superview!)
if let wantedIndexPath = tableView.indexPathForItem(at: point) {
let cell = tableView.cellForItem(at: wantedIndexPath) as! SpecificTableViewCell
}
}
}
Detecting the Section and row for UiTableView indexPath on click Button click
//MARK:- Buttom Action Method
#objc func checkUncheckList(_sender:UIButton)
{
if self.arrayRequestList != nil
{
let strSection = sender.title(for: .disabled)
let dict = self.arrayRequestList![Int(strSection!)!]["record"][sender.tag]
print("dict:\(dict)")
self.requestAcceptORReject(dict: dict, strAcceptorReject: "1")
}
}
Here is UITableView Cell Method to add the targate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherPropertySelectiingCell", for: indexPath as IndexPath) as! OtherPropertySelectiingCell
cell.btnAccept.tag = indexPath.row
cell.btnAccept.setTitle("\(indexPath.section)", for: .disabled)
cell.btnAccept.addTarget(self, action: #selector(checkUncheckList(_sender:)), for: .touchUpInside)
return cell
}
Swift 5. In cellForRowAtIndexPath you set the tag:
cell.shareButton.tag = indexPath.row
cell.shareButton.addTarget(self, action: #selector(shareBtnPressed(_:)), for: .touchUpInside)
Then in shareBtnPressed you fetch the tag
#IBAction func shareBtnPressed(_ sender: UIButton) {
let buttonRow = sender.tag
print("Video Shared in row \(buttonRow)")
}
As a follow up to #Lyndsey and #longbow's comments, I noticed that when I had the segue in storyboard going from the button to the destinationVC, the prepareForSegue was being called before the buttonClicked function could update the urlPath variable. To resolve this, I set the segue directly from the first VC to the destinationVC, and had the segue performed programmatically after the code in buttonClicked was executed. Maybe not ideal, but seems to be working.
func buttonClicked(sender:UIButton) {
let studentDic = tableData[sender.tag] as NSDictionary
let studentIDforTherapyInt = studentDic["ID"] as Int
studentIDforTherapy = String(studentIDforTherapyInt)
urlPath = "BaseURL..."+studentIDforTherapy
self.performSegueWithIdentifier("selectTherapySegue", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "selectTherapySegue") {
let svc = segue.destinationViewController as SelectTherapyViewController;
svc.urlPath = urlPath
}
Updated for Swift 5:
Place the following code within your ViewController class
#IBAction func buttonClicked(_ sender: UIButton) {
if let tableView = tableView {
let point = tableView.convert(sender.center, from: sender.superview!)
//can call wantedIndexPath.row here
}
}
}
I am doing it via prepareforSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath = self.tableView.indexPathForSelectedRow()
let item = tableViewCollection[indexPath!.row].id
let controller = segue.destinationViewController as? DetailVC
controller?.thisItem = item
}
and on the next controller i will just reload the full item properties, by knowing its id and setting it to the var thisItem in the DetailVC
I was going to use the indexPath approach until I came to understand that it would be unreliable/wrong in some situations (deleted or moved cell, for instance).
What I did is simpler. By example, I am displaying a series of colors and their RGB values—one per tableview cell. Each color is defined in an array of color structures. For clarity these are:
struct ColorStruct {
var colorname:String = ""
var red: Int = 0
var green: Int = 0
var blue: Int = 0
}
var colors:[ColorStruct] = [] // The color array
My prototype cell has a var to hold the actual index/key into my array:
class allListsCell: UITableViewCell {
#IBOutlet var cellColorView: UIView!
#IBOutlet var cellColorname: UILabel!
var colorIndex = Int() // ---> points directly back to colors[]
#IBAction func colorEditButton(_ sender: UIButton, forEvent event: UIEvent) {
print("colorEditButton: colors[] index:\(self.colorIndex), \(colors[self.colorIndex].colorname)")
}
}
This solution takes three lines of code, one in the prototype cell definition, the second in the logic that populates a new cell, and the the third in the IBAction function which is called when any cell's button is pressed.
Because I have effectively hidden the "key" (index) to the data in each cell AS I am populating that new cell, there is no calculation required -and- if you move cells there is no need to update anything.