Updating Time - SwiftUI

Hello! I am wanting to have the current time updating in SwiftUI I want to embed this in another view. After scouring the internet, this is what I've managed to put together so far.

Here's where I've got so far:
Code Block
struct Time : View {
    static let timeFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .none
        formatter.timeStyle = .medium
        return formatter
    }()
    var now = Date()
    @State var timer: Timer
    @State var repeater: Bool = true
    var body: some View {
            Text("\(now, formatter: Self.timeFormatter)")
        }
    }
}


I don't have loads of experience – any and all help is appreciated, and I thank-you all for your time.

Accepted Reply

I didn't know it sometimes does not work.

I'm using xcode 12.0-beta-3, target ios 13.5 and 14, tested on a few simulators and real devices, ipad(ios 14) and iphone(ios 13.5).

I cannot see any problems with the following (updated) test:


Code Block
import SwiftUI
struct ContentView: View {
var body: some View {
TestView().padding()
}
}
struct TestView: View {
@State var timeNow = ""
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var dateFormatter: DateFormatter {
let fmtr = DateFormatter()
fmtr.dateFormat = "LLLL dd, hh:mm:ss a"
return fmtr
}
var body: some View {
Text("Currently: " + timeNow)
.onReceive(timer) { _ in
self.timeNow = dateFormatter.string(from: Date())
}
}
}


What setup does it fail on?

Replies

you could use something like this:


Code Block
import SwiftUI
struct TestView: View {
   
  @State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
  @State var timeNow = ""
  let dateFormatter = DateFormatter()
   
   var body: some View {
    Text("Currently: " + timeNow)
      .onReceive(timer) { _ in
        self.timeNow = dateFormatter.string(from: Date())
      }
      .onAppear(perform: {dateFormatter.dateFormat = "LLLL dd, hh:mm:ss a"})
  }
}


This will display the time updated every second
Thank-you for your help. Is there a way to ensure it loads? It is loading approximately 50% of the time.
don't understand your request, "...ensure it loads?"
@workingdogintokyo If I had to guess LB00000 is talking about Timer/.onReceive's inconsistency in this build. In my apps I'm seeing it work probably only 2 out of 3 times a view loads, with views that worked without issue on iOS 13 and previous iOS 14 builds.
I didn't know it sometimes does not work.

I'm using xcode 12.0-beta-3, target ios 13.5 and 14, tested on a few simulators and real devices, ipad(ios 14) and iphone(ios 13.5).

I cannot see any problems with the following (updated) test:


Code Block
import SwiftUI
struct ContentView: View {
var body: some View {
TestView().padding()
}
}
struct TestView: View {
@State var timeNow = ""
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var dateFormatter: DateFormatter {
let fmtr = DateFormatter()
fmtr.dateFormat = "LLLL dd, hh:mm:ss a"
return fmtr
}
var body: some View {
Text("Currently: " + timeNow)
.onReceive(timer) { _ in
self.timeNow = dateFormatter.string(from: Date())
}
}
}


What setup does it fail on?

What setup does it fail on?

It depends on the exact time the Timer starts.

Assume your Timer starts at 00:00:00.995, it will be fired passing every one second, but not exactly.
There may be 0...10 msec delay (or more) between the exact scheduled time and the actual execution time of onReceive.
Code Block
------------------------------ Time shown
00:00:00.995 00:00:00
00:00:02.005 (10 msec delay) 00:00:02 <- missing 00:00:01
00:00:02.999 ( 4 msec delay) 00:00:02
:


With a few hundreds of tries, and you would be able to observe this behavior.
@OOper, good point. Do not use this code for precise time keeping or clocks synchronization.
This is documented behavior for timers. Timer events are coalesced for efficiency. The delay will increase when the system is busy doing higher-priority things.

One simple workaround is to adjust the tolerance or just update more often (every half-second for better 1-second resolution?). Though, “The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of this property.”, according to the docs.

For SwiftUI it would be wise to update the state (and trigger the view update) only if the actual displayed time has changed when the timer fires. Still, if the system is busy, you’ll notice stutter.
(Apparently the tolerance defaults to zero, so I guess finer resolution via more frequent updates is probably your best bet.)

Here is something I ripped up and works. I deliberately reduced the timer interval to 0.1 to give a more accurate seconds update.

You can download the code in

https://github.com/richardlamo/SwitfUI-Clock


    

    let clockTimer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()

    let calendar = Calendar.current

    @State var currentDate = Date()

    @State var hour : NSInteger = 0

    @State var minute : NSInteger = 0

    @State var second : NSInteger = 0

    

    let cyclePeriod = 0.5

    

    var body: some View {

        ZStack {

            VStack {

                Text("\(hour):\(minute):\(second)")

          

            }

        }

        .onReceive(clockTimer) {

            time in

            currentDate = time

            hour = calendar.component(.hour, from: currentDate)

            minute = calendar.component(.minute, from: currentDate)

            second = calendar.component(.second, from: currentDate)

        }

    }

}