Search code examples
swiftuitimer

SwiftUI Timer(fireAt: - giving Fatal error for nil value


All, I am trying to setup a timer to start at a given time and then repeat at intervals. The Timer.init(fireAt:... initializer seems to fit this, but is giving me an "Unexpectedly found nil while unwrapping an Optional value" on @main.

Here is my code

import SwiftUI

class testout {
    @objc func timerTesting() {
        print("Test")
    }
}

@main
struct My_MacOS_SandboxApp: App {
    
    var body: some Scene {      
        WindowGroup {                       
            TimerStartAt0Mins()         
        }
    }
}

struct TimerStartAt0Mins: View {
    
    @State private var testTimer = Timer()
    var myOut = testout()
    
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
            .onAppear {
                print("now = \(Date.now.formatted())")
                let nowplus10 = Date.now.addingTimeInterval(10)
                print("now+10 = \(nowplus10.formatted())")
                myOut.timerTesting()
                testTimer = Timer.init(fireAt: nowplus10, interval: 60, target: self, selector: #selector(myOut.timerTesting), userInfo: nil, repeats: true)
            }
    }
}

and the full error is: My MacOS Sandbox[17252:1206390] Swift/RangeReplaceableCollection.swift:625: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Any help would be appreciated.


Solution

  • Thanks to both of the responders - although neither was an actual solution to what I was wanting to do, they did give me inspiration on what to be researching. It turns out, that I was using the wrong Timer.init function. I couldn't get #selector's to work in the fireAt: init - so I used the one with a block:. I also found that the Timer is not always guaranteed to fire at the specified time (+/- a sec sometimes) so using interval: would eventually throw my needed timing off completely.

    So, here is what I ended up with to get a func to run at the start (0 secs) of each minute...

    struct TimerStartAt0Secs: View {
        
        @State private var testTimer: Timer?
        
        var body: some View {
            Text("Hello, World!")
                .onAppear {
                    print("now = \(Date.now.formatted(date: .omitted, time: .complete))")
                    fireAtStartOfNextMin()
                }
        }
        
        func fireAtStartOfNextMin() {
            let calendar = Calendar.current
            var timerStartComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .timeZone], from: Date.now)
            timerStartComponents.minute! += 1
            timerStartComponents.second = 0
            let timerStart = calendar.date(from: timerStartComponents)
            testTimer = Timer.init(fire: timerStart!, interval: 0, repeats: false, block: { _ in
                print("Test \(Date.now.formatted(date: .omitted, time: .complete))")
                fireAtStartOfNextMin()
            })
            RunLoop.main.add(testTimer!, forMode: RunLoop.Mode.common)
        }
        
    }