0

I'm trying to make a function that I can call on to update my UILabel's scoreLabel.text. However, I get an error whenever I try to change it. What's confusing to me is that I don't receive an error when changing it inside viewDidLoad(). Everywhere else returns the following error:

Imgur Link

In the console I also get error:

fatal error: unexpectedly found nil while unwrapping an Optional value.

So what I've been lead to believe is that when calling my function to update the text, the view hasn't loaded the UILabel yet. But I'm certain that this function is called only once the view has loaded.

Things I've checked/tried for:

  • That my IBOutlet is properly connected
  • That my function is being called
  • Using both scoreLabel.text and self.scoreLabel.text
  • Using a Strong and Weak outlet connection

I am also positive that changeTextLabel is being called after scoreLabel is loaded into memory. But again my error seems to say otherwise.

Here's a complete markup of my code. I've removed some irrelevant details for readability:

import UIKit
import SpriteKit

class GameViewController: UIViewController {



@IBOutlet weak var scoreLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    //This is where all my other code was
    print(scoreLabel.text)
    scoreLabel.text = "Testing" //This line can be run
}

func changeTextLabel() {
    print("changeTextLabel called")
    if self.scoreLabel != nil {
        self.scoreLabel.text = "yay"
    } else {
        print("scoreLabel is nil") //This is called every time
    }
}
}

Thanks for your time

Edit:GameScene.swift

This is only the part that should be of concern

func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
    print("Hit")
    let storyBoard = UIStoryboard(name: "Main", bundle: nil)
    let object = storyBoard.instantiateViewController(withIdentifier: "GameViewController") as! GameViewController

    object.changeTextLabel()
    projectile.removeFromParent()
    monster.removeFromParent()
}
EthanBar
  • 475
  • 1
  • 7
  • 22

5 Answers5

1

The problem is with this line,

 let game = GameViewController()
 game.changeTextLabel()

You should create GameViewController object using XIB or Storyboard.

In your case it is just creating object using .swift file, formally class. You should create your object using View which is having IBOutlet connection with scoreLabel.

Try (If using Storyboard),

let storyboard = UIStoryboard(name: "YourStoryBoard", bundle: nil)
let gameVC = storyboard.instantiateViewController(withIdentifier: "GameViewController") as! UIViewController

or (If using XIB)

var gameVC = GameViewController(nibName: "GameViewController", bundle: nil)

Update you changeTextLabel with following (Ref),

func changeTextLabel() {
    print(self.view)
    print("changeTextLabel called")
    if self.scoreLabel != nil {
        self.scoreLabel.text = "yay"
    } else {
        print("scoreLabel is nil") //This is called every time
    }
}
Community
  • 1
  • 1
Mohammad Zaid Pathan
  • 14,352
  • 6
  • 84
  • 112
  • Updated my GameScene to use this now, very helpful to know I've been doing it wrong! However, it hasn't fixed my error. And scoreLabel.text still = nil – EthanBar Nov 08 '16 at 06:24
  • @Mathperson Please update your question what changes you did, so I can look closer we are on right track. – Mohammad Zaid Pathan Nov 08 '16 at 06:25
1

I dont fully know your project setup but your approach seems complicated, its not what you should do in SpriteKit games. So I dont think its a good idea to tell you how to fix your current problem.

You should create your UI, such as labels, buttons etc using SpriteKit APIs (SKLabelNodes, SKSpriteNodes, SKNodes etc) directly in the relevant SKScene(s).

Your GameViewController should only really handle presenting SKScenes. so there should be next to no code there apart from loading the 1st SKScene.

If you have multiple SKScenes (MenuScene, ShopScene, SettingScene etc) your approach will fall apart because the score label will show in all SKScenes. GameViewController presents all your SKScenes, so whats added to GameViewController shows in all SKscenes. That means you have to remove/add labels and other UI for each scene and it will be chaos.

So to create a score label you should do this directly in the relevant scene. I like to use the lazy var approach to keep the setup code for the label in the same spot.

class GameScene: SKScene {

      lazy var scoreLabel: SKLabelNode = {
          let label = SKLabelNode(fontNamed: "HelveticaNeue")
          label.text = "SomeText"
          label.fontSize = 22
          label.fontColor = .yellow
          label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
          return label
      }()

      override func didMove(to view: SKView) {

          addChild(scoreLabel)
      }
} 

Than you can update it like this directly in the collision code you have.

scoreLabel.text = "NewText"

Now when you change from 1 SKScene to another SKScene you dont have to worry about removing this label, SpriteKit will do it for you.

You could also use xCodes level editor to add this label visually, similar to storyboards. This brings me to my final suggestion, which is that storyboards are not really used in SpriteKit games. Storyboards are for ViewControllers and in SpriteKit you are working with SKScenes.

Hope this helps

crashoverride777
  • 10,059
  • 2
  • 29
  • 52
0

It happens because you are not getting memory of label in any other function.It happens because of so many reasons.

One reason could be @IBOutlet weak var scoreLabel: UILabel! Try to create a strong variable outlet.

@IBOutlet strong var scoreLabel: UILabel!

Second reason could be that you are calling changeTextLabel before the label gets memory. So call it later.

Vincent
  • 4,076
  • 1
  • 36
  • 37
User511
  • 1,389
  • 6
  • 25
  • I've tried both using a strong and weak variable outlet, and changeTextLabel is being for sure called after the label is initialized into memory. Thanks for the help! – EthanBar Nov 08 '16 at 05:48
  • It should be working for you as well. It is working for me. – User511 Nov 08 '16 at 06:02
  • one more thing add a breakpoint and check if label is nil then do this self.scoreLabel?.text = "yay" – User511 Nov 08 '16 at 06:04
0

Replace this line

self.scoreLabel.text = "yay"

With

self.scoreLabel?.text = "yay"
0

Make these changes and try again:

   func changeTextLabel() {
        print("changeTextLabel called")
        if self.scoreLabel != nil {
           self.scoreLabel.text = "yay"
        }

   }


func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
    print("Hit")
    let game =  STORYBOARD.instantiateViewControllerWithIdentifier("STORYBOARD_IDENTIFIER") as! GameViewController
    game.changeTextLabel()
    projectile.removeFromParent()
    monster.removeFromParent()
}
Mukul More
  • 510
  • 2
  • 5
  • 17
  • Updated my GameScene to use this now, very helpful to know I've been doing it wrong! However, it hasn't fixed my error. And scoreLabel.text still = nil – EthanBar Nov 08 '16 at 06:24
  • The label has not come into memory unless the view is loaded you need to load the view before using the label . In this case you create instance of Game View Controller but its view has not been loaded ie the label is still nil. If you present the viewcontroller or pushviewcontroller that will resolve your issue . – Mukul More Nov 08 '16 at 06:42
  • Can you elaborate more on what you mean by "present" the viewcontroller? – EthanBar Nov 08 '16 at 06:45
  • can you post the class where you are using projectileDidCollideWithMonster delegate? – Mukul More Nov 08 '16 at 06:58