ios - 从后台计时器更新标签
问题描述
再会,
我正在开发一个运行良好的锻炼应用程序,除非将其移至后台。计时器暂停时暂停。我找到了一个我正在工作的后台计时器示例,但现在我无法让显示锻炼持续时间的 UILabel 工作。在控制台中,它声明我正在从我理解的主线程访问一个对象。我不知道该怎么做是让 UILabel 随着计时器从后台线程中更新而更新,更新标签位于主线程中。
这是我所拥有的(打印语句帮助我遵循代码):
import UIKit
class ViewController: UIViewController {
var time = 0
var timer = Timer()
@IBOutlet weak var outputLabel: UILabel!
@IBOutlet weak var start: UIButton!
@IBOutlet weak var paused: UIButton!
@IBAction func startButton(_ sender: UIButton) {
startButtonPressed()
}
@IBAction func pausedButton(_ sender: UIButton) {
pausedButtonPressed()
}
@IBOutlet weak var timerLabel: UILabel!
func updateTimerLabel() {
let hours = Int(self.time) / 3600
let minutes = Int(self.time) / 60 % 60
let seconds = Int(self.time) % 60
timerLabel.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
func startButtonPressed() {
outputLabel.text = "Workout Started"
start.isHidden = true
paused.isHidden = false
_backgroundTimer(repeated: true)
print("Calling _backgroundTimer(_:)")
}
func pausedButtonPressed(){
outputLabel.text = "Workout Paused"
timer.invalidate()
pauseWorkout()
}
func pauseWorkout(){
paused.isHidden = true
start.isHidden = false
}
func _backgroundTimer(repeated: Bool) -> Void {
NSLog("_backgroundTimer invoked.");
//The thread I used is a background thread, dispatch_async will set up a background thread to execute the code in the block.
DispatchQueue.global(qos:.userInitiated).async{
NSLog("NSTimer will be scheduled...");
//Define a NSTimer
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self._backgroundTimerAction(_:)), userInfo: nil, repeats: true);
print("Starting timer")
//Get the current RunLoop
let runLoop:RunLoop = RunLoop.current;
//Add the timer to the RunLoop
runLoop.add(self.timer, forMode: RunLoopMode.defaultRunLoopMode);
//Invoke the run method of RunLoop manually
NSLog("NSTimer scheduled...");
runLoop.run();
}
}
@objc func _backgroundTimerAction(_ timer: Foundation.Timer) -> Void {
print("_backgroundTimerAction(_:)")
time += 1
NSLog("time count -> \(time)");
}
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad()")
print("Hiding buttons")
paused.isHidden = true
start.isHidden = false
print("Clearing Labels")
outputLabel.text = ""
timerLabel.text = ""
print("\(timer)")
timer.invalidate()
time = 0
}
}
这是视图控制器的快照,我想更新 Duration。
非常感谢任何人可以提供的任何帮助。
真挚地,
凯文
解决方案
与其尝试在后台运行计时器,不如记录startDate
锻炼开始的时间并计算时间间隔。这样,应用程序实际上不必在后台运行来跟踪锻炼时间。计时器将仅用于更新用户界面。
现在暂停通过记录当前的锻炼间隔来工作。当锻炼重新开始时,它会从 中减去当前锻炼间隔Date()
以获得新的调整后的startDate
.
为进入后台和前台的应用添加通知,以便您可以在锻炼处于活动状态时重新启动 UI 更新计时器:
import UIKit
enum WorkoutState {
case inactive
case active
case paused
}
class ViewController: UIViewController {
var workoutState = WorkoutState.inactive
var workoutInterval = 0.0
var startDate = Date()
var timer = Timer()
@IBOutlet weak var outputLabel: UILabel!
@IBOutlet weak var start: UIButton!
@IBOutlet weak var paused: UIButton!
@IBAction func startButton(_ sender: UIButton) {
startButtonPressed()
}
@IBAction func pausedButton(_ sender: UIButton) {
pausedButtonPressed()
}
@IBOutlet weak var timerLabel: UILabel!
func updateTimerLabel() {
let interval = -Int(startDate.timeIntervalSinceNow)
let hours = interval / 3600
let minutes = interval / 60 % 60
let seconds = interval % 60
timerLabel.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
func startButtonPressed() {
if workoutState == .inactive {
startDate = Date()
} else if workoutState == .paused {
startDate = Date().addingTimeInterval(-workoutInterval)
}
workoutState = .active
outputLabel.text = "Workout Started"
start.isHidden = true
paused.isHidden = false
updateTimerLabel()
_foregroundTimer(repeated: true)
print("Calling _foregroundTimer(_:)")
}
func pausedButtonPressed(){
// record workout duration
workoutInterval = floor(-startDate.timeIntervalSinceNow)
outputLabel.text = "Workout Paused"
workoutState = .paused
timer.invalidate()
pauseWorkout()
}
func pauseWorkout(){
paused.isHidden = true
start.isHidden = false
}
func _foregroundTimer(repeated: Bool) -> Void {
NSLog("_foregroundTimer invoked.");
//Define a Timer
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerAction(_:)), userInfo: nil, repeats: true);
print("Starting timer")
}
@objc func timerAction(_ timer: Timer) {
print("timerAction(_:)")
self.updateTimerLabel()
}
@objc func observerMethod(notification: NSNotification) {
if notification.name == .UIApplicationDidEnterBackground {
print("app entering background")
// stop UI update
timer.invalidate()
} else if notification.name == .UIApplicationDidBecomeActive {
print("app entering foreground")
if workoutState == .active {
updateTimerLabel()
_foregroundTimer(repeated: true)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(observerMethod), name: .UIApplicationDidEnterBackground, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(observerMethod), name: .UIApplicationDidBecomeActive, object: nil)
print("viewDidLoad()")
print("Hiding buttons")
paused.isHidden = true
start.isHidden = false
print("Clearing Labels")
outputLabel.text = ""
timerLabel.text = ""
print("\(timer)")
timer.invalidate()
}
}
原始答案
只需调用updateTimerLabel()
主循环:
DispatchQueue.main.async {
self.updateTimerLabel()
}
全功能:
@objc func _backgroundTimerAction(_ timer: Timer) {
print("_backgroundTimerAction(_:)")
time += 1
DispatchQueue.main.async {
self.updateTimerLabel()
}
NSLog("time count -> \(time)")
}
笔记:
- 在后台线程上运行计时器并没有给你带来任何好处,只是设置它的麻烦。我建议只在主线程上运行它。
- 无需添加
-> Void
到 Swift 函数定义中;这是默认设置。 - 斯威夫特通常不需要分号
;
,所以丢掉那些。 self.time
已经是,因此没有必要从中Int
创建新的。Int
代替:
let hours = Int(self.time) / 3600
和:
let hours = self.time / 3600
推荐阅读
- android - 使用日期选择器和当前日期查找天数
- google-apps-script - 如果 O2 值更新,那么我需要使用模板向 C2 中的地址发送电子邮件。如果单元格 O2 已更新,则电子邮件应仅发送至 C2
- apache-kafka-streams - 如何使用相同的 APPLICATION_ID_CONFIG 运行两个或多个拓扑?
- http - Http POST 请求以 base64 编码的 json 形式返回正文
- javascript - 如何在控制台中打印值而不传递任何函数?
- maven - Maven给出编译错误:引用的jar包不存在
- python - 如何裁剪轮廓的内部?
- python - 使用 Windows CMD 从下载的文件夹安装依赖项
- javascript - 如何在 reactstrap 中垂直而不是水平设置卡片样式
- google-contacts-api - 创建谷歌联系人而不复制他们