Why does the value of my variable revert in Swift? - ios

I got my code working, but I don't know why it works.
Having this prepareForSegue Function doesn't work, and when my variable is called in the viewController that the prepareForSegue goes to, it returns nil.
The original VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == K.latinSegue {
if latinTextField.text != nil && latinTextField.text != "" {
guard let latinText = latinTextField.text else { fatalError("latinText broken")}
let destinationVC = LatinViewController()
destinationVC.latinText = latinText
}
}
}
The destinationVC:
var latinText: String?
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
let latinTranslator = LatinTranslator()
let latinDefinition = latinTranslator.getHTMLAndDefinition(latinText: **latinText!**)
textLabel.text = latinDefinition
}
latinText! Doesn't have a value when it is force unwrapped here, even though I set its value in the prepare for segue function.
Now, this code works. I kinda know how static variables work, so I'm just really confused as to why the previous code doesn't work - it throws a nil value not found error.
The originalVC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == K.latinSegue {
if latinTextField.text != nil && latinTextField.text != "" {
guard let latinText = latinTextField.text else { fatalError("latinText broken")}
LatinViewController.latinText = latinText
}
}
}
The destinationVC:
static var latinText: String!
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let latinTranslator = LatinTranslator()
let latinDefinition = latinTranslator.getHTMLAndDefinition(latinText: LatinViewController.latinText)
textLabel.text = latinDefinition
}
The segue is called when a button is pressed. I have checked, there is only one segue. Please help!

The issue you’re having is that you’re creating a new instance of LatinViewController and not using the instance that your app will actually be navigating to.
Instead of this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == K.latinSegue {
if latinTextField.text != nil && latinTextField.text != "" {
guard let latinText = latinTextField.text else { fatalError("latinText broken")}
let destinationVC = LatinViewController()
destinationVC.latinText = latinText
}
}
}
You should be doing something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == K.latinSegue, let destination = segue.destination as? LatinViewController else {
return
}
guard let text = latinTextField.text, !text.isEmpty else {
return
}
destination.latinText = text
}

it return nil because the destinationVC you create new LatinViewController not the viewController of your current segue
let destinationVC = LatinViewController()
change to this:
let destinationVC = segue.destination as? LatinViewController

Related

PersonalityQuiz guided app in swift fundamentals

I've got problem with some additional challenges. I need to filter an array of type Question by some property and then pass it into next View Controller via segue. I've done this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .animals
}
} else if sender == quiz4Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .cars
}
}
}
#IBAction func quiz3ButtonTapped(_ sender: UIButton) {
performSegue(withIdentifier: "animals", sender: sender)
}
#IBAction func quiz4Button(_ sender: UIButton) {
performSegue(withIdentifier: "cars", sender: sender)
}
Filtration works but it doesn't pass value to next View Controller. I declared variable in QuestionViewControler like that
var correctQuestions: [Question] = []
But when I need to access it I get error "Index out of range". So I figured that its empty..
Segues been made from buttons to VC
Ok. I've got it. The NavigationController was the problem here. Added into function push through NC and it worked ;) so closed I think
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .animals
}
} else if sender == quiz4Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .cars
}
}
}

How to transfer data between view controller using segues

I'm trying to pass data using a prepare(for segue:) function but it's showing nil in the second VC. Am I doing anything wrong?
class ViewController: UIViewController {
var first : [String] = []
#IBOutlet weak var passField: UITextField!
#IBOutlet weak var userID: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func login(_ sender: Any) {
let user : String = self.userID.text!
let password : String = self.passField.text!
if user != "" && password != "" {
let postString = ["username":user, “password”: password]
var request = URLRequest(url:URL(string:"http://mydomainhere.com/api/login")!)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: postString, options:.prettyPrinted)
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print("error=\(error)")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] {
let firstName = json["first_name"] as? String
let lastName = json["last_name"] as? String
self.first.append(firstName!) //putting into Array
self.performSegue(withIdentifier: "loginSegue", sender: self)
}
} catch {
print(error)
}
}
}
}
// data transfer to another controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loginSegue" {
let secondController = segue.destination as? SecondVC
secondController?.name = first //passing to next VC //* here having the issue its not passing the data to next VC
print(first) // here first is printing perfectly
}
}
}
// second View Controller
class SecondVC: UIViewController {
var menu_vc : MenuViewController!
var name : [String]? // passing to this Array
override func viewDidLoad() {
super.viewDidLoad()
print(name) // here printing nil
}
}
As suggested by #Sweeper, it could very well be that your destination view controller is embedded in a UINavigationViewController, hence your segue.destination is in fact a UINavigationViewController, not a SecondVC.
You can try this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var destinationViewController = segue.destination
if let navigationController = destinationViewController as? UINavigationController {
destinationViewController = navigationController.visibleViewController ?? destinationViewController
}
if let secondController = destinationViewController as? SecondVC {
secondController?.name = first
}
}
Of course the first four lines of code could be refactored in an appropriate function (even better if in an extension of UIViewController).
If that solves the problem, you can watch cs193p Stanford iOS course for further details.
In particular watch https://www.youtube.com/watch?v=HQrXM2zUPvY&index=6&list=PLPA-ayBrweUz32NSgNZdl0_QISw-f12Ai starting from the 30:20 mark.
Everything seems perfect with the below snippet
var first : [String] = []
#IBAction func btnTapped(_ sender: Any) {
let firstName = "iOS Geek"
self.first.append(firstName)
self.performSegue(withIdentifier: "MovetoSecVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MovetoSecVC"{
let secVC = segue.destination as! SecondVC
secVC.name = first
print(first) // it'll print here ["iOS Geek"]
}
}
// Your Second View Controller
class SecondVC: UIViewController {
var menu_vc : MenuViewController!
var name : [String]? // passing to this Array
override func viewDidLoad() {
super.viewDidLoad()
print(name!) // it'll print here ["iOS Geek"]
}
}

Swift 3, Successfully passed data but var returns nil when used

So here is my code from VC1 and passing the data to VC2.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedArtist = artists[indexPath.item]
performSegue(withIdentifier: "artistToArtSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController()
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
These codes here in VC2 will print the data
class ArtCollectionController: UICollectionViewController {
var artist = Artist() {
didSet{
print(artist.artistId ?? "did not work")
print(artist.name ?? "what name?")
}
}
var selectedArtist = Artist()
but when I use the the variable in these following test codes in VC2. They return a nil.
func fetchArtForArtist() {
guard let artistId = selectedArtist.artistId else {return}
print(artistId)
let fanRef = FIRDatabase.database().reference().child("art_ref").child(artistId)
fanRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
let artId = snapshot.key
print(artId)
// let dataRef = FIRDatabase.database().reference().child(artId)
// dataRef.observe(.value, with: { (snapshot) in
// let dictionary = snapshot.value as? [String: AnyObject]
// //let art =
// }, withCancel: nil)
}, withCancel: nil)
}
#IBAction func testButton(_ sender: UIBarButtonItem) {
print(selectedArtist.name ?? "no name")
print(12345)
}
override func viewDidAppear(_ animated: Bool) {
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
override func viewDidLoad() {
super.viewDidLoad()
fetchArtForArtist()
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
Im doing this in storyBoard. Im even using 2 vars seeing if there is a difference. I dont understand why the data is successfully passed to VC2 to a couple variables but when the variable is used it returns a nil. Please help.
The other responses are good, but I prefer a slightly different approach:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let artCollectionController as ArtCollectionController:
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
case let otherViewController as OtherViewController:
//Code for some other case
}
}
By using a switch statement, you have a prepareForSegue that will handle multiple different segues cleanly.
The case let construct is a cool trick that only executes that case if the variable in the switch can be case to the desired type. If it can be cast, it creates a local variable of the desired type.
I prefer deciding what code to execute based on the class of the destination view controller because it's less fragile than using the segue identifier. If you forget to set the segue identifier, or add a second segue later to the same type of view controller, or have a typo in the name of the identifier, that code won't work. If you have a typo in your class name, though, the compiler throws an error.
Because you set the artist property on a new instance of ArtCollectionController, which is destroyed upon the exit of the prepareForSegue function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController() // created
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
// destroyed here
}
}
Try this instead:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue",
let artCollectionController = segue.destination as? ArtCollectionController
{
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = segue.destination as! ArtCollectionController
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
try this, you are creating one more ArtCollectionController instead of passing data to segue one

Accessing a constant from another view controller

I have this function inside class firstViewController that produces itemID
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR/barcode is detected"
return
}
//Get metadata object
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
//if the found metadata is equal to the QR code metadata then update the status label's text and set the the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
let itemID = metadataObj.stringValue
print(itemID)
}
}
}
I want to call itemID in another viewController called secondViewController. How do I call itemID from the firstViewController inside the secondViewController?
You just need to pass that on "prepareForSegue".
just make sure you declare itemId as variable on secondviewcontroller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "secondViewControllerSeque") {
let secondViewController = segue.destination as! SecondViewController
secondViewController.itemID = self.itemID
}
}
If your segueing from the FirstViewController to SecondViewController you can pass the data like this.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerSegue" {
let secondViewController = segue.destination as? SecondViewController
if let itemID = itemID {
secondViewController.itemID = itemID
}
}
}
But don't forget to create itemID variable in FirstViewController and instead write the code like this
var itemID: String?
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
itemID = metadataObj.stringValue
}
The firstViewController class needs a member variable, itemID. Then the second viewcontroller should be able to reference it.
Right now itemID is a local variable in your captureOutpur function.
This goes away once the function is finished running. Assign it to the member instead.
If you are using a segue, you only need to do this:
FirstViewController (note: use capital letter on name):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowNextView" { // the identifier is what you named it
if let vc = segue.destination as? SecondViewController {
vc.itemID = itemID // itemID is accessible here, probably global
}
}
}
SecondViewController:
var itemID:String
func viewWillAppear() {
// code to use itemID value
}
Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
1.create a segue between ViewControllerA and ViewControllerB
2.name the segue with a Identifier in the attributes inspector of it
3.override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MySegueID" {
if let destination = segue.destinationViewController as? ViewControllerB {
destination.myInformation = self.myInformation
}
}
}
At first declare a variable in FirstViewController.
var itemID: String?
And call your function to assign your value to that variable before navigation to SecondViewController.
if metadataObj.stringValue != nil {
self.itemID = metadataObj.stringValue
}
Now, declare itemIdForSVC public variable in SecondViewController also,
public var itemIdForSVC: String?
If you are using Storyboard then pass parameter like this while navigating,
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "secondViewControllerSeque") {
let secondViewController = segue.destination as! SecondViewController
secondViewController.itemIdForSVC = self.itemID
}
}
Or if you are doing all programmatically then pass like this while navigating,
let secondViewConttoller = SecondViewController()
secondViewController.itemIdForSVC = self.itemID
self.navigationController?.pushViewController(secondViewConttoller, animated: true)

Cannot assign value of type String to type UILabel

I have marked the part of my code where the problem is, it is commented out. The error message is:
Cannot assign value of type String! to type UILabel!.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageLabelDos = sendingText
}
}
}
The label it should be changing is in my DetailViewController and it is a label. The code above is from my original ViewController. How can I make this work?
More code to put in context:
if metadataObj.stringValue != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("SendDataSegue", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.viaSegue = sendingText
}
}
}
You need to pass the String instead of setting text to label, because when you correct it and set like this sendToDetailViewController.messageLabelDos.text = sendingText, you will get nil error because messageLabelDos is not initialize yet, so try like this. Create one string instance in DetailViewController and use that inside prepareForSegue for passing String and then use that String instance in viewDidLoad to assign Label to text.
class ViewController: UIViewController {
//Your other methods
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageDos = sendingText
}
}
}
}
Inside DetailViewController
var messageDos: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.messageLabelDos.text = messageDos
}

Resources