Error trying to transfer data from variable between storyboards - ios

I have a game where I store the value of the high score of a player as "highScore", in the first view controllers.
What I want to do is to try to transfer this variable data, to the second view controller.
I entered in the following code in the first view controller's storyboard.
Btw, highscore2, is the variable that I delcared in the second view controller, to store the data from the first highscore variable.:
override func prepareForSegue(segue: UIStoryboardSegue!, sender:AnyObject!)
{
if (segue.identifier == "segueTest")
{
var svc = segue!.destinationViewController as! viewTwo;
svc.highScore2 = "High Score \(highScore)"
}
}
Now here is the code in my second view controller (viewTwo):
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
var highScore2:String!
override func viewDidLoad() {
highScoretwolbl.text = highScore2
}
}
For some reason, the code compiles, but the high score is not displayed, and is "nil".

Avoid forced unwrapping.
if let svc = segue.destinationViewController as? viewTwo{
svc.highScore2 = "High Score \(highScore)"
}
Then put in a breakpoint and make sure you got to all of these lines.

As stated in other answers, viewDidLoad only gets fired once. Since you
are using a forced unwrapped string, it is initially nil. It (highScore2) won't get a value until you set it which happens after viewDidLoad. Add some print statements so you can see the sequence of events.
Your best option is to use viewWillAppear. This will get fired every time
your view appears. This is what you want.
You don't need to use a force unwrapped variable. Just make a string a
make it empty initially.
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?)
{
if segue.identifier == "segueTest"
{
if let svc = segue.destinationViewController as? viewTwo {
print("setting score in prepareForSegue")
svc.highScore2 = "High Score \(highScore)"
}
else {
print("something bad happened")
}
}
}
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
// The following uses forced upwrapping which means it could be nil
// if you never set it and your app will crash.
// var highScore2: String!
// Instead, do this:
var highScore2: String = ""
override func viewWillAppear(animated: Bool) {
// putting this here will ensure that every time the screen
// is shown, the label will get set from the string.
highScoretwolbl.text = highScore2
}
}

If I recall correctly, the viewDidLoad method is called only once when the view controller is first initialized. So you are setting the highScoretwolbl variable before highScore2 actually has any data, this is why it is nil.
I would recommend setting the text inside viewWillAppear so it updates every time the segue happens.
So something like
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
highScoretwolbl.text = highScore2
}
Here is a reference to the lifecycle of all the methods of a view controller. View controller lifecycle

Related

How to reload data in UIViewController inside a UINavigationController

I have 2 child ViewContollers in a NavigationController. There is a UISwitch in the 2nd one that changes how I query a backend API in the 1st one.
This is my code in the 2nd one:
#IBAction func ordersTapped(_ sender: UISwitch) {
if sender.isOn {
UserDefaults.standard.set(true, forKey: "showPastOrders")
} else {
UserDefaults.standard.set(false, forKey: "showPastOrders")
}
}
This is my code in the 1st one:
override func viewDidLoad() {
super.viewDidLoad()
let orderList = UserDefaults.standard.bool(forKey: "showPastOrders")
loadOrderData(past: orderList)
}
The problem is, whenever I swap back and forth between the ViewControllers, the Userdefaults boolean value I set, is not read. I tried overriding the viewDidAppear to obtain the value but that did not help either.
So how would I obtain the boolean value each time when I jump back and forth between the VCs?
maybe you can fix it by setting up a didSet call on the Bool.
var showPastOrders: Bool {
didSet {
orderList = UserDefaults.standard.bool(forKey: "showPastOrders")
// view.layoutIfNeeded()
}
}
Now if setting the showPastOrders is not set correctly I would recommend delegation or passing the data with the segue, depending on how you set up your project.
Hope it helps.

When trying to segue to a view controller from a table view i get this error: Unexpectedly found nil while unwrapping

I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.

Many buttons to a single view controller

im developing an app that utilises many buttons( possibly 20 buttons) on one primary view controller that can are all able to activate a singular picker view within a pop up on a seperate view controller. i don’t think the answer is lots and lots segues. Is there a better approach I should be considering?
I’m thinking - some kind of multiuse segue that can be activated by any of the buttons, but nonidea how this is done.
Appreciate any advice
Mike
Set up all buttons to same action such as:
#IBAction func keyPressed(_ sender:UIButton){
// use button title string
self.keyString = sender.titleLabel?.text as! String
// or tag
self.keyTag= sender.tag?
self.performSegue(withIdentifier: "TheSegue", sender: self)
}
Then you would want to set up the View Controller that you are going to navigate to based on the state of the sender. So you would override the prepare:forSegue method as below.
override func prepare(for segue:UIStoryboardSegue, sender:Any?) {
let destController = segue.destination as! Dest_Controller_Class
// use tag or keyTitle to set controller attributes
// before view is shown
destController.keyTag = self.keyTag
destController.keyString = self.keyString
}
Now once you've navigated to the Dest_Controller_Class, you will have the properties of the button pressed locally in the view controller and could update the view as you see fit:
class Dest_Controller_Class: UIViewController {
var keyString: String?
var keyTag: Int?
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
if (keyString != nil) {
label.text = keyString;
// or likewise use tag
} else {
label.text = "keyString not set"
}
}
}

How do I pass data between two scenes in Swift?

I have two scenes where I would like to pass a single variable to another scene using a segue. I have tried but unfortunately all tutorials I have seen are dealing with the storyboard. I am not using the storyboard. I am doing all of this programatically.
Here is the segue i am trying to initialize:
func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueTest") {
var lose = segue.destinationViewController as! loserScene;
lose.toPass = scoreLabelNode.text
}
}
The Loser Scene is my second scene.
scoreLabelNode.text is the text of an NSTimer which i'm using as a score.
I want to move the scoreLabelNode.text into another scene which is my loserScene.
My loserScene is set up like this:
import SpriteKit
import Foundation
let GameOverLabelCategoryName = "gameOverLabel"
class loserScene: SKScene {
var toPass: String!
var scoreLabel = SKLabelNode(fontNamed:"[z] Arista Light")
override func didMoveToView(view: SKView) {
var gameOver = SKSpriteNode(imageNamed: "gameover.png")
gameOver.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
self.addChild(gameOver)
println(toPass)
}
}
I am trying to print 'toPass' to test the string segue, but I just get nil.
OK! So instead of using a segue. WHICH YOU CANNOT DO WITH AN SKSCENE. I used a struct. I put the struct outside of one of my scenes that I wanted data to be passed from, and made sure the data was being fed into the struct. I then accessed the struct from another scene and it works perfectly!
struct Variables {
static var aVariable = 0
}
this is the struct^ I have it set to 0 but it will be updated automatically when the score is recorded at the end of the run of my game.
I then accessed it like so in another scene:
print(Variables.aVariable)
This prints the variable from the struct in my new scene. I can also turn this struct into a string and use it for a labelnode.
Your toPass String returns nil because it is not initialized when you send the value from your prepareForSegue method.
Create a some function for example;
// Do whatever you need with the toPass variable.
func printToPassValue(){
print(toPass)
}
And add the property observers to your toPass variable. like this;
var toPass: String? {
willSet {
printToPassValue() // Call your function
}
}
And add this function to your viewDidLoad method.
override func viewDidLoad() {
super.viewDidLoad()
printToPassValue()
}

Swift Delegate and Optional

I'm working on my first iOS app using swift. I'm trying to load the a value from one viewController into another. I am using a protocol, but I can't get it to execute properly. I have searched around both stack overflow and elsewhere but haven't been able to find an answer that works in my situation.
Here's the VC I'm trying to pull the value from:
protocol AddHelperVCDelegate {
func didFinishAddingHelper(controller: AddHelperViewController)
}
class AddHelperViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var fetchedResultsController:NSFetchedResultsController = NSFetchedResultsController()
var delegate:AddHelperVCDelegate! = nil
var helperBonus:NSNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController = getFetchResultsController()
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
// Do any additional setup after loading the view.
}
}
And here is where I am trying to move the value (and the view) back to a proceeding VC.
// UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var indexPathForRow = tableView.indexPathForSelectedRow()
println("indexPath for selected row is: \(indexPathForRow)")
let thisUser = fetchedResultsController.objectAtIndexPath(indexPath) as UserModel
var cell:UserCell = tableView.dequeueReusableCellWithIdentifier("helperCell") as UserCell
helperBonus = thisUser.effectiveCombat
helperBonus = Int(helperBonus)
println("helperBonus is: \(helperBonus)")
delegate.didFinishAddingHelper(self)
}
If I make the delegate an optional (delegate?.didFinishAddingHelper(self)) then nothing happens. If I do not, I get a crash with the error message:
indexPath for selected row is: Optional(<NSIndexPath: 0xc000000000018016> {length = 2, path = 0 - 3})
helperBonus is: 0
fatal error: unexpectedly found nil while unwrapping an Optional value
Now, I know I'm declaring delegate as nil, but that's the limit to my understanding of what's going on. I need to add the value in the proceeding VC in this function:
func didFinishAddingHelper(controller: AddHelperViewController) {
self.effectiveCombat = Int(controller.helperBonus)
controller.navigationController?.popViewControllerAnimated(true)
}
The crash is happening because AddHelperViewController's delegate property is nil. This is because you aren't setting it.
Wherever you create the AddHelperViewController, set its delegate on the next line:
let addHelperVC = AddHelperViewController()
addHelperVC.delegate = self
Then when you call the delegate property, it will point back to the view controller that created it.
If your AddHelperViewController is created using a storyboard, set delegate in the prepareForSegue(_:sender:) of the method that is about to show the new controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? AddHelperViewController {
// If we got here, vc is an AddHelperViewController
vc.delegate = self
}
}
var delegate:AddHelperVCDelegate! = nil
Should be
var delegate:AddHelperVCDelegate?
It is failing because you are using !, which means that you guarantee that, while it can't be initialized during the classes init, it will not be nil by the time you want to use it.
Aaron Brager is absolutely correct when he says you need to set the delegate property.
With your delegate declared as optional (with ?), you can call it only if the object is not nil (ignored otherwise):
delegate?.didFinishAddingHelper(self)
On a side note, you might also consider making the delegate property weak in order to help prevent retain cycles.

Resources