prepareForSegue called before performSegue - ios

I am trying to perform a segue that passes a number of variables to the next view including one variable, currentID, which is retrieved from a parse database. performSegue should not be called until after currentID has been set to the currentID downloaded from the database. However, when I run the code, currentID ends up being an empty string when it is passed to the next view.
Here is my code called by the Button:
#IBAction func submitButtonPressed(_ sender: Any) {
let point = PFGeoPoint(latitude:0.0, longitude:0.0)
let testObject = PFObject(className: "Person")
testObject["inputAmount"] = inputAmount
testObject["outputAmount"] = outputAmount
testObject["inputCurrency"] = inputCurrency
testObject["outputCurrency"] = outputCurrency
testObject["location"] = point
testObject.saveInBackground { (success, error) -> Void in
// added test for success 11th July 2016
if success {
print("Object has been saved.")
self.currentID = String(describing: testObject.objectId!)
if(self.currentID != ""){
self.performSegue(withIdentifier: "mainToListSegue", sender: self)
}
} else {
if error != nil {
print (error)
} else {
print ("Error")
}
}
}
}
And here is the prepareForSegue method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let listViewController = (segue.destination as! UINavigationController).viewControllers[0] as! ListViewController
listViewController.inputCurrency = inputCurrency
listViewController.outputCurrency = outputCurrency
listViewController.inputAmount = inputAmount
listViewController.outputAmount = outputAmount
listViewController.currentID = currentID
listViewController.cellContent = cellContent
}

To achieve your needs, you MUST connect your segue between viewcontrollers, and not from UIButton to viewcontroller.
Every time you need to prepare your segue before calling it, this is the procedure:
Then, name it and use delegate method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
}
}

For navigating from one controller to another, connect your segue from view controller instead of from the button and it will work.

Related

How to set a delegate in Swift

I want to send my UserModel with all user informations from a ViewController (ShowUserViewController) to another ViewController (ChatViewController) with a delegate but its not working.
In my ShowUserViewControllers user are all informations I want to send to the ChatViewController.
var user: UserModel?
In my ChatViewController I have the following declaration where I want to send my datas:
var currentUser: UserModel?
Here my protocol:
protocol UserInfoToChatID {
func observeUserID(user: UserModel)
}
Here I prepare the segue and set delegate by tapping the button:
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
var delegate: UserInfoToChatID?
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
delegate?.observeUserID(user: user!)
}
At last I call the delegate in my ChatViewController:
extension ChatViewController: UserInfoToChatID {
func observeUserID(user: UserModel) {
self.currentUser = user
performSegue(withIdentifier: "UserInfoToChatVC", sender: self)
}
}
If you need to pass data from one ViewController to another, you don't have to use delegates for this. You can just pass this data as sender parameter of performSegue method:
performSegue(withIdentifier: "UserInfoToChatVC", sender: user!)
then in prepare for segue just downcast sender as UserModel and assign destination's currentUser variable
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = sender as! UserModel
}
}
But in your case you actually don't have to pass user as sender. You can just assign destination's currentUser variable as ShowUserViewController's global variable user
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = user!
}
}
2 things:
first, if you just want to pass data from one viewController to other viewController you don't need to use delegate pattern, just pass the object to the next viewController on prepare form segue.
second, if you want to implement the delegate pattern you should have one viewController than call to the delegate and the other implement the functions.
example:
protocol ExampleDelegate: class {
func delegateFunction()
}
class A {
//have delegate var
weak var delegate: ExampleDelegate?
// someWhere in the code when needed call to the delegate function...
delegate?.delegateFunction()
}
Class B: ExampleDelegate {
func delegateFunction() {
// do some code....
}
//when you move to the next viewControoler(to A in that case)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AClass" {
if let vc = segue.destination as? A {
vc.delegate = self
}
}
}
To pass the UserModel object forward, from ShowUserViewController to ChatViewController, you should use something called Dependency Injection:
So you'll do something like this inside ShowUserViewController:
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "UserInfoToChatVC", sender: nil)
}
Note: The sender parameter should be the object that initiated the segue. It could be self, i.e. the ShowUserViewController object, but I'd advise against passing the UserModel object, because that object did not initiate the segue, and has nothing to do with navigation at all. It should be injected inside the Destination Controller later on.
In the same file, override the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
I believe you've mostly done this part right, but you may need to communicate back from ChatViewController to ShowUserViewController.
In that case, you can and should use Delegation.
Create something like this inside ShowUserViewController:
protocol ChatViewControllerDelegate: class {
func didUpdateUser(_ model: UserModel)
}
class ChatViewController: UIViewControler {
var user: UserModel?
weak var delegate: ChatViewControllerDelegate?
/* more code */
func someEventHappened() {
delegate?.didUpdateUser(self.user!)
}
}
Finally, there is an additional line to be added to the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
// Add this line...
chatVC.delegate = self
}
}
And specify that the ShowUserViewController implements the ChatViewControllerDelegate protocol, then override the didUpdateUser(_:) method:
func didUpdateUser(_ model: UserModel) {
// Some code here
}

How to access variables when preparing for segue?

I'm preparing for a Segue (just learning at the moment!) and want to pass an array, which is created in a function, through a segue to arrive at the new view controller.
It's working fine if I just put a text string in there, but when I try change to an array it is blank - I think because it can't access the array because it's outside the scope - but I'm stuck on how to do it
Here's the initial VC code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
destVC.workoutName = (testArray)
}
}
//Get the new view controller using segue.destinationViewController.
//Pass the selected object to the new view controller.
}
func testfunction() {
let testArray = ["blah","blah","ploop"]
}
and the 'receiving VC code'
class WorkoutViewController: UIViewController {
var workoutName = [String]()
override func viewDidLoad() {
super.viewDidLoad()
print(workoutName)
// Do any additional setup after loading the view.
}
I'm nearly there with it but think I must be missing something basic. How do you do this passing arrays/other variables created in functions?
If the function had more than 1 variable/array, would that change the approach? I.e. one function might produce the exercises in the workout AND the number of reps for example
You can send it in sender
self.performSegue(withIdentifier: "goToWorkout ", sender: testArray)
and in
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
destVC.workoutName = sender as! [String]
}
}
Yes, both occurrences of testArray are not in the same scope and the compiler complains.
Declare the function this way
func testfunction() -> [String] {
let testArray = ["blah","blah","ploop"]
return testArray
}
and assign the array by calling the function
destVC.workoutName = testfunction()
Your issue is caused by testArray being a local variable defined inside the testfunction function making it only accessible from inside the function. If you want to make a variable accessible from everywhere inside the class, you'll have to make it an instance property.
class InitialVC: UIViewController {
let testArray = ["blah","blah","ploop"]
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
destVC.workoutName = testArray
}
}
//Get the new view controller using segue.destinationViewController.
//Pass the selected object to the new view controller.
}
}
When saying:
destVC.workoutName = (testArray)
in the prepare(for segue: UIStoryboardSegue, sender: Any?) method, it definitely doesn't refers to testArray variable in testfunction(), since it is a local variable. If you are not getting a compile time error at the above line, then probably your declaring testArray somewhere in the view controller or maybe globally in the app.
So what you could do is to declare testfunction() as testfunction() -> [String]:
func testfunction() -> [String] {
return["blah","blah","ploop"]
}
thus:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
destVC.workoutName = testfunction()
}
}
}
func testfunction() -> [String] {
return ["blah","blah","ploop"]
}
I guess this is what you want. Functions / methods can return values. It can be achieved by the -> syntax.
Now you can use like:
destVC.workoutName = testfunction()
Few notes
Functions, methods and variables should be named through camel case notation. So, instead of testfunction you should write testFunction (maybe choose a better name also).
Do not forget to read Apple documentation on the subject: Functions.

Swift sending Multiple Objects to View Controller

I am trying to send multiple objects from my initial view controller to my Username VC. Here is the segue code from my controllers: The issue comes when I add in the code to send the second object, termreport. If I delete the termsM and the assignment, it send the students as usually, but I also need to send the termReport object. How would I fix this?
ViewControler:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let students = sender as AnyObject as? [Student]
else { return }
guard let termsM = sender as AnyObject as? [TermReport] //How would I send both objects?
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = students
secondVC.userWebView = webView
secondVC.terms = termsM // not sending
}
let gradeResponse = try Parser(innerHTML)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.students)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.termReports) //how would I send both variables?
UsernameVC:
var terms: [TermReport]!
override func viewDidLoad() {
print("TERM \(terms[0].grades[3])")//returns found nil optional ERROR
}
You have to include all of the variables you want to send to another ViewController using a segue into a single object (which can be a collection as well). You either create a custom class/struct that has properties with type [Student] and [TermReport] or put these into a native collection (Tuple or Dictionary).
Create custom struct:
struct TermData {
var students = [Student]()
var termReports = [TermReport]()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueData = sender as? TermData
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = segueData.students
secondVC.userWebView = webView
secondVC.terms = segueData.termReports
}
}
let gradeResponse = try Parser(innerHTML)
let termData = TermData(students: gradeResponse.students, termReports: gradeResponse.termReports)
self.performSegue(withIdentifier: "ShowStudents", sender: termData)

Swift 3, Successfully passed data but var returns nil when used

So here is my code from VC1 and passing the data to VC2.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedArtist = artists[indexPath.item]
performSegue(withIdentifier: "artistToArtSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController()
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
These codes here in VC2 will print the data
class ArtCollectionController: UICollectionViewController {
var artist = Artist() {
didSet{
print(artist.artistId ?? "did not work")
print(artist.name ?? "what name?")
}
}
var selectedArtist = Artist()
but when I use the the variable in these following test codes in VC2. They return a nil.
func fetchArtForArtist() {
guard let artistId = selectedArtist.artistId else {return}
print(artistId)
let fanRef = FIRDatabase.database().reference().child("art_ref").child(artistId)
fanRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
let artId = snapshot.key
print(artId)
// let dataRef = FIRDatabase.database().reference().child(artId)
// dataRef.observe(.value, with: { (snapshot) in
// let dictionary = snapshot.value as? [String: AnyObject]
// //let art =
// }, withCancel: nil)
}, withCancel: nil)
}
#IBAction func testButton(_ sender: UIBarButtonItem) {
print(selectedArtist.name ?? "no name")
print(12345)
}
override func viewDidAppear(_ animated: Bool) {
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
override func viewDidLoad() {
super.viewDidLoad()
fetchArtForArtist()
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
Im doing this in storyBoard. Im even using 2 vars seeing if there is a difference. I dont understand why the data is successfully passed to VC2 to a couple variables but when the variable is used it returns a nil. Please help.
The other responses are good, but I prefer a slightly different approach:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let artCollectionController as ArtCollectionController:
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
case let otherViewController as OtherViewController:
//Code for some other case
}
}
By using a switch statement, you have a prepareForSegue that will handle multiple different segues cleanly.
The case let construct is a cool trick that only executes that case if the variable in the switch can be case to the desired type. If it can be cast, it creates a local variable of the desired type.
I prefer deciding what code to execute based on the class of the destination view controller because it's less fragile than using the segue identifier. If you forget to set the segue identifier, or add a second segue later to the same type of view controller, or have a typo in the name of the identifier, that code won't work. If you have a typo in your class name, though, the compiler throws an error.
Because you set the artist property on a new instance of ArtCollectionController, which is destroyed upon the exit of the prepareForSegue function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController() // created
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
// destroyed here
}
}
Try this instead:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue",
let artCollectionController = segue.destination as? ArtCollectionController
{
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = segue.destination as! ArtCollectionController
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
try this, you are creating one more ArtCollectionController instead of passing data to segue one

Pass a parameter to prepareForSegue from function

I have to functions, both of them trigger performSegueWithIdentifier with the same segue. But depending of which function was called I need to pass different parameters in prepareForSegue.
Some thing like
func first() {
// do some stuff
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func second() {
// do some stuff
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
if functionFirstWasCalled {
destination.property = value
} else if functionSecondWasCalled {
destination.property = anotherValue
}
}
}
Surely, I can do this by setting booleans from second() and first() and then checking them in prepareForSegue - but maybe there is some more elegant way to do this ?
In objective -c you would do:
[self performSegueWithIdentifier:#"segueNAme" sender:#"firstMethod"];
and you can access this message in the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([sender isEqualToString:#"firstMethod"]) {
//firstMEthod called the segue
}
}
The swift equivalent I think would be:
self.performSegueWithIdentifier("segueNAme", sender: "firstMethod")
and
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject) {
if (sender == "firstMethod") {
//firstMEthod called the segue
}
}
My suggestion would be to instead of sending a plain string , send a dictionary type object that contains the methodName, className and some other params useful for future debugging.
All you have to do is send a flag by sender attribute, something like this:
func first() {
performSegueWithIdentifier("My segue", sender:true)
}
func second() {
// do some stuff
performSegueWithIdentifier("My segue", sender:false)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
let isFirstFunctionCalled = sender as! Bool // cast sender to bool
if isFirstFunctionCalled {
destination.property = value
} else {
destination.property = anotherValue
}
}
}
Simply set parallel properties in this View Controller which you then pass to the destination View Controller based on the function called. i.e:
var a = ""
var b = 0
func first() {
// do some stuff
a = "first function determined this variable."
b = 1
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func second() {
// do some stuff
a = "second function determined this variable."
b = 182
performSegueWithIdentifier("My segue", sender:AnyObject?)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "My segue" {
let destination = segue.destinationViewController as! MyController
if functionFirstWasCalled {
destination.property = a
} else if functionSecondWasCalled {
destination.property = b
}
}
}

Resources