0

I am trying to follow a YouTube tutorial and learn Swift by making a simple card game. After completing the tutorial I wanted to add some features of my own such as resetting the score and cards once the app is opened. When I say opened I mean either entering the foreground or launching the app. I am currently attempting this by creating a method called reset in ViewController.swift and calling the method in AppDelegate.swift when the function applicationWillEnterForeground is called. I am able to build the code successfully and run it however when the app comes to foreground I get an error which states "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value".

AppDelegate.swift:

//
//  AppDelegate.swift
//  War
//
//  Created by Rafael on 3/17/18.
//  Copyright © 2018 Rafael. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{

    var window: UIWindow?
    var ViewControl: ViewController = ViewController()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        print("Launching app!")
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
        print("Going inactive!")
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
        print("Entering background!")


    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
        print("Entering Foreground!")
        ViewControl.reset()
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
        print("Going active!")
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        print("Going to terminate!")

    }

}


    enter code here

ViewController.swift:

//
//  ViewController.swift
//  War
//
//  Created by Rafael on 3/17/18.
//  Copyright © 2018 Rafael. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var rightImageView: UIImageView!
    @IBOutlet weak var leftImageView: UIImageView!

    @IBOutlet weak var leftScoreLabel: UILabel!
    var leftScore = 0
    @IBOutlet weak var rightScoreLabel: UILabel!
    var rightScore = 0

    let cardNames = ["card2", "card3", "card4", "card5", "card6", "card7", "card8", "card9", "card10", "card11", "card12", "card13", "card14"]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        print("loading view!")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func dealTapped(_ sender: Any) {
        let leftNumber = Int(arc4random_uniform(13))
        let rightNumber = Int(arc4random_uniform(13))

        if leftNumber > rightNumber {
            leftScore += 1
            leftScoreLabel.text = String(leftScore)
        } else if(rightNumber == leftNumber){
            // Do nothing - TIE
        } else{
            rightScore += 1
            rightScoreLabel.text = String(rightScore)
        }
        leftImageView.image = UIImage(named: cardNames[leftNumber])
        rightImageView.image = UIImage(named: cardNames[rightNumber])
    }

    func reset() {
        print("resetting!")
        leftImageView.image = UIImage(named: "back")
        rightImageView.image = UIImage(named: "back")

        rightScoreLabel.text = "0"
        leftScoreLabel.text = "0"
        rightScore = 0
        leftScore = 0
    }
}

All outlets are working fine and the app works fine. The only issue is when the function reset is called. How can I make my app reset the cards when the app is going to be in view? I am still learning the language.

  • `var ViewControl: ViewController = ViewController()` with this you are creating new instance... well so what you can do is in ViewController class call `reset()` in `viewWillAppear` and from AppDelegate you can call reset func with `ViewController.reset()` – Latenec Mar 18 '18 at 01:12
  • Possible duplicate of [What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?](https://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu) – Tamás Sengel Mar 18 '18 at 01:39
  • If I simply type `ViewController.reset()` I get an "Instance member 'reset' cannot be used on type 'ViewController'; did you mean to use a value of this type instead?" error message. There isn't any fix buttons so I am not sure how to fix it. As for the viewWillAppear it doesn't seem to run at all. I put a print statement in there and I never see it in the console. Everything in my reset function seems to work fine except when trying to access something from the storyboard. – RafaelPiloto10 Mar 18 '18 at 01:46
  • I am not asking what "fatal error: unexpectedly found nil while unwrapping an Optional value" means but instead "How can I make my app reset the cards [a UIImage] when the app is going to be in view?" – RafaelPiloto10 Mar 18 '18 at 01:48
  • When checking the debugger I do indeed see that my new instance of viewController which is named "viewControl" has stored the value of nil for the ImageView variables and labels. I think that I am not connecting to the outlets and therefore not getting the images/values. how can I speak to viewController.swift directly from AppDelegate.swift instead of having to create a new instance of ViewController. I believe that this may be the issue but I have no clue if I am right or how to solve it. – RafaelPiloto10 Mar 18 '18 at 01:56

2 Answers2

2

Here's a couple of things to think about. First of all, trying to reset your view's state, every time they leave the app and come back is almost certainly not what users would expect. Can you imagine what it would be like to be playing a game, an email comes in and you switch over to look, then go back and your game is back to the beginning of the level?

Also, unless you are doing something that is View Controller independent, such as fetching cloud records from the background, you probably need to just stay out of AppDelegate. The paradigm Apple has set up (Model View Controller) for it's platforms is to control what you see on the screen from inside of the View Controller. You are trying to control the View Controller from AppDelegate in order to manipulate the content on screen. I would suggest rethinking how you're going about controlling the screen contents. Also, once the app has been in the background for a certain amount of time (you're in the home screen or another app), it will eventually reset it's state anyway and then viewWillAppear will be called again. That's why each ViewController has methods for saving and restoring state, when this happens.

Despite all of this, there is a way of accessing your ViewController's instance from AppDelegate. You can get to the root view controller like this let rootVC = UIApplication.shared.keyWindow?.rootViewController. Remember though, what you think of as your main screen, may not be the root view controller. The root controller could be the tab bar, or a navigation controller. If so, you'll have to keep on going down the chain until you find it.

Another way to get messages across to a ViewController, without having direct access to it's instance, is to send a message with foundation's NotificationCenter. Like this: NotificationCenter.default.post(name: NSNotification.Name(rawValue: "app has returned from background"), object: nil). And then in the view controller you would put something like this: NotificationCenter.default.addObserver(self, selector: #selector(screenRefreshedFunction), name: NSNotification.Name(rawValue: "app has returned from background"), object: nil), then create that function. I think iOS has built-in notifications for when the app goes into the background, that you can set up to observe. I'm not sure what they are without looking through the documentation.

bdepaz
  • 434
  • 1
  • 3
  • 9
  • I understand that the behavior that I am looking for may not be desired by the user. I was simply experimenting and learning the language when I tried to implement this idea. Its definitely helping me learn how Swift and how IOS development works. I will give Notification Center a try as I believe it may be what I am looking for. Thanks for the help – RafaelPiloto10 Mar 18 '18 at 03:06
  • Your welcome. Anytime you start learning a new platform you’re going to hit some bumps. You’ll get the hang of it. But if the Notification Center helps you, please remember to mark the answer as the right one. Good luck’ – bdepaz Mar 18 '18 at 03:11
1

You're doing a few things wrong. First of all, you don't appear to be persisting any of the data to disk, so everything will reset everytime you start anyway. But here's a couple of things.

1: You should be doing resetting of your ViewController's properties / state, inside the view controller itself; not from AppDelegate.

2: you named your view controller variable like this. var ViewController: ViewController = ViewController(). The proper way to name your variables in Swift / Objective-C is camel cased var viewController = ViewController() or better yet something like var myViewController = ViewController(). Because when you typed in ViewController, it had the same name as the class itself, it wasn't trying to access your instance. It's really confusing to yourself (and the compiler) to name your ivars exactly the same as the class name.

3: You initialized an instance of ViewController, but it's not the same one that you're seeing on the screen. What you see on the screen is owned and created by the Storyboard. That ViewController you created in AppDelegate wasn't actually doing anything. That's why the IBOutlets were nil, because the labels on the screen were inside of the instance the Storyboard made on it's own. So all you have to do is put your logic in your ViewController.swift file; you won't have to create your own instance.

That being said, if you want something to happen after a view controller appears on the screen, you either need to write a function inside of it called override func viewWillAppear (animated: Bool) {} or put it in viewDidLoad(). In iOS, the Model View Controller pattern is used. Each screen is controlled by a UIViewController. So any kind of logic for manipulating the screen and it's views should done inside of the View Controller subclass. The way the view controllers work is, first viewDidLoad() is called, then viewWillAppear(), finally viewDidAppear().

bdepaz
  • 434
  • 1
  • 3
  • 9
  • Thank you for pointing out the camel casing issue and for your suggestions. In your third suggestion you mention the viewWillAppear function which I tried. I wrote it and inside put a print statement. I noticed that the print statement only went off once which was when the app was launched. I am trying to get this print statement to go off every time the app is pressed. What I mean by this is when I press the home button and then return to the app, the print statement should go off. If I terminate the app and launch it, it should go off, etc. – RafaelPiloto10 Mar 18 '18 at 02:27