Incorrect data passed on first click of button - ios

Salutations,
My problem is as follows, I have an app I am working on which contains two views. The first simply has 2 buttons labeled "Slide Show One", and "Slide Show Two".
When I click on the first button, it displays the information for the second slideshow as (due to my supreme novice-ness), I select which of the slideshows to select via a boolean as follows:
var button : Bool = false;
then:
#IBAction func slideShowOne() {
button = true;
}
#IBAction func slideShowTwo() {
print("clicked button 2");
button = false;
}
Finally in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let passedPic = segue.destination as! ViewControllerTwo;
if(button == true) {
print(button);
passedPic.picsChosen = sLS1;
}
if(button == false) {
print(button);
passedPic.picsChosen = sLS2;
}
}
Only way to have it display the correct information is by clicking on say button 1 for btn1 slideshow, going back, then clicking on button 1 again. Why is this, does it have anything to do with how swift handles function calls?
Aside: Swift knowledge is now a grand total of one week.
EDIT: Got it working, much appreciated. Now a quick question, at this time my two IBActions are empty, but required since they have segues attached to them, would be the best way to either make them meaningful, or perhaps still be able to segue; I assume the prepare function is an absolute must, otherwise there is no way (of which I know), to send the required data to my second VC.
#IBAction func slideShowOne() {
//self.performSegue(withIdentifier: "slideOne", sender: self);
}
#IBAction func slideShowTwo() {
//self.performSegue(withIdentifier: "slideTwo", sender: self);
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let passedPic = segue.destination as! ViewControllerTwo;
if(segue.identifier == "slideOne") {
passedPic.picsChosen = sLS1;
} else {
passedPic.picsChosen = sLS2;
}
}

Try connecting view controller to view controller via storyboard - same ctrl dragging but whole view controller to other.
Give that segue identifier -> select identifier and on forth tab give it identfiier.
After that in one of your buttons, eg:
#IBAction func slideShowOne() {
button = true;
self.performSegue(withIdentifier: "toTheOtherVC", sender: self)
}
And you will have desired behaviour.

The reason is that: override func prepare(for segue: UIStoryboardSegue, sender: Any?) always be executed firstly.
So the actual workflow is as following:
-> 1) Initialize first view
-> 2) var button = false
-> 3) the user taps btn1
-> 4) func prepare is called
-> 5) second view is displayed (var button is still false at this moment, that's why you get incorrect response)
-> 6) func slideShowOne() is called (the user already is in second view)
-> 7) var button = true caused by func slideShowOne() (the user already is in second view)
-> 8) the user goes back
-> 9) the user taps btn1 again
-> 10) func prepare is called
-> 11) second view is displayed (the user gets correct response because value of var button has been changed in step7)
Let me know if you have any other questions. Have fun. : )

Related

Data turns Nil when passing from previous View Controller going to another View Controller using Segue

I am about to pass data from a ViewController going to another ViewController using segue. When checking the data(event) from a variable thru breakpoint in the 1st View Controller the data(event) is not nil. But when I checked the 2nd View Controller the data(event) is nil. I am confuse whether if the reason is, I have error in my codes or because of the error appeared in my console that says Unable to insert COPY_SEND. Hope I can get some help from you. Thank you
Segue from First View Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.self.event = event
dashBoardController.self.passcode = passcode
}
}
Event and Passcode Turns Nil
override func viewDidLoad() {
super.viewDidLoad()
guard event != nil, passcode != nil else {
_ = SCLAlertView(appearance: appearance).showError("No Event Details", subTitle: "There's no event details, please logout and try again")
return
}
showEventDetails()
}
showEventDetails
func showEventDetails() {
DispatchQueue.main.async{
self.eventNameLabel.text = "\(self.event.name.uppercased())"
self.locationLabel.text = "\(self.event.location.uppercased())"
if let dateStringFromDate = getFormattedStringFromDate(date: (self.event.startDateTime), formatString: "MMMM dd, yyyy/ hh:mm a") {
self.dateTimeLabel.text = dateStringFromDate
} else {
self.dateTimeLabel.text = "-"
}
}
}
I am assuming you linked the segue which goes to DashBoardViewController on your submitButton by Interface Builder, which means when you are tapping on the submit button, the #IBAction func submitButton(_ sender: UIButton) { } gets called, where you check if your passcode is good to go and if so you are calling validateEventPasscode() which calls an API endpoint (asynchronous) and only there you are populating the self.event = event (line 187 in ViewController.swift).
Now, what really happens is that when you link a segue from a button by IB (interface builder), there will be a perform segue internally which we have to stop by overriding the following method in ViewController.swift: source
func shouldPerformSegue(withIdentifier identifier: String,
sender: Any?) -> Bool {
return false
}
This way your call from line 190 - ViewController.swift:
self.performSegue(withIdentifier: "showEventDashboard", sender: self)
is the one that fires:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.event = event
dashBoardController.passcode = passcode
}
}
You can test my theory by placing three breakpoints in ViewController.swift:
line 134 at validateEventPasscode() from submitButton IBAction func;
line 190 at self.performSegue(withIdentifier: "showEventDashboard", sender: self) from validateEventPasscode() func;
line 108 at dashBoardController.event = event from prepare(for segue, sender) func;
Buggy order of execution: 1, 3, 2 - at the moment this I would expect if my theory is correct;
Desired order of execution: 1, 2, 3.
Long story short, you populate your self.event after you perfomSegue and go to the next screen, that's why your event is nil in the next VC.
I used as reference the ViewController.swift file from your repo: ViewController.swift
Hope it helps, cheers!
Replace your prepareForSegue method in FirstViewController with this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DashBoardViewController" {
let dashBoardController = segue.destination as! DashBoardViewController
dashBoardController.event = event
dashBoardController.passcode = passcode
}
}
you don't need to write
dashBoardController.self.event = event
dashBoardController.self.passcode = passcode
Just remove self from above two lines.

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.

check textfield has value before redirecting to another view - swift

I've been learning swift for the past 3 hours, so far so good I'm currently passing hard coded data from one view to another. The issue I'm trying to resolve is the following.
I have one text field on my view, if the user has entered data into that field then I allow them to navigate to the next page after the button has been pressed, if the textfield is empty then I display an error message, seems simple right? Well I can't figure this one out unfortunately, On the button I created a push segue method so once clicked it will go from view one to view two, now I have conditioned this function to check if the textfield is empty if so don't redirect however when I click the button I see the error message but it still redirects? this is my current code:
#IBOutlet var txtfield1: UITextField!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if txtfield1.text.isEmpty
{
nameLabel.text = "error don't redirect"
}
else{
let newVC: SecondViewController = segue.destinationViewController as! SecondViewController
let passedPhrase = "redirect to new controller / view"
newVC.receivedPhrase = passedPhrase
}
}
Can anyone shed any light into why this is happening?
You code is right, but you need to override this function shouldPerformSegueWithIdentifier.
You need to use this to prevent performSegueWithIdentifier if your text is empty
override func shouldPerformSegueWithIdentifier(identifier: String?,sender: AnyObject?) -> Bool {
if txtfield1.text.isEmpty {
return false
}
return true
}
prepareForSegue notifies the view controller that a segue is about to be performed.
Use shouldPerformSegueWithIdentifierto determines whether the segue with the specified identifier should be performed.
So you should check if text is empty in shouldPerformSegueWithIdentifier
override func shouldPerformSegueWithIdentifier(identifier: String,sender: AnyObject?) -> Bool {
if txtfield1.text.isEmpty {
return false
print("empty")
} else{
return true
}
}

segue getting twise call in swift

I have two view Controller one is ScheduleController and other is EditShiftController. In ScheduleController I have a table view name is scheduleTable. In the scheduleTable I have a cell with identifier ScheduleWithData. In this cell i have two different button Button EDIT and Button DELETE. i have directly connected button EDIT with EditShiftController with segue identifier editShiftSegue.
The Code of TableViewCell.swift
var editShiftClick: (() -> Void)? = nil
#IBAction func editShiftBtn(sender: AnyObject) {
if let onButtonTapped = self.editShiftClick {
onButtonTapped()
}
}
The Code of ScheduleController.swift
myCell!.editShiftClick = {
self.performSegueWithIdentifier("editShiftSegue", sender: dataTemp[indexPath.row].id)
}
The function for prepareSegue in ScheduleController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "editShiftSegue") {
print("senderIndexPath=\(sender)")
if (sender != nil) {
let svc = segue.destinationViewController as! EditShiftViewController;
svc.label = String(sender)
}
}
}
Snip Shot of Segue
enter image description here
When I click on EDIT button then EditShiftController getting twice call I don't know why
please, can any one help me here Thank you in Advance
myCell!.editShiftClick = {
//self.performSegueWithIdentifier("editShiftSegue", sender: dataTemp[indexPath.row].id)
}
Comment self.performSegueWithIdentifier("editShiftSegue", sender: dataTemp[indexPath.row].id) this line because if you have connected edit button directly with segue then no need to performsegue programatically also!!
Update:
myCell!.editShiftClick = {
self.performSegueWithIdentifier("editShiftSegue", sender: self)
}
don't comment that line and delete that segue which is given from edit button. give segue from viewcontroller to viewcontroller and give identifier editShiftSegue and then perform segue as above code.

Unwind Segue using performSegueWithIdentifier - Doesn't work - Swift 2.1

I am trying to unwind in my button click, after receiving response.
LoginController
#IBAction func SignInPressed(sender: AnyObject) {
if (onSuccess) {
performSegueWithIdentifier("unwindToGlobal", sender: AnyObject?())
}
}
and this is where it comes from (GlobalController).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if (segue.identifier == "globalToLogin")
{
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
let loginController = segue.destinationViewController as? LoginController
})
}
}
This one works and as soon as I enter the app (GlobalController is the starting view), I get directed to Login Page. However, when I click on the login button on LoginController, unfortunately it doesn't unwind.
When I try to remove these connections and ctrl + drag button to exit, it just doesn't do anything.
I couldn't find what I am doing wrong, thus it's not working. What am I missing?
Here a fews of articles about Unwind, that was super for me, maybe that can help
Unwind

Resources