Passing values between ViewControllers based on list selection in Swift - swift

I'm trying to pass the selected index number of a listView selection from one ViewController to another but am running into an issue with the tableView didSelectRowAtIndexPath delegate runs slightly later than the prepareForSegue function.
Basically, in didSelectRowAtIndexPath, I seta variable, which is then picked up in the prepareForSegue. The issue is that prepareForSegue seems to run the second that the cell is selected and before the didSelectRowAtIndexPath function is called so my variable is not passed.
My main bits of code are:
tableView didSelectRowAtIndexPath delegate, which sets 'selectedResult'...
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
selectedResult = indexPath.item
txtNameSearch.resignFirstResponder() //get rid of keyboard when table touched.
println("saved result")
//tableView.deselectRowAtIndexPath(indexPath, animated: false)
//var tappedItem: ToDoItem = self.toDoItems.objectAtIndex(indexPath.row) as ToDoItem
//tappedItem.completed = !tappedItem.completed
//tableView.reloadData()
}
prepareForSegue function which sends the variable as 'toPass' to the other ViewController ('detailViewController'):
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!){
if (segue.identifier == "detailSeque") {
println("preparing")
var svc = segue!.destinationViewController as detailViewController
svc.toPass = selectedResult
//println(tableView.indexPathsForSelectedRows().
//svc.toPass = tableView.indexPathForSelectedRow()
}
}
Thanks in advance

If you have an outlet to your tableView in your ViewController, you can just call indexPathForSelectedRow in prepareForSegue.
If your ViewController is a subclass of UITableViewController, then you can do:
let row = self.tableView.indexPathForSelectedRow().row
println("row \(row) was selected")
If your ViewController is not a subclass of UITableViewController, set up an IBOutlet to the UITableView in your view controller:
#IBOutlet var tableView: UITableView!
and wire that up in Interface Builder and call it from prepareForSegue as show above.

Instead of triggering your segue directly from a storyboard action, why don't you try programmatically calling performSegueWithIdentifier in your didSelectRowAtIndexPath method, after selectedResult is set?

Related

Any idea as to why my data isn't being passed to the new tableview?

I am passing data from one tableview to another. I want the category data that the tableviewA contains to be passed to tableviewB. When I perform the segue, the print data that I have for TableviewB is empty.
This is tableviewA
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let category = listOfCategories[indexPath.row].strCategory
let vc = MealsByCategoryVC()
vc.mealCategory = category
print(category) // Properly returns the category
performSegue(withIdentifier: "searchMeals", sender: nil)
}
This is tableview2
class MealsByCategoryVC: UITableViewController {
var mealCategory : String = ""
var listOfMeals : [Meals] = []
override func viewDidLoad() {
super.viewDidLoad()
print("Meal category is \(mealCategory)") //This statement returns "Meal category is "
}
This:
let vc = MealsByCategoryVC()
vc.mealCategory = category
performSegue(withIdentifier: "searchMeals", sender: nil)
...is not how you pass a value into a view controller that you are creating by calling performSegue. The first two lines of that code do nothing at all! The view controller created by the segue is different from the one you are creating by saying MealsByCategoryVC(); in fact, the latter is just thrown away, uselessly. You are setting the mealCategory of the wrong view controller instance.
Instead, implement prepare(for:sender:). That's what it's for. You receive the segue and its destination view controller. That is the view controller whose mealCategory you need to set.

Dissmiss UITableViewController from button in TableViewCell

I have a UITableViewController that has a header view class with a UIButton in it that I've linked up in interface builder. I've got it working fine so that when you tap the button the code fires.
However, I have no idea what to write to dismiss the table view when you tap this button.
class headerCell: UITableViewCell {
#IBAction func exit () {
print("got pressed")
// tried: MyTableView().dismiss(animated: true, completion: nil)
// But did not work, did nothing?
}
}
class MyTableView: UITableViewController {
// All the code for the tableView is here...
}
MyTableView().dismiss(animated: true, completion: nil)
This makes a brand new table view controller and dismisses it. Since it was never even presented, this does nothing
You need to dismiss the one that contains the cell.
This is one way: https://stackoverflow.com/a/50334803/3937
But I recommend adding a weak reference to the header cell to the MyTableView and setting it on construction
Also, MyTableView is a ViewController, not a view. So, MyTableViewController is a better name.
After experimenting with the various answers, I've found that creating an IBAction is not the best way to do this, and instead I've simply added an IBOutlet for the button, then done this in the TableViewController:
#objc func exit () {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! headerCell
cell.exitButton.addTarget(self, action: #selector(exit), for: .touchUpInside)
return cell
}

Swift add button to UICollectionViewCell that opens new controller

I have a UICollectionViewCell header on a UICollectionViewController, and I've added a button to it. I would like for the button, when clicked, to push a new view controller atop the current one. The problem is that the button doesn't have access to the navigation controller of the UICollectionViewController, so I there's no way to directly push a controller from, say, a connector to the buttn (that I know of). Is there any way to achieve this? Maybe something can be overriden, such as a collectionView function. Thanks!
If you just want to process the cell selection there is a handy method in the UICollectionViewDelegate that you can implement to get the index path of the pressed cell.
If your goal is to have a custom button inside the cell (or maybe even several) you can use delegation pattern to retrieve user actions to your controller to than process in any way, including pushing/presenting new controllers. Assign the controller's instance (the one managing the collection view) to the delegate member of your cell.
Define a protocol that I would call something like MyCustomCellDelegate (replace MyCustomCell with a more appropriate name for your case). Something like MyCustomCellDelegate: class { func didPressButtonX() }
Declare an optional delegate property in your cell subclass. weak var delegate: MyCustomCellDelegate?
Implement your delegate protocol by the class you want to respond to button presses (or any other interactions defined by your protocol).
Every time you create/dequeue a cell for your UICollectionView to use you set the delegate property to the view controller managing the collection view. cell.delegate = self (if done inside the view controller itself).
After receiving the UI event inside your custom cell use your delegate property to retrieve the action to the controller (or with ever object you used when assigning the property). Something like: delegate?.didPressButtonX()
In your class that implements MyCustomCellDelegate use the method to push the new controller.
Below I will provide sample code that should give more details on the implementation of the proposed solution:
// In your UICollectionViewCell subclass file
protocol MyCustomCellDelegate: class {
func didPressButtonX()
func didPressButtonY()
}
MyCustomCell: UICollectionViewCell {
weak var delegate: MyCustomCellDelegate?
#IBOutlet var buttonX: UIButton!
#IBOutlet var buttonY: UIButton!
#IBAction func didPressButtonX(sender: Any) {
delegate?.didPressButtonX()
}
#IBAction func didPressButtonY(sender: Any) {
delegate?.didPressButtonY()
}
}
// Now in your UICollectionViewController subclass file
MyCustomCollectionViewController: UICollectionViewController {
// ...
override func collectionView(UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier identifier: "YourCellIdentifierGoesHere", for indexPath: indexPath) as! MyCustomCell
// In here we assign the delegate member of the cell to make sure once
// an UI event occurs the cell will call methods implemented by our controller
cell.delegate = self
// further cell setup if needed ...
return cell
}
}
// In order for the instance of our controller to be used as cell's delegate
// we implement the protocol that we defined earlier in the cell file
extension MyCustomCollectionViewController: MyCustomCellDelegate {
func didPressButtonX() {
print("X button was pressed")
// now lets finally push some new controller
let yourNextCoolViewController = UIViewController()
self.push(yourNextCoolViewController, animated: true)
// OR if you are using segues
self.performSegue(withIdentifier: "YourSegueIdentifierGoesHere", sender: self)
}
func didPressButtonY() {
print("Y button was pressed")
}
}

Fill a textField with a selected TableViewCell?

In my initial view there is a blank textField (with an invisible button over it) that segues to a TableView when you click it. I want to send the text data from my TableViewCell selection to the blank textField in the original view.
I've made a View2.swift file, which is a replica of my original View. I was trying to edit the source code in there to push the textData by sending the .text from the indexPath of the TableView.
This video shows how to send data from a first view to a second view, but I'm trying to send data from my second view (TableView) back to my first view (View).
Here is my code:
ViewController.swift
import UIKit
class ViewController: UITableViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController : View2 = segue.destinationViewController as! View2
DestViewController.formulaSelectionText = ViewController.
}
}
View2.swift:
import Foundation
import UIKit
class View2: UIViewController {
override func viewDidLoad(){
formulaSelection.text = indexPathForCell(Cell: UITableView)
}
}
I assume it would be best to just update the value in the initial view. I'm thinking with a UITextFieldDelegate?
It is easy to pass back UITableViewCell Selected value back to a controller. Simply on selection of a cell, update the textfield's value in previous controller and pop the controller/dismiss it if you are using navigation controller / modal controller.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//get last controller in hierarchy
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController];
var prevController : PrevViewController = viewControllers[viewControllers.count - 1] as PrevViewController;
prevController.textfield.text = dataSource[indexPath.row]; //update textfield here
self.navigationController!.popViewControllerAnimated(true);
}
Hope it helps!

Master-Detail: UINavigatorController vs Storyboard Segue

Scenario: Master(TableView) --> Detail.
Modus Operandi: Select Row --> display DetailVC
As you can see below, I have a MasterVC embedded in a UINavigationController:
I currently display the DetailVC via pushing it into the UINavigationController's VC stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
let storyboard = UIStoryboard(name: "Bliss", bundle: nil);
let controller = storyboard.instantiateViewControllerWithIdentifier("DiaryPlayerVC") as DiaryPlayerViewController
self.navigationController?.pushViewController(controller, animated: true)
}
This works fine.
However, the 'prepareForSeque' doesn't fire:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDiaryPlayer" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as NSDate
// (segue.destinationViewController as DiaryPlayerViewController).detailItem = object
}
}
}
I understand that I probably have two (2) conflicting paradigms here:
1) Using the UINavigationController vs
2) Using the Storyboard Relationship.
So...
Option 1: it appears that I can remove the Segue link to have a storyboard stand-alone DetailVC.
Option 2: via Segue, I'm assuming I can remove the UINavigatorController from the link.
I'm currently using Option #1, launching the DetailVC via the UINavigationController.
Question: If I choose Option #2, how do I access (launch) the DetailVC ("Diary Player") from the Master's Row and hence, fire the Segue's 'prepareForSegue()'?
Answer: create a segue from the table view cell to the detail view controller.
Your screenshot shows that you already created a segue in your storyboard. Give that segue an identifier in its property inspectory. Then you can simply perform the segue in the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
performSegueWithIdentifier("mySegueIdentifier", sender: nil)
}
Note: Ctrl-drag the segue from the TableViewController icon, not from the TableViewCell.