Go to next cell of a table view from the viewcontroller - ios

I have a question regarding a Swift project.
I have a TableViewController with multiple cells, and when I click on them I go to a ViewController where data is passed from the tableViewController.
I want to implement a button in the ViewController that allows me to display the ViewController with the content of the "next cell" from the table view controller. For instance, I clicked on the 2nd cell of the table view controller, so I go to the ViewController that displays the data corresponding to that cell, and then when I click on that "next" button in the viewController I want to display the content of the 3rd cell of the table view controller. How do I do that in a clean way?
Here is the code I use to pass the data from tableViewController to ViewController :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let a = articles {
return a.count
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("ArticleCell", forIndexPath: indexPath)
let article = articles?[indexPath.row]
if let a = article {
cell.textLabel?.text = a.articleName
cell.textLabel?.font = UIFont(name: "Avenir", size: 18)
cell.textLabel?.numberOfLines = 0
if let i = a.tableViewImage {
cell.imageView?.image = UIImage(named: i)
}
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowArticle" {
let articleVC = segue.destinationViewController as? ArticleViewController
guard let cell = sender as? UITableViewCell,
let indexPath = tableView.indexPathForCell(cell) else {
return
}
articleVC?.article = articles?[indexPath.row]
}
}
In the viewController, to show the right article, I wrote this in the viewDidLoad() (my viewController shows a web view of an article, and I have a class Article where articleLink comes from):
if let a = article {
articleWebView.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(a.articleLink, ofType: "html")!)))
}
I have linked a nextButton in Main.Storyboard:
#IBOutlet weak var nextButton: UIButton!
I'm relatively new to Swift, and have no idea how to do it (my main problem is because I declare my data in the tableViewController, and I don't see how I could stay in the ViewController and "import" data from the tableViewController).

Change your code of TableViewController's prepareForSegue like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowArticle" {
let articleVC = segue.destinationViewController as? ArticleViewController
guard let cell = sender as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) else {
return
}
articleVC?.articles = articles
articleVC?.selectedIndex = indexPath.row
articleVC?.article = articles?[indexPath.row]
}
}
Now add two global variable in your ViewController like below also change your next button click like this
var articles: [article]!
var selectedIndex: Int!
//Now your button click
#IBAction func nextButtonClick(sender: UIButton) {
if ((self.selectedIndex + 1) < self.articles.count) {
self.selectedIndex++
let nextArticle = self.articles[self.selectedIndex]
articleWebView.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(nextArticle.articleLink, ofType: "html")!)))
}
}
I don't know you are using NSArray or Array so create the articles array object same as you created in TableViewController.
Hope this will help you.

Related

How to preserve the original indexPath.row after applying filters to Table View?

My app uses "filter" buttons in which the whereField query is refined based on which filter buttons are pressed. This is an example before filtering:
But this is an example after filtering:
The issue is that when I click into one of the Rows, it takes me to the next page that corresponds to the original indexPath.row in my database belonging to that Row. How can I preserve the original indexPath.row? E.g., Cell B to always be indexPath.row = 1, even after filtering.
This is my cellForRowAt of my first View Controller.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
// Get the mealPlan that the tableView is asking about
let mealPlanInTable = mealPlan[indexPath.row]
// Customize the cell
cell.displayMealPlan(mealPlanInTable)
// Return the cell
return cell
}
And how I connect this View Controller's indexPath.row to the next View Controller after a cell is tapped:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Detect the indexPath the user selected
let indexPath = tableView.indexPathForSelectedRow
// Get the mealPlan the user selected
let mealPlanSelected = mealPlan[indexPath!.row]
// Get a reference to the NextiewController
let NextVC = segue.destination as! NextViewController
// Get a reference to the currentMealPlanIndex in the NextViewController
NextVC.currentMealPlanIndex = indexPath!.row
}
Any advice is much appreciated!
You are getting values from wrong array. Also it's better to pass the obj instead of index.
You need to have 2 variables - one for all data & other for filtered data.
Use filtered data var in tableview datasource & for passing to NextVC.
Considering your class name is MealPlan. Here is the source.
var allMealPlans: [MealPlan]
var filteredMealPlans: [MealPlan]
func onFilterButtonPressed() {
filteredMealPlans = allMealPlans.filter({
// return true/false here based on your filters
})
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
// Get the mealPlan that the tableView is asking about
let mealPlanInTable = filteredMealPlans[indexPath.row]
// Customize the cell
cell.displayMealPlan(mealPlanInTable)
// Return the cell
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Detect the indexPath the user selected
let indexPath = tableView.indexPathForSelectedRow
// Get the mealPlan the user selected
let mealPlanSelected = filteredMealPlans[indexPath!.row]
// Get a reference to the NextiewController
let NextVC = segue.destination as! NextViewController
// Get a reference to the currentMealPlanIndex in the NextViewController
NextVC.currentMealPlan = mealPlanSelected
}
Add a variable in your NextVC for currentMealPlan
class NextVC: UIViewController {
var currentMealPlan: MealPlan?
}
Thank you all for the comments/advice! Instead of connecting the data in the view controllers through the indexPath, I used a document ID that is consistent with the data flowing between my view controllers. This works with all of my filtering.
This is in my first ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
let ingredientsVC = segue.destination as! IngredientsViewController
let documentID = mealPlan[indexPath.row].docID
ingredientsVC.currentMealPlanIndex = indexPath.row
ingredientsVC.passedDocID = documentID!
}
}
And this is in my second ViewController:
// This variable references the unique Document ID
var passedDocID = ""
// This is how I use that document ID to get a reference to the appropriate data
let selectedMealPlanIndex = mealPlan.firstIndex(where: {$0.docID == passedDocID})
let currentMealPlan = mealPlan[selectedMealPlanIndex!]

Passing CloudKit records from TableViewController A to TableViewControllerB on segue with dynamic prototype cells

I'm able to populate TableViewController A with records from CloudKit (var restaurants: [CKRecord] = []) but not sure how to pass a subset of [CKRecord] onto TableViewController B since both controllers have dynamic prototype cells and have their respective view cells defined as UITableViewCell. Further, the segue should only be triggered when the user taps the navigation item on TableViewController A (not when cell is selected).
Here's how I've implemented cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dataCell", for: indexPath) as! RestuarantViewCell
// Configure the cell...
let restaurant = restaurants[indexPath.row]
cell.RestNameLabel?.text = restaurant.object(forKey: "name") as? String
cell.RestDescLabel?.text = restaurant.object(forKey: "desc") as? String
if let image = restaurant.object(forKey: "image"), let imageAsset = image as? CKAsset {
if let imageData = try?Data.init(contentsOf: imageAsset.fileURL) {
cell.RestImageView?.image = UIImage(data: imageData)
}
}
return cell
}
When a user performs a leading swipe, add that item to a list (var list: [CKRecord] = [])
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let addAction = UIContextualAction(style: .normal, title: "Save") { (action, sourceView, completionHandler) in
//Add to list
self.list.append(self.restaurants[indexPath.row])
print(self.list)
//Delete row and reload tableView i.e. tableView.reloadData()
self.tableView.deleteRows(at: [indexPath], with: .fade)
//Call completion handler to dismiss the action button
completionHandler(true)
}
//Configuring delete button
addAction.backgroundColor = UIColor(red: 76, green: 217, blue: 100)
//Create button for user swipe
let swipeConfiguration = UISwipeActionsConfiguration(actions: [addAction])
return swipeConfiguration
}
This list is only visible when the user tap the navigation item on TableViewController A (not when cell is selected). Segue "showList" has been implemented in storyboard from nav item to TableViewController B.
In short, TableViewController B is a list of restaurant (name, image) saves from TableViewController A
Should I use func prepare(for segue: UIStoryboardSegue, sender: Any?) or perhaps save this swipe to the cloud and fetch it again in TableViewController B?
Appreciate your time and suggestions!
Yes, I would use prepare for segue as you've mentioned. Make the restaurants array a class property on both table A and table B.
Something like this:
//Table View Controller A
class TableViewA: UIViewController{
var restaurants = [CKRecord]()
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
if segue.identifier == "NameThisInYourStoryboard"{
let vc = segue.destinationController as! TableViewB
vc.restaurants = self.restaurants //Pass the data
}
}
}
//Table View Controller B
class TableViewB: UIViewController{
var restaurants = [CKRecord]()
}
This all presupposes that you set up the delegate and datasource of your controllers, and define the segue in your storyboard from A's table row tap to show table B.

Sending cell info from one view controller to another Swift and Parse

I have a tableView displaying all the users on ViewController1(VC1) when the currentUser chooses a cell it segues to ViewController2(VC2) and needs to display all the user information from the cell that was clicked onto labels and a PFImageView. I have all the user info for the last cell displaying. So even though I clicked a different cell the next VC is only displaying the user at the end of the list. So lets say my user cell list display starts with Aisling, roger, dave and john. I click on cell 'Aisling' and segue to VC2, it displays all of Johns profile info.
VC1:
under the class:
var appUsers = [PFUser]()
var userToShow = PFUser()
then:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let singleCell: CustomCell = tableView.dequeueReusableCellWithIdentifier("mySingleCellid") as! CustomCell
let userObject = appUsers[indexPath.row] as PFObject
singleCell.userName.text = userObject["name"] as? String
singleCell.userAge.text = userObject["age"] as? String
singleCell.usersProfileImage.file = userObject["image"] as? PFFile
singleCell.usersProfileImage.loadInBackground()
// when userToShow is here it shows the user detail of the last cell everytime
userToShow = appUsers[indexPath.row]
return singleCell
} // cell for row
// when userToShow is here it crashes the app when the cell is clicked giving the error:unexpectedly found nil while unwrapping an Optional value
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
userToShow = appUsers[indexPath.row]
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userProfileDetailsSegue" {
if let destinationVC = segue.destinationViewController as? UserProfileDetailsViewController {
destinationVC.userToShowDetail = userToShow
}
}
}
}
On VC2:
below the class:
var userToShowDetail = PFUser()
Then:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
userName.text = userToShowDetail["name"] as? String
userBio.text = userToShowDetail["bio"] as? String
userAge.text = userToShowDetail["age"] as? String
userProfile.file = userToShowDetail["image"] as? PFFile
userProfile.loadInBackground()
}
It only displays in VC2 the user details belonging to the user in the last cell on VC1. Instead of the user details belonging to the user in the cell that was chosen.
Stuck on this for a few weeks now and my deadline is pushing on can't solve it any help would really be appreciated!!! All links and help I could find online is for C# and obj c.
VC1
//PUT THIS IN THE FIRST VC CLASS
var appUserResult: PFUser?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//TAKE OUT LET - YOU'VE ALREADY DEFINED AS CLASS VAR
appUserResult = appUsers[indexPath.row]
print(appUserResult)
self.performSegueWithIdentifier("openVC2", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "openVC2" {
let indexPath = self.resultsPageTableView.indexPathForSelectedRow!
//INDEXPATH ISN'T AVAILABLE IN PREPAREFORSEGUE
//let userToShow = appUsers[indexPath.row]
let newVc = segue.destinationViewController as! UserProfileDetailsViewController
//USE THE CLASS VARIABLE TO PASS
newVc.userToShowDetail = appUserResult
VC2
var userToShowDetail: PFUser?
In storyboard, delete the old segue. Create a new one from VC1 to VC2 (not from the table cells). Give it the identifier "openVC2".

Prepare for segue is autocalled

I have a view crontroller called "subcateory2", this viewcontroller has a uicollectionview wit custom cell. I need two segues from my app. One of the called "to_videostable" from the viewcontroller to other view controller and the other calles "reload_collection" from the cell to the same viewcontroller(because the subcategory can have n-level of subcategories). The problem is with my prepareForSegue(i check in this function the identifier , that is defined in the " func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)",and execute different actions). When i select a cell this should happen:
first: go to my "func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)", check the condition and define my identifier for segue.
second: go to the prepareforsegue, check the condition and execute the actions.
but actually this happen:
first: in my ios simulator i select a cell.
second: my code go the prepareforsegue and go always for the segue called "reload_collection"(before going to my func collectionView(...)), and create a white views. In this moment is like a two threads are created, one of them go to the white windows and the other to the next stop.
third: this "second theard" go to the func collectionview(...) and check the condition, define the identifier, call to the performSegueWithIdentifier and go to the prepareforsegue function. In the prepareforsegue check the identifier and execute the differentes actions.
This is my code:
import UIKit
class Subcategory2: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
let viewUtils = ViewControllerUtils()
var result_category: Array<JSON> = []
#IBOutlet weak var collectionview: UICollectionView!
var tit: String!
var url: String!
var end_url:String = "?page_size=100"
var id_category: Int!
var index: NSIndexPath!
var url_children : String = ""
let imagePath = "http://d1rkb03u2ginv9.cloudfront.net/wp-content/uploads/"
override func viewDidLoad() {
self.viewUtils.showActivityIndicator(self.view)
super.viewDidLoad()
self.viewUtils.showActivityIndicator(self.view)
if self.result_category.isEmpty{
var category = category_function()
self.url = self.url + self.end_url
self.result_category = category.load_subcategory(self.url)}
self.viewUtils.hideActivityIndicator(self.view)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)!
println("entroooooooo")
if (self.result_category[indexPath.row]["children"].string != nil){
self.url_children = self.result_category[indexPath.row]["children"].string!
//while(self.url_children.isEmpty){}
println("voy a reloadcollection")
performSegueWithIdentifier("reload_collection", sender: cell)
//performSegueWithIdentifier("reload_collection3", sender: self)
}else{
println("voy a to_videostables")
performSegueWithIdentifier("to_videostable", sender: cell)
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//println(result_category.count)
return result_category.count }
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) ->UICollectionViewCell {
let cell: CollectionViewCellController2 = collectionView.dequeueReusableCellWithReuseIdentifier("cell2", forIndexPath: indexPath) as! CollectionViewCellController2
println(self.result_category[indexPath.row]["slug"].stringValue)
cell.label.text = self.result_category[indexPath.row]["slug"].stringValue
if (self.result_category[indexPath.row]["images"]["file"].string != nil){
//println("+++++++++++++++++")
var image = self.result_category[indexPath.row]["images"]["file"].stringValue
cell.image.sd_setImageWithURL(NSURL(string:self.imagePath + (image as! String)))
}else{
var image = "http://www.camping-oaza.com/images/joomlart/demo/default.jpg"
cell.image.sd_setImageWithURL(NSURL(string: image))
//cell.image.image = UIImage(named: image)
}
cell.NumberVideosLabel.text = self.result_category[indexPath.row]["videos_count"].stringValue
cell.NumberSubcategoryLabel.text = self.result_category[indexPath.row]["children_count"].stringValue
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "to_videostable"{
println("-------------")
println("voy a to_videostables")
let cell = sender as! UICollectionViewCell
let index = self.collectionview!.indexPathForCell(cell) // this return -> NSIndexPath?
//if (self.result_category[index!.row]["children"].string != nil){
// self.loaddata(self.result_category[index!.row]["children"].string!)
//}else{
let vc : VideosViewController = segue.destinationViewController as! VideosViewController
println(self.result_category[index!.row]["id"].intValue)
vc.id_category = self.result_category[index!.row]["id"].intValue
}
if segue.identifier == "to_livevideos"{
println("-------------")
println("to_livevideos")
println("-------------------")
let vc : SWRevealViewController = segue.destinationViewController as! SWRevealViewController
}
if segue.identifier == "reload_collection"{
println("-------------")
println("reload_collection")
println("-------------------")
var category = category_function()
let vc : Subcategory2 = segue.destinationViewController as! Subcategory2
vc.url = self.url_children
println(category.load_subcategory(self.url_children + self.end_url))
}
}
}
With this problem, always is created a white windows and after is created a windows with the real information.
the order of the println is :
- "reload_collection"
- "entroooooooo"
- "voy a reloadcollection" or "voy a to_videostables"
In this pictures show my main.stoyboard and the windwos that i can see in my app.
Updated Answer
You have a situation where you want to decide which segue to take when a cell is selected. You have wired one of your segues directly from the cell, which means the storyboard will create that segue for you. You also are calling performSegueWithIdentifier which creates another segue. You need to implement shouldPerformSegueWithIdentifier to cancel the "reload_collection" segue when you want segue to "to_videostables".
In the Original Answer below, I suggested you wire both segues from the viewController, but that won't work because one of your segues is back to the same viewController.
So, another way to do this is to:
Modify didSelectItemAtIndexPath to remove the code that handles the "reload_collection" segue. The Storyboard will be making that segue:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)!
println("entroooooooo")
if result_category[indexPath.row]["children"].string == nil {
println("voy a to_videostables")
performSegueWithIdentifier("to_videostable", sender: cell)
}
}
Wire the segue "reload_collection" from the cell to the viewController. This will allow the Storyboard to perform this segue for you.
Implement shouldPerformSegueWithIdentifier to tell the Storyboard when it should make this segue:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if segue.identifier == "reload_collection" {
let indexPath = collectionView.indexPathForCell(sender as! UICollectionViewCell)
return result_category[indexPath.row]["children"].string != nil
}
return true
}
In prepareForSegue you will need to set up url_children since it is no longer being done by didSelectItemAtIndexPath:
if segue.identifier == "reload_collection"{
println("-------------")
println("reload_collection")
println("-------------------")
var category = category_function()
let vc : Subcategory2 = segue.destinationViewController as! Subcategory2
let indexPath = collectionView.indexPathForCell(sender as! UICollectionViewCell)
url_children = result_category[indexPath.row]["children"].string!
vc.url = url_children
println(category.load_subcategory(self.url_children + self.end_url))
}
Original Answer
Your segue is getting auto-called because you have wired it from the cell. If you want to trigger it with performSegueWithIdentifier it needs to be wired from the viewController like the other segue. Just remove the segue from the cell and rewire it from the viewController and give it the same identifier it had when you wired it from the cell and it should work.

detecting uibutton pressed in tableview: Swift Best Practices

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.

Resources