Swift 3 - Prepare Segue - ios

I have 3 scenes in my storyboard. My initial View Controller is a Navigation Controller, then there is a relationship root view controller to a UI ViewController (view controller a) and then I have a push segue from a button in the ViewController to the third ViewController (view controller b) in the scene. I have given the push segue an identifier. Now I am trying to prepare my segue in the 2nd view controller (view controller a) like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "HistorySegue" {
if let viewController = segue.destination as? HistoryController {
viewController.detailItem = barcodeInt as AnyObject
}
}
}
However when I run this code and push the button in controller a I get the following error:
fatal error: attempt to bridge an implicitly unwrapped optional containing nil
What am I doing wrong?

Replace your code with the following, it will not crash at least.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "HistorySegue" {
if let viewController = segue.destination as? HistoryController {
if(barcodeInt != nil){
viewController.detailItem = barcodeInt as AnyObject
}
}
}
}

It must be that barcodeInt is defined as an implicitly unwrapped optional, like:
var barcodeInt:Int!
In that case, if it is nil when assigning it to detailItem, because of the !, swift takes your word for it that there is a non-nil value in there and dereferences it. That's a runtime error. Your best bet is to avoid ! in code you write (it's ok to leave the Apple generated code for IBOutlets, for example) if at all possible and learn more about optionals before going back to implicitly unwrapped optionals. And then, still use them sparingly.
Safer code for your situation:
if let viewController = segue.destination as? HistoryController,
let barcodeInt = barcodeInt as? AnyObject {
viewController.detailItem = barcodeInt
} else {
NSLog("Error: expected barcodeInt to be set")
}

I had the same issue. The logic is that one first prepares the segue (loads the UIViewController referenced by the container view), assigns it to a variable, and then uses it in viewDidLoad(). This code should work:
Swift 4.2
// usually an IBoutlet
var viewController: HistoryController!
override func viewDidLoad() {
super.viewDidLoad()
viewController.detailItem = barcodeInt as AnyObject
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "HistorySegue"
{ viewController = segue.destination as! HistoryController }
}
detailItem could possibly be defined as an IBoutlet in HistoryController, it depends on the OP code. In my case, where I had two simple container views with a label each inside, this has been the final working code for the main view controller class:
import UIKit
class ViewController: UIViewController {
#IBOutlet var firstView: ReusableViewController!
#IBOutlet var secondView: ReusableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
firstView.myLabel.text = "My 1st reuse!!!"
secondView.myLabel.text = "And that's the 2nd!"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueFirstView")
{ firstView = (segue.destination as! ReusableViewController) }
if (segue.identifier == "segueSecondView")
{ secondView = (segue.destination as! ReusableViewController) }
}
}
With that I could finally change the text of the two different UILabel directly from the main controller!
For a detailed explanation of how to use the container views one may check this S.O. answer.

Related

does Prepare For Segue work when data send by second view controller

i'm new to ios development
few days back when i was learning how to send data from one VC to anotherVC then i used
override func prepare(for segue: UIStoryboardSegue, sender: self) {
if segue.identifire == "segue1" {
let data = segue.destinetion as! secondViewController
data.labelName = labelFirst.text
}
and now when in receiving data from secondVC then i also using almost same code
as i saw on web
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue2" {
let secondVC = segue.destination as! secondViewController
secondVC.delegate = self
}
}
so plz anyone can explain real quick why does it look same
or what's difference
To send data back from secondVC to firstVC rather use delegates. Based on the line "secondVC.delegate = self" that you wrote, you already read an article on this and might just need some additional info.
In secondVC put the following code outside of the class scope. for example above
class secondVC: UIViewController {}
The code to place there is
protocol secondVCDelegate {
func didFinishTask(returnData: String)
}
Put the following line inside of the class scope of secondVC
var delegate: secondVCDelegate?
Then in firstVC inherit the secondVC delegate by adding secondVCDelegate to the class. (e.g: class firstVC: UIViewController, secondVCDelegate {})
Then add the function to your firstVC
func didFinishTask(returnData: String) {
//Do something here with returnData
print(returnData)
}
Hope this helps!

Trouble passing variable through segue (Swift)

I have a variable called correctAnswers = 0 at the top of my program, I have a function where every time it is called it adds 1 to the correctAnswers value. I tested that the function was actually changing the variable by making it print after every +1 and sure enough it goes 1, 2, 3, 4 and so on. However I have an end screen that shows the results after the program has reached its limit of questions (10 in this case)
EDIT #2: Here is my prepare for segue function
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "EndScreenSegue") {
let secondViewController = segue.destination as! EndScreen
correctAnswers = sender as! Int
secondViewController.gso = self
}
}
And here is the code in my end screen:
var gso: GameScreen?
override func viewDidLoad() {
super.viewDidLoad()
gso = GameScreen()
print(gso?.correctAnswers ?? 100)
}
When I print correct Answers it is still 0.
EDIT #3 I also tried this method of passing it through a segue and it is always nil. Does the fact its not working have anything to do with the fact the variable is being changed in a function? thats the only reason I can possibly think of to explain why nothing is working.
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "EndScreenSegue") {
let secondViewController = segue.destination as! EndScreen
secondViewController.recievedAnswers = correctAnswers
}
}
class EndScreen: UIViewController{
var recievedAnswers: Int!
override func viewDidLoad() {
super.viewDidLoad()
print(recievedAnswers!)
}
}
EDIT #4 Figured it out! I think.
It appears they changed prepareForSegue and it no longer works, yet xcode does not give you an error? Should another post be made about this??
Code that actually works:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EndScreenSegue"{
let secondViewController = segue.destination as! EndScreen
secondViewController.recievedAnswers = correctAnswers
}
}
You're creating a new instance of GameScreen in the viewDidLoad method of your EndScreen view controller. You need to pass the GameScreen object from your previous view controller to this one.
In your segue you're setting the correctAnswers property of your EndScreen view controller, but you're printing the value of gso?.correctAnswers. A new gso was just instantiated prior to this print statement and (I'm assuming) has a default value of 0 which is why you're getting 0 printed out.
You either need to pass the GameScreen through the segue (assuming that you're segueing from GameScreen), like so:
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "EndScreenSegue") {
let secondViewController = segue.destination as! EndScreen
correctAnswers = sender as! Int
secondViewController.gso = self
}
}
Or you can just pass the correctAnswers through the segue as you're doing but be sure to use that value, not gso?.correctAnswers
class EndScreen: UIViewController{
var correctAnswers: Int?
override func viewDidLoad() {
super.viewDidLoad()
print(correctAnswers ?? 100)
}
}
You are creating a new instance of GameScreen.
Delete this line gso = GameScreen() from viewDidLoad
For some reason it looks like prepareForSegue no longer works, yet xcode does not give an error when you use it. The correct format now is:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EndScreenSegue"{
let secondViewController = segue.destination as! EndScreen
secondViewController.recievedAnswers = correctAnswers
}
}

Changing label text of second ViewController upon clicking button in first ViewController

I'm new to Swift and to iOS Development. I currently have 2 ViewControllers, a button in the first and a label in the second one. I've connected the first button to the second ViewController and the transition works.
Now, when I try changing the label's text I get the error:
fatal error: unexpectedly found nil while unwrapping an Optional
value
.
Here you find my prepare function in the first ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! SecondViewController
vc.secondResultLabel.text = "Testing"
}
}
Can it be that the label in the second ViewController is somehow protected ?
Thanks for the help
You need to pass the String to the SecondViewController instead of directing setting it, as the UILabel has not been created yet.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! SecondViewController
vc.secondResultLabelText = "Testing"
}
}
And in your SecondViewController viewDidLoad method set the UILabel to be the string
var secondResultLabelText : String!
override func viewDidLoad() {
secondResultLabelText.text = secondResultLabelText
}
add a string variable in the second view controller
var labelText: String!
in second view controller also (in viewDidLoad)
self.secondResultLabel.text = self.labelText
then first view controller prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! SecondViewController
vc.labelText = "Testing"
}
}
this is because second view controller's UILabel Outlet is not being initialized yet in prepare for segue
Rikh's answer is the same, both his answer and mine are the same
Welcome aboard :)
Your problem is that your SecondViewController, and more specifically vc.secondResultLabelText is not initiated when you call prepare, so secondResultLabel is actually nil at that time.
You need to add a variable to your SecondViewController like so:
var labelText: String = ""
And then set that value instead:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! SecondViewController
vc.labelText = "Testing"
}
}
In viewWillAppear or viewDidLoad on your SecondViewController you can then use that value for your secondResultLabelText which is now ready, connected, and won't crash
secondResultLabelText.text = labelText
Hope that helps.
First take a global variable in SecondViewController... eg I took "secondViewControllerVariable". Then get the text you want to display in your SecondViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "mySegue"
{
let vc = segue.destination as! SecondViewController
vc.secondViewControllerVariable = "Your string you get in FirstViewController"
}
}
And then in your SecondViewController, in viewDidLoad method set the UILabel to be the string
var secondViewControllerVariable : String! // You have to declare this first in your SecondViewController Globally
override func viewDidLoad()
{
vc.secondResultLabelText.text = secondViewControllerVariable
}
That's it. Happy Coding.

Set data in `prepareForSegue` with navigation controller

I am developing an iOS application in Swift.
I want to send data from a view to an other one, using the prepareForSegue function.
However, my target view is preceded by a navigation controller, so it doesn't work. How can I set data on the VC contained within the navigation controller?
In prepareForSegue access the target navigation controller, and then its top:
let destinationNavigationController = segue.destination as! UINavigationController
let targetController = destinationNavigationController.topViewController
From the target controller you can access its view and pass data.
In old - now obsolete - versions of Swift and UIKit the code was slightly different:
let destinationNavigationController = segue.destinationViewController as UINavigationController
let targetController = destinationNavigationController.topViewController
Prepare the segue in the SendViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueShowNavigation" {
if let destVC = segue.destination as? UINavigationController,
let targetController = destVC.topViewController as? ReceiveViewController {
targetController.data = "hello from ReceiveVC !"
}
}
}
Edit the identifier segue to "showNavigationController"
In your ReceiveViewController add
this
var data : String = ""
override func viewDidLoad() {
super.viewDidLoad()
print("data from ReceiveViewController is \(data)")
}
Of course you can send any other type of data (int, Bool, JSON ...)
Complete answer using optional binding and Swift 3 & 4:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? MyViewControllerClass {
myViewController.yourProperty = myProperty
}
}
Here is the answer for Swift 3:
let svc = segue.destination as? UINavigationController
let controller: MyController = svc?.topViewController as! MyController
controller.myProperty = "Hi there"
A one liner in Swift 3:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination.childViewControllers[0] as? FooController {
vc.variable = localvariable
}
}
In Swift 5
If you must not only segue from a SourceViewController to a DestinationViewController embedded in a UINavigationController, but also to a new Storyboard also, then do the following...
Place a "Storyboard Reference" object from your Object Library next to your source ViewController in Interface Builder, and then drag a segue to it (from a button on the SourceViewController view, for instance). Name the segue identifier "ToOtherStoryboard", for example.
Go to NavigationViewController and give it a Storyboard ID using the Identity Inspector. "DestinationNavVC" would do.
Click the Storyboard Reference icon you created in step 1, and in its attribute inspector's 'Referenced ID' field, enter the Storyboard ID you wrote for the UINavigationController in step 2. This creates the segue from source to the DestinationViewController no matter what you write in source file of the source ViewController. This is because seguing to a NaviationController will automatically show the root ViewController (the first one) of the UINavigationController.
(OPTIONAL) If you need to attach data along with your segue and send it to properties within the DestinationViewController, you would write the following code inside a Prepare-For-Segue method in your SourceViewController file:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToOtherStoryboard" {
let destinationNavVC = segue.destination as! UINavigationController
let destinationVC = destinationNavVC.topController as! DestinationViewController
destinationVC.name = nameTextField.text // for example
destinationVC.occupation = occupationTextField.text
}
}
You do not NEED to have a PrepareForSegue if you're simply trying to move from one ViewController to another, the methods above will work (w/o step 3)
In your IBAction Outlet method for your button you used to initiate the segue, you would write:
performSegue(withIdentifer: "ToOtherStoryboard", sender: self)
Set the identifier name in the segue arrow property in order to use in the the performeSegue.
Like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc: ProfileViewController = segue.destination as? ProfileViewController {
//do any setting to the next screen
}
}
and then:
performSegue(withIdentifier: "yourIdentifierOfViewProfile", sender: indexPath.row)
I hope it helps.
It's a good idea to skip the check for UINavigationController as there may be multiple segues that use a navigationController and so will go into that check for every segue that uses a navigationController. A better way is to check the first viewController of the children and cast it as the viewController you are looking for.
if let destVC = segue.destination.children.first as? MyViewController {
destVC.hideBottomBar = true
}

Swift passing arrays between 2 tableviews. Returns nil instead

I am pretty new to SWIFT coding. My intention is to pass 2 arrays with values from Viewcontroller1 to Viewcontroller2. But it returns me nil value in Viewcontroller2. Can someone advise me please?
Here is the partial code in ViewController1.
#IBAction func solve(sender: AnyObject) {
//pass information to the nextviewcontroller
func prepareForSegue ( segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "Solve") {
var svc = segue!.destinationViewController as ViewController2
svc.toPass = self.force
svc.toPass2 = stiffness
}
}
Here is the partial code in ViewController2.
class ViewController2: UITableViewController {
var toPass:[String]!
var toPass2:[String]!
override func viewDidLoad() {
super.viewDidLoad()
println(self.toPass)
println(self.toPass2)
// Do any additional setup after loading the view.
}
The full code can be found in here. https://github.com/cherrythia/SWIFTpassingarrays/blob/master/README.md
I have created project similar to yours (using your github link) and #Sebastian Wramba is correct. You have to separate these two functions:
#IBAction func solve_pressed(sender: AnyObject) {
self.performSegueWithIdentifier("Solve", sender: sender)
}
override func prepareForSegue ( segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "Solve") {
var svc = segue.destinationViewController as ViewController2
svc.toPass = self.force
svc.toPass2 = stiffness
}
}
Okay, solved (pun intended). You have to give your segue identifier, in this particular case, "Solve". In your first view controller, select push to View Controller2, go to the fourth tab and set identifier to "Solve".

Resources