是否可以检测状态栏时钟上的“分钟”何时变化?

问题描述 投票:0回答:9

我有一个复杂的问题需要描述,所以我将保存它并总结我正在尝试做的事情。我希望在状态栏上的时间发生变化时“收到通知”,以便我可以重新计算时间。我目前正在计算时间,直到很好,但是有一个 1 分钟的窗口,我的计算和时间戳不匹配......这一切都取决于他们打开应用程序的时间,与 iPhone“秒”时钟的位置相比他们打开了它。

简而言之,我们能否检测到状态栏上的分钟变化?如果是的话,怎么办?

谢谢!

iphone ios ios4
9个回答
13
投票

甚至比 Emilio 的答案更简单 - 当您的视图加载时(或者当您想要触发事件时),只需检查当前日期的秒部分(例如使用 NSCalendar),安排一个将在 60-currentSeconds 内触发的计时器(这将是下一分钟,零秒),最后,安排每 60 秒触发一次的新计时器。


7
投票

基于@mja的建议:

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSSecondCalendarUnit fromDate:[NSDate date]];
NSInteger currentSecond = [components second];

//+1 to ensure we fire right after the minute change
NSDate *fireDate = [[NSDate date] dateByAddingTimeInterval:60 - currentSecond + 1];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:fireDate
                                          interval:60
                                            target:self
                                          selector:@selector(YOURSELECTORHERE)
                                          userInfo:nil
                                           repeats:YES];

[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

我正在使用它,效果很好。如果您需要删除计时器,您必须更加小心。


5
投票

我深入研究并找到了

Calendar.nextDate(after:matching:matchingPolicy:repeatedTimePolicy:direction:)
方法,根本不需要在应用程序代码中进行任何计算。

示例:

var zeroSeconds = DateComponents()
zeroSeconds.second = 0
guard let nextMinute = Calendar(identifier: .gregorian).nextDate(after: Date(), matching: zeroSeconds, matchingPolicy: .nextTime, direction: .forward) else {
  // This should not happen since there should always be a next minute
  return
}

let timer = Timer(fire: nextMinute.advanced(by: 0.1), interval: 60, repeats: true) { _ in
  // This runs at every turn of a minute
}

RunLoop.main.add(timer, forMode: .default)

正如其他答案所指出的,一些缓冲区似乎是必要的,以确保在分钟结束后触发它(例如,如果您每分钟都显示时间,则至关重要),因此

nextMinute.advanced(by: 0.1)
(100 毫秒)。我还没有找到任何关于具体多少的文档,但在我的简短测试中,100 毫秒似乎还可以。


5
投票

基于 @SG1 代码示例,但使用 Swift:

// Calculate fireDate on every minute's first second
let now: Date = Date()
let calendar: Calendar = Calendar.current
let currentSeconds: Int = calendar.component(.second, from: now)

// Init timer
self.timer = Timer(
    fire: now.addingTimeInterval(Double(60 - currentSeconds + 1)),
    interval: 60 /*seconds*/,
    repeats: true,
    block: { (t: Timer) in
        self.sendNotification()
    })

// Start timer
RunLoop.main.add(self.timer, forMode: RunLoopMode.defaultRunLoopMode)

private func sendNotification(){
    ...
}

2
投票

这就是我要做的:

当应用程序启动时,我会启动一个计时器,每秒更新一次以检查时间何时变化。

我第一次发现时间发生变化时,我会让该计时器无效并启动一个每分钟触发一次的计时器。

此分钟计时器将与状态栏上的时钟同步,延迟不超过一秒。


1
投票

这是使用

scheduledTimer
触发计时器的另一种方法,这种方法使用一个函数,一旦计时器被触发,就会循环调用自身,而不是使用固定的 60 秒间隔。

private var timer: Timer?

private func setupTimer() {
    let calendar: Calendar = .current
    let seconds = calendar.component(.second, from: Date())
    timer = Timer.scheduledTimer(
        withTimeInterval: 60 - TimeInterval(seconds), repeats: false
    ) { [weak self] timer in
        self?.timer = nil
        // Do something, eg. update UI
        self?.setupTimer()
    }
}

0
投票

没有这样的通知,所以我想你需要编写一些经常轮询时间的代码。我将为此创建一个运行循环源,将其添加到通用模式中,并让它检查自您最喜欢的参考日期以来的当前

NSTimerInterval
是否大于或等于某个预先计算的
NSTimeInterval
,其中包含时间间隔接下来整整一分钟。


0
投票

基于@stefanlindbohm的答案,但没有对

self
的强烈引用,并且如果对象已被释放,则在下次触发时停止计时器:

var zeroSeconds = DateComponents()
zeroSeconds.second = 0
guard let nextMinute = Calendar(identifier: .gregorian).nextDate(after: Date(), matching: zeroSeconds, matchingPolicy: .nextTime, direction: .forward) else {
    // This should not happen since there should always be a next minute
    return
}

var timer: Timer?
timer = Timer(fire: nextMinute.advanced(by: 0.1), interval: 60, repeats: true) { [weak self] _ in
    guard let self = self else {
        timer?.invalidate()
        timer = nil
        return
    }

    // Do your work here, using `self`.
}

if let timer = timer {
    RunLoop.main.add(timer, forMode: .default)
}

0
投票

这是一个 SwiftUI ViewModel 的示例,当分钟值发生变化时,它将每分钟发布当前时间。这可以轻松修改以发布应用程序范围更新的通知。

final class TimeChangeViewModel: ObservableObject {
@Published var time = Date()
private var timer: Timer?

init() {
    setInitialTimer()
}

deinit {
    timer?.invalidate()
}

private func setInitialTimer() {
    let components = Calendar.current.dateComponents([.second], from: Date())
    let currentSecondsValue = components.second ?? 0
    let secondsUntilNextMinute: TimeInterval = TimeInterval(60 - currentSecondsValue)
    
    timer = Timer.scheduledTimer(withTimeInterval: secondsUntilNextMinute, repeats: false, block: { _ in
        self.updateTime()
        self.setOneMinuteUpdateTimer()
    })
}

private func setOneMinuteUpdateTimer() {
    timer?.invalidate()
    
    timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true, block: { _ in
        self.updateTime()
    })
    
    timer?.tolerance = 0.5
}

private func updateTime() {
    DispatchQueue.main.async {
        self.time = Date()
        // could post a custom notification here...
    }
}

}

© www.soinside.com 2019 - 2024. All rights reserved.