swiftwatchkitapple-watchwatchoswatchos-5

Fatal error when trying to stop timer object in swift


I'm currently working on a stop watch for an Apple Watch. The watch has two interfaces and two controllers. TimerController and SwitchController. The TimerController runs the timer. I'm trying stop the time object from the SwitchController and have therefore made an function that stops the timer in my TimerController which i can reach in the SwitchController. Problem is that i get a fatal error in the TimerController, and i cant figure out why?

the function timeStopp() in the timerController returns the error:

"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"

See This Image!

The interfaces

Errors

SwipeController Swipe error

TimeController time error 1 time error 2

FrameWork Do i type it in here?

TimerController

import WatchKit
import Foundation

class TimerController: WKInterfaceController {
    @IBOutlet weak var timerOutlet: WKInterfaceTimer!

    var myTimer : Timer?
    var duration : TimeInterval = 45.0 //arbitrary number. 45 seconds

    var isPaused = false //flag to determine if it is paused or not
    var elapsedTime : TimeInterval = 0.0 //time that has passed between 
    pause/resume
    var startTime = NSDate()
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        start_timer()   
        timerOutlet.setTextColor(UIColor.red)

        // Configure interface objects here.
    }

    func start_timer() {
        myTimer = Timer.scheduledTimer(timeInterval: duration, target: 
        self,selector: Selector(("timerDone")), userInfo: nil, repeats: 
        false)
        timerOutlet.setDate(NSDate(timeIntervalSinceNow: duration ) as 
        Date)
        timerOutlet.start()
    }
    func timerDone(){
        //timer done counting down
    }
    @IBAction func pauseResumePressed() {
        //timer is paused. so unpause it and resume countdown
        if isPaused{
            isPaused = false
            myTimer = Timer.scheduledTimer(timeInterval: duration - 
            elapsedTime, target: self, selector: 
            Selector(("timerDone")), userInfo: 
            nil, repeats: false)
            timerOutlet.setDate(NSDate(timeIntervalSinceNow: duration - 
            elapsedTime) as Date)
            timerOutlet.start()
            startTime = NSDate()
            //pauseResumeButton.setTitle("Pause")

        }
            //pause the timer
        else{
            isPaused = true

            //get how much time has passed before they paused it
            let paused = NSDate()
            elapsedTime += paused.timeIntervalSince(startTime as Date)

            //stop watchkit timer on the screen
            timerOutlet.stop()

            //stop the ticking of the internal timer
            myTimer!.invalidate()

            //do whatever UI changes you need to
            //pauseResumeButton.setTitle("Resume")
        }
    }

    override func willActivate() {
        // This method is called when watch view controller is about to 
        be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no 
        longer visible
        super.didDeactivate()
    }

}

UPDATED SwipeController

import WatchKit
import Foundation


class SwipeController: WKInterfaceController {

    //@IBOutlet weak var myTimer: WKInterfaceTimer!
    var timer = TimerController()

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)

        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to 
be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no 
longer visible
        super.didDeactivate()
    }

    /stopp call is made here
    @IBAction func PauseButton() {
       timer.pauseResumePressed()
    }

}

Solution

  • Update

    Import Notification Center first. Click on your project in the Project Navigator on the left side. In General go to "Linked Frameworks and Libraries". Click on the + button and Search for Notification Center, then add the framework

    Screen

    Delete the whole TimerViewController and paste it with this: https://gist.github.com/rawandahmad698/a0095e584eaa4df278a23c92e71cca62

    Delete the whole SwipeViewController and paste it with this: https://gist.github.com/rawandahmad698/9ac0b7a411b7e88ff44068938a0b250a

    First. add this at the end of your code to your TimerController:

    import NotificationCenter
    extension Notification.Name {
         static let stopTimer = Notification.Name("stopTimer")
    }
    

    After that. on your SwipeController edit this line:

    @IBAction func PauseButton() {
        // Post notification:
        let userInfo = ["foo": 1, "bar": "baz"] as [String: Any] // you could also transfer data
    
        NotificationCenter.default.post(name: .stopTimer, object: nil, userInfo: userInfo)
    }
    

    Finally. on your TimerController add this to willActivate()

    NotificationCenter.default.addObserver(self, selector: #selector(stop_timer(notification:)), name: .stopTimer, object: nil)
    

    DONT FORGET: add this below willActivate()

    func stop_timer(notification:NSNotification) {
    
        // Timer is paused. so unpause it and resume countdown
        if isPaused {
            isPaused = false
            timerOutlet.start()
            startTime = NSDate()
    
        } else {
            isPaused = true
    
            // Stop watchkit timer on the screen
            timerOutlet.stop()
    
            // Stop the ticking of the internal timer
            myTimer!.invalidate()
    
            // Do whatever UI changes you need to
    
        }
    
    }
    

    Old Solution

    You can't invalidate a timer that hasn't been scheduled yet. First add an NSTimer:

    import WatchKit
    import Foundation
    
    class TimerController: WKInterfaceController {
        @IBOutlet weak var timerOutlet: WKInterfaceTimer!
        var myTimer : NSTimer?
        var duration : NSTimeInterval = 45.0 //arbitrary number. 45 seconds
    
        // UPDATE
         var isPaused = false //flag to determine if it is paused or not
          var elapsedTime : NSTimeInterval = 0.0 //time that has passed between pause/resume
        var startTime = NSDate()
        override func awake(withContext context: Any?) {
            super.awake(withContext: context)
    
            timerOutlet.setTextColor(UIColor.red)
    
    
            // Configure interface objects here.
    }
    

    Pause the timer using a button:

     @IBAction func pauseResumePressed() {
        //timer is paused. so unpause it and resume countdown
        if isPaused{
            isPaused = false
            myTimer = NSTimer.scheduledTimerWithTimeInterval(duration - elapsedTime, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
            timerOutlet.setDate(NSDate(timeIntervalSinceNow: duration - elapsedTime))
            timerOutlet.start()
            startTime = NSDate()
            pauseResumeButton.setTitle("Pause")
    
    
          }
          //pause the timer
          else{
                isPaused = true
    
                //get how much time has passed before they paused it
                let paused = NSDate()
                elapsedTime += paused.timeIntervalSinceDate(startTime)
    
                //stop watchkit timer on the screen
                timerOutlet.stop()
    
                //stop the ticking of the internal timer
                myTimer!.invalidate()
    
                //do whatever UI changes you need to
                pauseResumeButton.setTitle("Resume")
            }
        }
    

    Start the timer:

    func start_timer() {
         myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self,selector: Selector("timerDone"), userInfo: nil, repeats: false)
         timerOutlet.setDate(NSDate(timeIntervalSinceNow: duration ))
         timerOutlet.start()
    }
    

    When the timer completes. This block gets invoked

    func timerDone(){
           //timer done counting down
    }