Second Controller not receiving data - swift

I have two controllers and I am trying to pass data from the first one to second, but it is not receiving at all. I tried passing data as Properties and also using prepareForSegue, but some how, second view controller is not receiving the value from the first controller. First Controller is called MainViewController and second one is FactViewController. I am using this extension bellow because I am using a contentView inside my first screen. When I tap a cell from the content view I want to perform the data transference. factValue constant is receiving normally the data I want, but when I transfer it to the second controller (FactViewController) it doesn't transfer.
USING PROPERTY WAY:
extension MainViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let factValue = facts!.result[indexPath.row].value
let fvc = FactViewController()
fvc.receivedValue = factValue
performSegue(withIdentifier: "segueMainscAndFactsc", sender: nil)
}
USING PREPAREFORSEGUE WAY:
extension MainViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let factValue = facts!.result[indexPath.row].value
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let destinationVC = segue.destination as! FactViewController
destinationVC.receivedValue = factValue
}
}
}
This is my second viewController (FactViewController):
class FactViewController: UIViewController {
var receivedValue: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print(receivedValue)
}
}
When I print the variable receivedValue in the second Controller inside viewDidLoad it still prints an empty String. Does any one has a suggestion to fix it?

Well, the first way is very obviously invalid. You say let fvc = FactViewController() and set its receivedValue, but then you throw it away and instead you trigger a segue which involves a completely different FactViewController. So the one you set the receivedValue is not the one whose view got loaded.
The second way is also invalid because your prepareForSegue is buried inside another a function (and has the wrong signature), so it never even gets called.
What you actually want is more like this:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "segueMainscAndFactsc", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! FactViewController
let factValue = // ???
destinationVC.receivedValue = factValue
}
Note that I've corrected your prepare signature.
The hard part here is working out what factValue should be. But for now, just make it some arbitrary value to convince yourself that this works! Then you can work on obtaining the correct value to hand over to the destination vc.

Related

Use perform segue into a collection view

i need to pass my data into collection view with index path.
i'm trying to use this code but i don't know why i have 2 times the result, one time it's ok and the second one is nil.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showDetails", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let cell = sender as? UICollectionViewCell,
let indexPath = self.collectionView.indexPath(for: cell) {
let secondViewController = segue.destination as! CollectionDetailViewController //Cast with your DestinationController
//Now simply set the title property of vc
secondViewController.people = person[indexPath.row]
}
}
You are getting 2 calls to prepare(for:sender:) because your segue is wired from your UICollectionViewCell. This will call prepare(for:sender:) for you, so you don't need this code:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showDetails", sender: indexPath)
}
Delete it, and you should be good to go.

Delegate method to segue in tableviewcell

I have a delegate method where if I press a button in the tableview, it should segue to another view controller and pass along data but it doesn't seem to work.
func goToVC(uid: String) { //delegate method
performSegue(withIdentifier: "showVC", sender: self) //Do I need this
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showVC", sender: self)
self.tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVC" {
if let indexPath = tableView.indexPathForSelectedRow {
let guestVC = segue.destination as! GuestViewController
guestVC.ref = userArray[indexPath.row].ref
}
}
class MainViewController: UIViewController {
// set the cell's delegate in the data source
// pass the object to the cell from the data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.mainViewControllerDelegate = self
cell.object = someArray[indexPath.row]
}
// this is the method that gets called by the cell through the delegate
func pushToViewController(object: YourDataObject) {
let destination = SomeViewController()
destination.object = object
navigationController?.pushViewController(destination, animated: true)
}
}
class TheTableViewCell: UITableViewCell {
// create a delegate and a data object
var mainViewControllerDelegate: MainViewController?
var object: YourDataObject?
// this is the method that gets called when the button in the cell is tapped
#objc func buttonAction() {
mainViewControllerDelegate?.pushToViewController(object: object)
}
}
I highly recommend that beginners do not use Interface Builder. The less you use it early, the quicker you will understand more. Interface Builder is fool's gold for beginners.
You dont need delegate method here. Delegate method can be used if you need to pass the value from the child view controller.
What you are doing is exactly right. Make sure you set the segue identifier in the story board correctly.
And one more thing dont set your table IBOutlet as default tableView try setting a name apt for that table like toDoTable, so it will easy to debug.

Seguing from uicollectionview that is inside of a tableview

I've put a uicollectionview inside of a uitableview. I'm having trouble seguing to another viewcontroller after selecting a collectionview cell that is inside of the table view cell.
// if the user selects a cell, navigate to the viewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// we check did cell exists or did we pressed a cell
if let cell = sender as? UICollectionViewCell {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "cell") as! TestingTableView
// define index to later on pass exact guest user related info
let index = cell2.collectionView?.indexPath(for: cell)!.row
print(index as Any)
// if segue is guest...
if segue.identifier == "guest" {
// call guestvc to access guest var
let guestvc = segue.destination as! GuestCommunityViewVC
// assign guest user inf to guest var
guestvc.guest = communities[index!] as! NSDictionary
}
}
}
}
I'm getting an error at the line:
let index = cell2.collectionView?.indexPath(for: cell)!.row
because it is saying the value is nil. Does anyone know a better method to do this?
Here is an example of how to use a delegate:
1) Create a protocol outside of a class declaration:
protocol customProtocolName:class {
func pushToNewView(withData:[DataType])
}
note: use class in order to prevent a reference cycle
2) Create a delegate inside of the UITableViewCell that holds the reference to the UICollectionView:
class customUITableViewCell {
weak var delegate:customProtocolName? = nil
}
3) Inside the UIViewController that holds the reference to the UITableView, make sure you add the protocol besides the class declaration and add the function we created to ensure that the protocol specifications are satisfied:
class customViewController: customProtocolName {
func pushToNewView(withData:[DataType]) {
//inside here is where you will write the code to trigger the segue to the desired new UIViewController
//You can take this new data and store it in this ViewController and then during the segue pass it along
}
}
4) In the UITableViewDelegate function, "cellForRowAt", set the delegate inside the customUITableViewCell to self:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! customUITableViewCell
cell.delegate = self
return cell
}
5) Inside the customUITableViewCell, where the UICollectionView delegate function handles "didSelectItemAt" delegate function, you trigger the protocol function there like so:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.pushToNewView(withData:[DataType])
}
This is a very simplified example, if you wanted to pass an IndexPath, then you can modify the function to do so. you can also pass back anything you want as well, it isn't limited.

How to send indexpath when performing segue on collectionViewCell?

I have a collection view controller with similar cells and I want each cell open next viewController with specific content. I think I must send indexpath.row with the segue but in this part I face an error:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newViewController = segue.destination as? myCollectionViewController {
newViewController.passedValue = indexPath.row // exactly here
}
}
There are a couple of things wrong in your code. indexPath.row variable is not declared in your prepare function. To pass the selected cell info to another view controller you first need to add a global variable in your class.
var selectedCell = 0
Next implement the function didSelectRowAtIndexPath in your class. This function is called every time you select a cell. In this function get the cell info and assign it to selectedCell var
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
selectedCell = indexPath.row
}
Now in your prepare method use this var value and pass it on to the next controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourIdentifier" {
let VC = segue.destination as! YourViewController
VC.passed = selectedCell
}
}
Hope this helps.
In the above code:
newViewController.passedValue = indexPath.row // exactly here
what is the value of indexPath here? Is is an instance variable?
Instead of using segue, use UICollectionViewDelegate method:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
//Push your controller here
}

Linking tableview cell information to a view controller

I'm pretty new to Swift. I wish to reflect tableview cell data (title and description of a post) in a new view controller. Here's the code I put in the first view controller (which contains the tableview cell).
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let indexPath = tableView.indexPathForSelectedRow()!
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as! PostCell!
let post = posts[indexPath.row]
valuetoPass = post.title
valuetoPass_desc = post.postDescription
performSegueWithIdentifier("seguetoVC", sender: self)
}
And this one...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguetoVC" {
let viewController = segue.destinationViewController as! UpdateVC
viewController.toPassTitle = valuetoPass
viewController.toPassDesc = valuetoPass_desc
}
}
Here's a part of UpdateVC..Added the variables, toPassTitle and toPassDesc...And these are the lines I added to viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
self.postTxt.delegate = self
self.descTxt.delegate = self
descTxt.text = toPassTitle
postTxt.text = toPassDesc
}
Just wondering what I'm doing wrong-- viewController.toPassTitle and viewController.toPassDesc keep on returning a null value. Thanks in advance.
You can set the sender parameter to be whatever type you want, there's no reason for it to be self, i.e. the originating view controller if that's not what you need. In fact, there should never be a need to pass the originating view controller as the sender, because you can get it from segue.sourceViewController in prepareForSegue.
In the documentation for prepareForSegue, Apple suggests:
Because segues can be triggered from multiple sources, you can use the information in the segue and sender parameters to disambiguate between different logical paths in your app. For example, if the segue originated from a table view, the sender parameter would identify the table view cell that the user tapped. You could then use that information to set the data on the destination view controller.
Which sounds like exactly the situation you're in. Since you've already figured out the model object for your cell, we can pass that rather than the cell. So, you could call performSegueWithIdentifier("seguetoVC", sender: post), and then change up your prepareForSegue implementation to use the details from the post directly, e.g.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguetoVC" {
let viewController = segue.destinationViewController as! UpdateVC
if let post = sender as ? Post {
viewController.toPassTitle = post.title
viewController.toPassDesc = post.postDescription
}
}
}
You don't need to call tableView.cellForRowAtIndexPath(indexPath) as! PostCell! anywhere, and it could be fairly expensive, so don't do it if you don't use it. Your didSelectRowAtIndexPath method might look more like this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let post = posts[indexPath.row]
performSegueWithIdentifier("seguetoVC", sender: post)
}
There's also a bunch more you could do to make it safer, so have a look at unwrapping optionals using constructs such as if let and guard let, which can also help you avoid force casting (!), and potentially crashing your app. I've done that in unwrapping sender to post, so you can see an example there.
You do not need to declare indexPath again. didSelectRowAtIndexPath already has indexPath. Update your codes in didSelectRowAtIndexPath method.
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let post = posts[indexPath.row]
valuetoPass = post.title
valuetoPass_desc = post.postDescription
performSegueWithIdentifier("seguetoVC", sender: self)
}
On top of that, ensure that in your storyboard, you need to name your segue identifier with seguetoVC also.