Nightmare on Segue street - ios

What the app should do:
1) The user types a word into a textField and taps the corresponding button
2) The app should segue to another scene containing two labels. One should display the word typed by the user, the other identifies the button tapped. These pieces of data should be passed via properties on the receiving ViewController.
What it actually does:
1) The segue is immediately invoked, apparently bypassing prepare(segue: sender:)
2) Both labels are blank
3) Breakpoints and print() indicate that prepare(segue: sender:) is never called
What I've checked/tried:
1) The buttons have the correct tags in storyboard, according to my print() statements
2) I've substituted if else for switch
3) I'm pretty sure I've read every associated question and answer on SO
4) Switched sender in prepare(segue: sender:) from Any? to UIButton and back again
The code
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var tf2: UITextField!
#IBOutlet weak var tf3: UITextField!
#IBOutlet weak var tf4: UITextField!
#IBAction func sendButton(_ sender: UIButton) {
print("sender.tag is \(sender.tag)")
self.performSegue(withIdentifier: "mySegue", sender: sender)
print("sender.tag is \(sender.tag)")
}
#IBAction func button2(_ sender: UIButton) {
print("sender.tag is \(sender.tag)")
self.performSegue(withIdentifier: "mySegue", sender: sender)
print("sender.tag is \(sender.tag)")
}
#IBAction func button3(_ sender: UIButton) {
print("sender.tag is \(sender.tag)")
self.performSegue(withIdentifier: "mySegue", sender: sender)
print("sender.tag is \(sender.tag)")
}
#IBAction func button4(_ sender: UIButton) {
print("sender.tag is \(sender.tag)")
self.performSegue(withIdentifier: "mySegue", sender: sender)
print("sender.tag is \(sender.tag)")
}
func prepare(for segue: UIStoryboardSegue, sender: UIButton) {
print("Inside prepare for segue")
print("sender.tag is \(sender.tag)")
if segue.identifier == "mySegue" {
let vc = segue.destination as! SecondViewController
print("sender.tag is \(sender.tag)")
switch sender.tag {
case 101:
if (textField.text?.count)! >= 1 {
vc.staticText = "Button 101"
vc.textProp = self.textField.text!
print("sender.tag is \(sender.tag)")
}
case 102:
if (tf2.text?.count)! >= 1 {
vc.staticText = "Button 102"
vc.textProp = self.tf2.text!
print("sender.tag is \(sender.tag)")
}
case 103:
if (tf3.text?.count)! >= 1 {
vc.staticText = "Button 103"
vc.textProp = self.tf3.text!
print("sender.tag is \(sender.tag)")
}
case 104:
if (tf4.text?.count)! >= 1 {
vc.staticText = "Button 104"
vc.textProp = self.tf4.text!
print("sender.tag is \(sender.tag)")
}
default:
print("Something went wrong")
}
// print("In FirstVC, vc.textProp = \(vc.textProp)")
}else{
print("No text in textField...")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I admit upfront that I'm pretty good at overlooking the obvious, but I did put in a lot of time before bugging you guys. Any help or direction will be very much appreciated...
Thanks!

prepare(for segue is not called because the signature is wrong. You must not change the type of the sender parameter.
Cast sender to the expected type in an extra line for example
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue",
let button = sender as? UIButton {
...
and replace all subsequent occurrences of sender with button

Related

How to get UIButton name in swift5?

I am currently making a pin-code. I want to incorporate all the functions into one function, in order to integrate the button event function into one So I want to get the name of UIButton, but I don't know how.
#IBOutlet weak var oneButton: UIButton!
#IBOutlet weak var twoButton: UIButton!
...
var pinCodeNum : String! = ""
...
#IBAction func OneButton(_ sender: UIButton) {
pincodeLogic(sender)
}
func pincodeLogic(_ sender: UIButton) {
// I want get value is (example : 'oneButton' or 'twoButton' or 'threeButton' more )
}
As you can see from my code, I'm getting a 'sender' as a parameter I want to know the name of oneButton or twoButton using this parameter. How do I know?
My number button consists of a button and a label.
EDit
#IBAction func OneButton(_ sender: UIButton) {
pincodeLogic(sender)
}
func pincodeLogic(_ sender: UIButton) {
if let number = sender.currentTitle {
print(number)
}
}
I can't see the print log.
You can compare the sender with your button instances.
func pincodeLogic(_ sender: UIButton) {
switch sender {
case oneButton:
print("oneButton pressed")
case twoButton:
print("twoButton pressed")
default:
print("unknown button pressed")
}
}
To access the contents of the label present in the button using the sender, this is an example:
#IBAction func OneButton(_ sender: UIButton) {
print(sender.titleLabel?.text)
}
so you could do this:
#IBAction func OneButton(_ sender: UIButton) {
pincodeLogic(sender)
}
func pincodeLogic(_ sender: UIButton) {
if let number = sender.titleLabel?.text {
print(number)
}
}
I hope I've been there for you. Let me know.
if you want to access the button action to perform some specific task.Just put a tag with your each button and add the same target to all.
#IBAction func btnAction(_ sender: UIButton) {
switch sender.tag {
case 1:
print("oneButton pressed")
case 2:
print("twoButton pressed")
default:
print("unknown button pressed")
}
}
If you need just to print the button title the do the following.
#IBAction func btnAction(_ sender: UIButton) {
print(sender.titleLabel?.text! as! String)
}
#IBAction func btnClick(_ sender: UIButton{
print(sender.titleLabel!.text!)
}

How do I pass specific text to a label depending on which UIButton is pressed?

I'm very new to coding and I'm having trouble passing data to a second view controller. I want to change a label in the second view controller depending on which button is pressed in the first (if you press art the label says "art museum", if you press rollercoaster the label says "theme park")
I have tried putting the override function into the brackets for the button but I got the error "override can only be specified on class members"
firstacCode
class firstacCode: UIViewController {
#IBAction func art(_ sender: Any) {
performSegue(withIdentifier: "artSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destVC : acCode = segue.destination as! acCode
destVC.descriptionLabel = "art museum"
}
acCode
class acCode: UIViewController {
var descriptionLabel :String = "";
#IBOutlet weak var descriptionText: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
descriptionText.text = descriptionLabel
// Do any additional setup after loading the view.
}
This worked for me for one button but I'm not sure how to add another button into the equation. Any help would be greatly appreciated.
First of all connect your buttons to the art function, from the attribute inspector give tags to the buttons.
Now your code will be look like this :
#IBAction func art(_ sender: UIButton) {
if sender.tag == 0{
performSegue(withIdentifier: "mySegue", sender: "art museum")
}
if sender.tag == 1{
performSegue(withIdentifier: "mySegue", sender: "Theme Park")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc : acCode = segue.destination as! acCode
vc.destVc = sender as! String
}
First off, welcome to Stackoverflow. You're already doing the passing of data to the other controller correctly. So your only problem now I guess is how to handle multiple buttons in your IBAction function.
Tip:
You can actually make this #IBAction func art(_ sender: Any) as #IBAction func art(_ sender: UIButton). Instead of Any, turn it into UIButton to avoid casting.
Assuming you connected multiple buttons into that art IBAction function, you can catch that specific button object through your sender and pass that sender to the sender parameter of your prepareForSegue, or in your case, I just realized that you might have different function for your two buttons, just pass the string to that sender parameter.
So your code would be like so:
#IBAction func art(_ sender: Any) {
performSegue(withIdentifier: "artSegue", sender: "art museum")
}
#IBAction func rollercoaster(_ sender: Any) {
performSegue(withIdentifier: "artSegue", sender: "theme park")
}
and in your prepareForSegue, cast the sender to String.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destVC : acCode = segue.destination as! acCode
destVC.descriptionLabel = sender as! String
}
#IBAction func art(_ sender: Any) {
sender.tag = 1
performSegue(withIdentifier: "artSegue", sender: self)
}
#IBAction func art2(_ sender: Any) {
sender.tag = 2
performSegue(withIdentifier: "art2Segue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(sender.tag == 1){
let destVC : acCode = segue.destination as! acCode
destVC.descriptionLabel = "art museum"
}
if(sender.tag == 2){
let destVC : acCode = segue.destination as! acCode
destVC.descriptionLabel = "art2 museum"
}
}

UITableView add row from other Viewcontroller

I have two view controllers one with the 3 tableviews and other controller I have a uitextfield and the text entered in the text field I want to add it to one of the tableview called ScheduleTableView in the other view controller.
Here is my code but I get the error unexpectedly found nil while unwrapping an optional value at vc.ScheduleTableView.beginUpdates()
#IBAction func addButtonTapped(_ sender: YTRoundedButton) {
self.performSegue(withIdentifier: "secondvc", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondvc" {
print(TitleTextField.text!)
let vc = segue.destination as! GrowthMainViewController
vc.ScheduleArray.append(TitleTextField.text!)
let indexPath = IndexPath(row: vc.ScheduleArray.count - 1, section: 0)
vc.ScheduleTableView.beginUpdates()
vc.ScheduleTableView.insertRows(at: [indexPath], with: .automatic)
vc.ScheduleTableView.reloadData()
vc.ScheduleTableView.endUpdates()
TitleTextField.text = ""
view.endEditing(true)
}
}
The resolution for this was changing where the array was declared (singleton is fine for this), and removing the unnecessary inserting, updating, reloading, etc.
The numRowsInSection method then calls the scheduleArray.count to show all corresponding data.
Before:
#IBAction func addButtonTapped(_ sender: YTRoundedButton) {
self.performSegue(withIdentifier: "secondvc", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondvc" {
print(TitleTextField.text!)
let vc = segue.destination as! GrowthMainViewController
vc.ScheduleArray.append(TitleTextField.text!)
let indexPath = IndexPath(row: vc.ScheduleArray.count - 1, section: 0)
vc.ScheduleTableView.beginUpdates()
vc.ScheduleTableView.insertRows(at: [indexPath], with: .automatic)
vc.ScheduleTableView.reloadData()
vc.ScheduleTableView.endUpdates()
TitleTextField.text = ""
view.endEditing(true)
}
}
After :
#IBAction func addButtonTapped(_ sender: YTRoundedButton) {
self.performSegue(withIdentifier: "secondvc", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondvc" {
guard let text = TitleTextField.text else { return }
scheduleArray.append(text)
TitleTextField.text = ""
view.endEditing(true)
}
}
vc.ScheduleArray.count - 1 is probably a negative indexpath
try this
if (vc.ScheduleArray.count - 1 >= 0){
vc.ScheduleTableView.insertRows(at: [indexPath], with: .automatic)
}
The problem is that you trying to update a view controller inside of prepare function. Inside that function, the view is instantiated but it's Outlets are not connected yet.
To solve that issue, follow these steps:
On this method you should first update your model:
#IBAction func addButtonTapped(_ sender: YTRoundedButton) {
// update your model here
self.performSegue(withIdentifier: "secondvc", sender: self)
}
In your destination view controller, you should handle this model change and reload the table view's data.
override func viewDidLoad() {
self.tableView.reloadData()
}

Swift Multiple buttons sending String through same segue

Looking for some clarification and just to double check if I am on the right path. I have a category list where multiple buttons will send a specific string through a segue. I have the IBOutlets for each button but want to make sure when that specific button is touched that specific string is sent. I am just unsure if the way I am setting up the segue is correct so that each button is specific to the set strings. So far, the current segue works for "attractionsButton" but when I tap other buttons it passes the same data. I know it's not set, but want to make sure that when another button is tapped its not sending the wrong string.
#IBOutlet weak var attractionsButton: UIButton!
#IBOutlet weak var eatingButton: UIButton!
#IBOutlet weak var financialButton: UIButton!
#IBOutlet weak var lodgingButton: UIButton!
#IBOutlet weak var medicalButton: UIButton!
#IBOutlet weak var publicButton: UIButton!
#IBOutlet weak var servicesButton: UIButton!
#IBOutlet weak var storesButton: UIButton!
#IBOutlet weak var transportationButton: UIButton!
let attractions = "Attractions & Entertainment"
let eating = "Eating & Drinking"
var financial = "Financial Institution"
var lodging = "Lodging Establishment"
var medical = "Medical & Health"
var publicService = "Public Services & Buildings"
var services = "Service"
var stores = "Stores & Shopping"
var transportation = "Transportation"
#IBAction func attractionsButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func eatingButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func financialButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func lodgingButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func medicalButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func publicButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func serviceButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func storesButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
#IBAction func transportationButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "category" {
if let button1 = attractionsButton {
let user = attractions
let controller = segue.destination as? CategoryListedViewController
controller?.categoryList = user
}
}
}
change self in your action to sender, you can use this one action for all the buttons
#IBAction func transportationButton(_ sender: Any) {
performSegue(withIdentifier: "category", sender: sender)
}
use this code in your prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "category" {
let senderButton = sender as! UIButton
switch senderButton{
case attractionsButton:
let user = attractions
let controller = segue.destination as? CategoryListedViewController
controller?.categoryList = user
case eatingButton:
//editing button scenario
print("editing button scenario")
default:
//default code
print("default scenario")
}
}
}
All of your buttons can connect to just this one #IBAction:
#IBAction func allButtons (_ sender: Any) {
performSegue(withIdentifier: "category", sender: sender)
}
Of course, if that is all the buttons are doing, you can skip using the #IBAction entirely and just wire the segues from the buttons directly. If you do that when creating the first button in the Storyboard, you can copy that button and all copies will be wired to the same segue.
Then in prepare(for:sender:), compare the sender to your #IBOutlets to set your string:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let button = sender as? UIButton,
let controller = segue.destination as? CategoryListedViewController,
segue.identifier == "category" {
let str: String
switch button {
case attractionsButton: str = attractions
case eatingButton: str = eating
case financialButton: str = financial
default: str = ""
}
controller.categoryList = str
}
}
If I were you I would use buttons tags and a good old enum to work this out:
First, in Interface Builder, you set the tag property of each of your buttons (and you don't even need to have them anymore as #IBOutlet)
1 for attractionsButton, 2 for eatingButton, etc.
Then, you create an enum, with raw value as Int with matching values :
enum Category : Int, CustomStringConvertible {
case attractions = 1
case eating = 2
case financial = 3
case lodging = 4
case medical = 5
case publicService = 6
case services = 7
case stores = 8
case transportation = 9
var description : String {
switch self {
case .attractions: return "Attractions & Entertainment"
case .eating: return "Eating & Drinking"
case .financial: return "Financial Institution"
case .lodging: return "Lodging Establishment"
case .medical: return "Medical & Health"
case .publicService: return "Public Services & Buildings"
case .services: return "Service"
case .stores: return "Stores & Shopping"
case .transportation: return "Transportation"
}
}
}
After that, you link all your buttons to only one #IBAction like this one:
#IBAction func onButtonTap(_ sender: UIButton) {
performSegue(withIdentifier: "category", sender: Category(rawValue: sender.tag))
}
This way, according to the tag of the button, you'll create the enum you need.
As an end, in your segue preparation, you can set things like that:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "category" {
guard
let controller = segue.destination as? CategoryListedViewController,
let category = sender as? Category
else { return }
controller.categoryList = category.description
}
}
This way, things are much more concise and you can attache more behaviors to your enum Category, use it in switches, and so on, instead of relying on Strings or multiplying copy&paste code.

Is it possible for a button to open into a specific tab when using the Tab Bar Controller?

The question is a little confusing to articulate so hopefully an image will be a little more helpful. Here's the setup:
So, I want the First View button to open the FirstViewController, and I want the Second View button to open the SecondViewController. If I link the Second View button to the SecondViewController like this:
I lose the tab navigation. And if I connect the button to the TabViewController like this:
then it will automatically open into the FirstViewController. Unless I'm missing something, it seems like this would need to be done with a little extra code but I cannot seem to find anything that explains how to do this.
Thank you!
Create storyboard segue from Viewcontroller to TabBarController with an identifier. Then assign selelctedIndex value of TabBarController in prepareforsegue method
#IBAction func firstBtnAction(_ sender: Any) {
(sender as! UIButton).tag = 0
performSegue(withIdentifier: "tabBar", sender: sender)
}
#IBAction func secBtnAction(_ sender: Any) {
(sender as! UIButton).tag = 1
performSegue(withIdentifier: "tabBar", sender: sender)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "tabBar" {
if let vc = segue.destination as? UITabBarController {
vc.selectedIndex = (sender as! UIButton).tag
}
}
}
In tabViewController you need to implement following functions..
var index : Int!
#IBAction func button1(_ sender: Any) {
index = 1
self.performSegue(withIdentifier: "TabBarSegue", sender: self)
}
#IBAction func button2(_ sender: Any) {
index = 2
self.performSegue(withIdentifier: "TabBarSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "TabBarSegue"){
let videoController : TabBarController = segue.destination as! TabBarController
videoController.index = index
}
Then you need to implement TabBarController and implement following..
import UIKit
class TabBarController: UITabBarController {
var index : Int!
override func viewDidLoad() {
super.viewDidLoad()
selectedIndex = index
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Resources