iOS - Sending sender-linked variables through a segue - ios

Through using a loop I've programmatically created a bunch of UIButtons. Each of these triggers the same segue to another View Controller which is supposed to display some information related to the button.
For this to work, I need to send at least one attribute/variable linked to the specific button tapped through the segue.
One option I tried was creating a new UIButton class to hold the attribute.
class statButton: UIButton {
var buttonIndex = Int()
}
If this even works, how would I access that data here:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "StatDetailSegue" {
if let destinationVC = segue.destination as? StatDetailViewController {
destinationVC.statTitle = // need to access here
}
}
}
The variable I need to send is a value in a dictionary

You have to make sure that the sender is a statButton, and then cast its buttonIndex property to a proper String:
if let destinationVC = segue.destination as? StatDetailViewController,
let index = (sender as? statButton).buttonIndex {
destinationVC.statTitle = String(index) //this is supposing that destinationVC.statTitle expects a String
}
P.S: It is a convention in Swift to have the names of classes with an uppercase first letter:
class statButton: UIButton { ... }

Related

Pass variable returned Object via Segue depending on which button pressed

I have a WorkoutGenerator struct which returns different workouts based on different parameters, for example generateWorkout.standardWorkout returns something different to generateWorkout.hardWorkout.
I have 3 buttons on a 'Workout Setup' page, each meant to pass a different type of workout to the 'Workout Page' (there is also an input field for 'workout time').
I have currently have one segue goToWorkout from "Workout Setup Page" to "Workout Page"
What I want to do, is trigger the segue to the workout page, passing the different workouts depending on what the user presses.
So far I have :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//currently have one segue between the pages
if segue.identifier == "goToWorkout" {
let finalTimeForWorkout = Int(timeInputField.text!)
//set a standardWorkout to pass to Workout Page
let finalWorkout = FinalWorkout(generatedWorkout: WorkoutGenerator.standardWorkout.generate(), timeForWorkout: finalTimeForWorkout!)
//set the final parameters ready to pass
let finalWorkoutTime = finalWorkout.timeForWorkout
let finalWorkoutExercises = finalWorkout.generatedWorkout.workoutExercises
if let destVC = segue.destination as? WorkoutController {
destVC.selectedWorkoutExerciseArray = finalWorkoutExercises
destVC.selectedWorkoutTime = finalWorkoutTime
}
}
}
and then something like this for each button :
//use this button to pass a standard workout
//want to pass a diff workout if a diff button pressed
#IBAction func standardWorkoutPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToWorkout", sender: self )
}
My problem after playing around with it for a few hours is how to elegantly pass a different workout to the workout page.
i.e. I guess I could literally just copy and paste all the code for each button and make a new segue for each but that seems like the wrong way to do it!
The sort of thing I've tried is defining the workouts as variables in an if else if section but then the final workouts are out of scope for the segue.
Hope this makes sense, answers I can find about conditional segues seem to mostly refer to 'only allow the segue to happen under this condition' rather than pass different data sets to the same destination. e.g. example1 and example2
I'll add my comment as an answer instead, to make it easier to show some code examples.
Add a property to your viewcontroller:
var selectedWorkout : FinalWorkout!
in each of your three button action methods you set this property to the workout associated with each button. So for your standard workout:
#IBAction func standardWorkoutPressed(_ sender: UIButton) {
let finalTimeForWorkout = Int(timeInputField.text!)
self.selectedWorkout = FinalWorkout(generatedWorkout: WorkoutGenerator.standardWorkout.generate(), timeForWorkout: finalTimeForWorkout!)
performSegue(withIdentifier: "goToWorkout", sender: self )
}
Finally:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
let finalWorkoutTime = selectedWorkout.timeForWorkout
let finalWorkoutExercises = selectedWorkout.generatedWorkout.workoutExercises
if let destVC = segue.destination as? WorkoutController {
destVC.selectedWorkoutExerciseArray = finalWorkoutExercises
destVC.selectedWorkoutTime = finalWorkoutTime
}
}
}
In performSegue(withIdentifier:sender:), sender can be anything you like.
Use sender to pass the generated workout to performSegue:
//use this button to pass a standard workout
//want to pass a diff workout if a diff button pressed
#IBAction func standardWorkoutPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToWorkout", sender: WorkoutGenerator.standardWorkout.generate() )
}
Then in prepare(for:sender:)
let finalWorkout = FinalWorkout(generatedWorkout: sender as! YourWorkoutGeneratorType, timeForWorkout: finalTimeForWorkout!)
If I have this right, you want to pass a different argument into your FinalWorkout() function based on which button was pressed?
It doesn't always make for very readable code, but you can use the UIButton tag property. If you set each button's tag property to a unique value [0, 1, 2], you can use that info to generate a different workout:
// This code would be in: override func prepare(for segue: UIStoryboardSegue, sender: Any?)
let buttonTag = (sender as! UIButton).tag
if buttonTag == 0 { // Generate type of workout}
else if buttonTag == 1 { // Generate type of workout }
else if buttonTag == 2 { // Generate type of workout }
Or if you're worried about tags not being an intuitive representation, you could just test for equality with the IBOutlet reference you have for each button:
let buttonPressed = sender as! UIButton
if (buttonPressed == self.yourButtonIBOutletPropertyName) { // select a workout for this button}
I may have missed what you were asking, if this is the case comment and I'll update the answer.

Segue to chat viewcontroller

I have included images to hopefully make this easier to understand. My FirstViewController has a collection view with a list of users from my firebase database.When I click on the users I am segued to a DetailedViewController that has more information about the user that was clicked. Within that viewController, the goal is to click on the compose button and segue to a viewController that allows me to chat with the user, whose profile I clicked on.
I have gotten as far as this for segueing from DetailedViewController to Message user 1.
#IBAction func SendMessage(_ sender: Any) {
performSegue(withIdentifier: "chat", sender: self)
}
I am not sure how to make sure I am sending the particular user I click on a message.
This is how I am passing data from FirstViewController to DetailedViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
if let indexPaths = self.CollectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! BookDetailsViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.CollectionView!.indexPath(for: cell)
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
let Booked = post["title"] as? String
vc.Booked = Booked
print(indexPath?.row)
} }
One route to take is in your DetailViewController class, or whatever class you have implementing performSegue(withIdentifier:, sender:), add this:
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "chat", let dest = segue.destination as? ChatViewController else {
return
}
dest.user = self.user
}
I don't know how you have the user declared but if user is an object, do what I have above. If you're saying user as a way to umbrella multiple properties you want to pass.. do this:
dest.name = self.name
dest.number = self.number
//etc
prepare(for segue:, sender:) allows you to intercept any segue from it's class and set up whatever variables needed before the performSegue(...) executes. To target your code to a specific segue/destination/situation, make sure your code runs a check on those constraints before executing; otherwise the code will execute on all segues implemented in the class. In my example, I used your segue's identifier as that check.

Set value in random ViewController (random segue)

I have 12 ViewControllers. My code gives a random segue to one of these ViewControllers (1-12)
let segues = ["View1", "View2", "View3", "View4", "View5", "View6", "View7", "View8", "View9", "View10", "View11", "View12"]
let index = Int(arc4random_uniform(UInt32(segues.count)))
let segueName = segues[index]
self.performSegueWithIdentifier(segueName, sender: self)
.
Now, I want to change a variable in the random ViewController that has been chosen (var firstSegue = false) but I can't figure out how?
.
Could someone change this into something that will work?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as UIViewController
destinationVC.firstSegue = true
}
Make a protocol with the firstSegue property and extend that to all of your view controllers. Then use as? TheProtocolYouMade instead of as UIViewController
Or use a common sub-class.
EDIT: sample code
Make a protocol with the firstSegue property
protocol P {
var firstSegue: Bool { get set }
}
extend that to all of your view controllers.
extension YOURViewController1: P {
}
It is assumed that YOURViewController1 has a var firstSegue: Bool in it. Now that property is how YOURViewController1 conforms to protocol P. Do this for all view controllers that have that property.
Now you can write this code
if let asP = segue.destinationViewController as? P {
// you can access asP.firstSegue here
}
As this controller has property that's not from standard UIViewController, than it's some custom controller, so you can cast it and than fill this value. As well you can use KVC - which is not really safe solution.

How can i create a segue that pass data to another view controller only if certain criteria are met

I want to create a segue to pass data to another view controller but there are certain criteria that must happen for the segue to happen. If possible i would prefer to use the segue Id instead of the dragging method.
this is an example Im trying to accomplish
#IBAction func SubmitButtonPressed(sender: AnyObject) {
if 1<0 {
// dont perform segue
}else{
//Perform segue
// i want to pass this data in the next VC
var data = "foo"
//this is my segue id i want o use to go to the Second VC
var segueId = "segueForgotPasswordTwo"
// second VC
var secondVc = "viewController2"
// Iwant to to use prepare for segue but im getting errors in the parameters
prepareForSegue(UIStoryboardSegue, sender: AnyObject?){
}
}
}
Your question is a bit unclear but I believe this is what you are looking for...
func someFunction(){
if //some condition {
//code
}else if //some condition {
//code
} else {
//perform segue by using the folowing line. Assign the identifier to the segue in the storyboard.
//Do this by first creating a segue by dragging from a view controller to the destination view controller. Be sure to drag from the VIEWCONTROLLER, to the destination VIEWCONTROLLER. DO NOT just drag from the button. Next, choose the type of segue (eg. show or present modally), and then type in an indentifier for this segue.
performSegueWithIdentifier("SegueIdentifier", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueIdentifier" {
//now find your view controller that you are seguing to.
let controller = segue.destinationViewController as! SomeViewController
//access the properties of your viewController and set them as desired. this is how you will pass data along
controller.property = someValue
}
}
Overview:
Hook the segue from the source view controller to the destination view controller (see left side red arrows)
Don’t hook it from the button to the destination view controller
Create an action for the button to do your custom condition check then perform segue
Screenshot:
Code:
var data = "foo"
#IBAction func buttonPressed(sender: UIButton) {
let someCondition = true
if someCondition {
performSegueWithIdentifier("showGreen", sender: self)
}
else {
performSegueWithIdentifier("showPink", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showGreen" {
let greenVC = segue.destinationViewController as! GreenViewController
// Make sure the data variable exists in GreenViewController
greenVC.data = data
}
}
You can implement the shouldPerformSegueWithIdentifier function in your ViewController. When the segue is triggered, this function can cancel the segue if it returns false, so you can simply include whatever logic is required in this function and return true/false as appropriate.

Swift segue by identifer causing EXC_BAD_ACCESS

I have a navigation controller as the initial view. It goes to VC1 which has 3 buttons, and each button is tied to an action that sets a parameter in VC2 before invoking performSegueWithIdentifier("goNow", sender: nil). I have this in my VC1 too.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "goNow") {
if let sView: SViewController = segue.destinationViewController as? SViewController {
sView.action = self.action
}
}
}
When invoked, VC2 appears for a few seconds and it terminates with EXC_BAD_ACCESS.
I am not extremely familiar with Swift, but I know that there are some questions when attempting to compare strings. Instead of using:if (segue.identifier == "goNow")
~ edit ~
Setting the class in Interface Builder (storyboard) for the destination view controller.
Create a String Variable using the segue.identifier value and compare the two like this:
let segueID = String(segue.identifier)
let segueToOpen = "goNow"
let isEqual = (segueID == segueToOpen)
// Now your if statement
if isEqual {
// ...
}
Also, you might want to consider separating the logic in the next if statement to read something like:
let sView: SViewController = segue.destinationViewController
Here is an excerpt from RayWenderlick's site and using the as operator:

Resources