Why doesn't performSegue() call shouldPerformSegue() - swift

Quite new to programming in Swift and mobile development in general. I am trying to use performSegue() and control it without if-else statements. I made a google search how to use override func shouldPerformSegue() and tried to implement it in different ways but none of them solved my situation. Here I need to segue to Yellow or Green views if the switch is on using corresponding buttons. Even if I made return false, the function does not cancel the segue to happen. What is the reason of this behaviour and how can I fix it? Many thanks.
class ViewController: UIViewController {
#IBOutlet var segueSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func yellowButtonTapped(_ button: UIButton) {
performSegue(withIdentifier: "Yellow", sender: nil)
}
#IBAction func greenButtonTapped(_ button: UIButton) {
performSegue(withIdentifier: "Green", sender: nil)
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
// return segueSwitch.isOn
return false
}
}

shouldPerformSegue does not get called if you use performSegue(withIdentifier: in code. This function only get called if the segue is triggered from storyboard.
You can easily test this by adding 2 Buttons to a ViewController in storyboard one with the storyboard segue and one with an IBOutlet to a new ViewController. Then add a print statement to shouldPerformSegue. In the Button outlet call performSegue. Only the first button performing the segue from storyboard will print to the console.
And additionally you don´t need it. Any validation you would perform in shouldPerformSegue can be done in the ...ButtonTapped function:
#IBAction func yellowButtonTapped(_ button: UIButton) {
if validateSegue(){
performSegue(withIdentifier: "Yellow", sender: nil)
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
validateSegue()
}
func validateSegue() -> Bool{
......
}

This is deliberate and makes perfect sense. shouldPerformSegue lets you prevent an automatically triggered segue in case you don't want it performed. But if you didn't want a manual segue performed, all you had to do is nothing — don't say performSegue in the first place.
and control it without if-else statements
If-else is exactly how to control it.

Related

How to disable a button within a anyObject Button in swift

Ok so I want to be able to disable the button within itself. So when I press the button it will disable itself.
import UIKit
class ViewController: UIViewController {
#IBOutlet var buttons: [UIButton]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func but(_ sender: AnyObject) {
//this code doesn't seem to work at all, and I don't know why
(sender as AnyObject).isEnabled = false
}
}
It gives me an error message when ever I try to do this "Cannot assign to immutable expression of type '#lvalue Bool?'"
So is there a different way I can disable this button
Presumably, you've hooked up this IBAction in Interface Builder to a UIButton. So, you can change your function signature to:
#IBAction func but(_ sender: UIButton) {
sender.isEnabled = false
}
AnyObject doesn't have a property called isEnabled, which is why your previous code failed.
within the button I use this code
#IBAction func but(_ sender: AnyObject) {
var disableMyButton = sender as? UIButton
disableMyButton?.isEnabled = false
}

Use closure to pass data between two controllers

my problem looks so simple, but since I am a beginner, I have problem to understand the concept of the closure to pass data between two controllers
for example I have a static table view controllers that has one cell and a title inside it
class FirstView: UITableViewController {
#IBOutlet weak var titleLabel: UILabel!
and I have an another view controller that contain a button inside it
class SecondViewController: UIViewController {
#IBAction func pressChangeButton(_ sender: UIButton) {
}
and there is segue1 between these two controllers, with identifier "segue1"
I want a to a simple task, I want to add a boolean closure that it will be true if the change button is pressed.
that is why I create a closure function the second view controller that has change button.
var change : ((Bool) -> Void)?
I just want, that the second view controllers tells the first one that change closure is now true (after pressing the change button) and the first view controllers simply change the title table inside it to whatever (I just want to see that how it can be done)
I don't know I have to use prepare sugue function?
Could anyone help me to understand this concept?
You can try
class FirstView: UITableViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBAction func goToSecond(_ sender: UIButton) {
self.performSegue(withIdentifier: "segue1", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue1" {
let des = segue.destination as! SecondViewController
des.change = { [weak self] (value) in
print(value)
self?.titleLabel.text = "SetValue"// set a value
}
}
}
}
class SecondViewController: UIViewController {
var change : ((Bool) -> Void)?
#IBAction func pressChangeButton(_ sender: UIButton) {
change?(true)
}
}
A closure is basically a piece of code that you can run. In Swift a closure is a first class citizen as it can be passed around as parameters and return type of functions. That being said, you can pass or set a closure as you normally would for other objects.
As per Sh_Kan's answer, just set SecondViewController's closure in prepare(for segue:sender:), always paying extra attention to retain cycles. You might also want to take a look at delegate design pattern in order to exchange data and messages between your controllers.

How can I use a segue when I have two buttons that need to be pressed to activate it?

Basically, what I want to do is activate a certain segue when the user presses 2 different buttons. Unfortunately, I only know how to do it when 1 button is pressed. How can I achieve this? Thanks in advance!
I'm assuming you're supposed to press these buttons sequentially.
Create 2 boolean variables in your ViewController and 2 IBAction methods, one for each button.
When button A or B is pressed, set the corresponding boolean to true.
When the second button (either A or B) is pressed, check if the other button's boolean variable is true. If yes, trigger the segue programmatically.
You can create this segue between the two view controllers in the storyboard. Remember to give the segue a fitting name.
Here's some example code.
class MyViewController: UIViewController {
var buttonAIsTapped: Bool = false
var buttonBIsTapped: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonATapped(_ sender: UIButton) {
buttonAIsTapped = true
if buttonBIsTapped == true {
self.performSegue(withIdentifier: "SegueName", sender: self)
}
#IBAction func buttonBTapped(_ sender: UIButton) {
buttonBIsTapped = true
if buttonAIsTapped == true {
self.performSegue(withIdentifier: "SegueName", sender: self)
}
}

Value not passing between viewControllers

Struggling to get my viewControllers to send value from the main viewController to a second. I want it to happen on a button click, I'm going to get the value from the button and pass it to the new form. But it just isn't working.
Code for main ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func butClick(_ sender: UIButton) {
NSLog("Button Pressed : %#",[sender .currentTitle])
//var tt = [sender .currentTitle]
// Create the view controller
let vc = TimesTablesViewController(nibName: "TimesTablesViewController", bundle: nil)
vc.passedValue = "xx"
self.performSegue(withIdentifier: "pushSegue", sender: nil)
}
}
Code for second viewController called TimesTablesViewController:
class TimesTablesViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
var passedValue:String = ""
override func viewDidLoad() {
super.viewDidLoad()
titleLabel?.text = "\(passedValue) Times Table"
}
}
I've followed tutorials but can't seem to solve the problem! Thanks for any help!
Replace
self.performSegue(withIdentifier: "pushSegue", sender: nil)
with
self.present(vc,animated:true,completion:nil)
or ( if the current vc is inside a naigation )
self.navigationController?.pushViewController(vc,animated:true)
Using
self.performSegue(withIdentifier: "pushSegue", sender: nil)
is fit with storyboards not xibs and if this your case then you need to use the above line only inside the button action with implementing this method inside the source vc
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pushSegue" {
if let nextViewController = segue.destination as? TimesTablesViewController{
nextViewController.passedValue = "xx"
}
}
}
I’m assuming that the new view controller is appearing, but you’re simply not seeing the data. If so, you’re evidently using storyboards. The TimesTablesViewController(nibName:bundle:) only works if you’re using XIB/NIBs and manually presenting new view controller.
If you’re really using storyboards, simplify your butClick method:
#IBAction func butClick(_ sender: UIButton) {
NSLog("Button Pressed")
performSegue(withIdentifier: "pushSegue", sender: self)
}
But implement prepare(for:sender:):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? TimesTablesViewController {
destination.passedValue = "xx"
}
}
Assuming the above fixes your problem, I might suggest a further simplification. Notably, if your butClick(_:) method is really only calling performSegue, you can segue to this next scene without any #IBAction method at all:
remove butClick(_:) entirely;
remove the connection between the button and the butClick method in IB, on the “Connections Inspector” tab in the right panel; and
control-drag from the button previously hooked up to butClick(_:) to the scene for TimesTablesViewController.
That will simplify your code further.

unwind segue not triggering

I have been learning swift and have made the foundation of most of my app. I have the following storyboard
app storyboard
Everything works fine. For example, I have an unwind segue on the add course view controller that triggers when you press save and you are returned to the 'your courses' view controller.
When you are on the my courses view controller, you can select a course and the topics are displayed, you can then select a topic and you are taken to an update score view controller, this all works fine.
However, my problem is this. I want to make it so that when you select save in the updatescore view controller, an unwind segue is triggered (the same as in the add course) and you are returned to the list of topics in the topics view controller.
However, I have followed many tutorials and obviously got it working before. (My action method for the unwind segue is in the correct topics view controller) but when i press save, the unwind segue is not returning me to the topics view controller.
Could anyone suggest a reason for this? I have spent a lot of time trying to find an answer and gone through many tutorials but have not managed to solve it.
I have also included a screen shot of the connections of the triggered segues for my save button to show that it is set up. Showing triggered segue for save button
i have the following code in the update score view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if saveButton === sender {
print("save button selected")
}
}
But even this is not getting triggered when I click on save.
Many thanks
UPDATE:
After following Ronatorys advice My view controller for the update score is as follows but it is still not working:
import UIKit
class UpdateScoreTableViewController: UITableViewController {
#IBOutlet weak var topicGettingUpdated: UITextField!
#IBOutlet weak var newScore: UITextField!
#IBOutlet weak var saveButton: UIBarButtonItem!
var index:Int?
var Topics:[String]!
var TopicToUpdate:String?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let uiBarButtonItem = sender as? UIBarButtonItem else {
print("There is no UIBarButtonItem sender")
return
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0 && indexPath.row == 0 {
newScore.becomeFirstResponder()
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
But the prepare for segue is not even getting triggered.
Like Craig in the comments said, it's not that easy to find the problem. So I just build a simple app where you can follow the steps as guide and see if you forgot something to setup the functionality right. Hope it will help you. Note: Code is in Swift 3.0, but should be easy to adopt to 2.*
1. Storyboard with two View Controllers:
2. Declare the action method for the unwind segue in the FirstViewController.swift:
class FirstViewController: UIViewController {
// action method for the unwind segue
#IBAction func updateScore(_ segue: UIStoryboardSegue) {
print("Back in the FirstViewController")
}
}
3. Connect the Save button in the Storyboard with the action method (with ctrl + drag):
4. Connect your Save button with the SecondViewController.swift file, to use it for checking in your prepareSegue method (with ctrl + drag):
5. Add the prepare(for:sender:) method to your SecondViewController.swift:
class SecondViewController: UIViewController {
#IBOutlet weak var saveButtonPressed: UIBarButtonItem!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// check safely with guard that your save button is the sender and you can use it
// if not print message
guard let uiBarButtonItem = sender as? UIBarButtonItem else {
print("There is no UIBarButtonItem sender")
return
}
// check if you selected the save button
if saveButtonPressed == uiBarButtonItem {
print("save button selected")
}
}
}
Result:
The sample app you can find here
I did not manage to get the unwind segue to work but instead used
navigationController!.popViewControllerAnimated(true)
as a work around and this works fine.