从后台计时器更新标签 [英] update label from background timer
问题描述
美好的一天,
我正在开发一个锻炼应用程序,除非将其移动到后台,否则效果很好。计时器暂停时。我找到了一个我工作的背景计时器的例子,但现在我无法获得显示锻炼持续时间的UILabel。在控制台中,它声明我正在从我理解的主线程中访问一个对象。我不知道该怎么做是让UILabel更新,因为定时器在后台线程中更新,更新标签位于主线程中。
这是我所拥有的(打印语句帮助我遵循代码):
import UIKit
class ViewController:UIViewController {
var time = 0
var timer = Timer()
@IBOutlet weak var outputLabel:UILabel!
@IBOutlet弱var start:UIButton!
@IBOutlet弱变暂停: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(格式:%02i:%02i:%02i,小时,分钟,秒)
}
func startButtonPressed(){
outputLabel.text =Workout Started
start.isHidden = true
暂停。 isHidden = false
_backgroundTimer(重复:true)
print(Calling _backgroundTimer(_ :))
}
func pausedButtonPressed(){
outputLabel.text =Workout Paused
timer.invalidate()
pauseWorkout()
}
func pauseWorkout(){
paused.isHidden = true
start.isHidden = false
}
func _backgroundTimer(重复:Bool) - > Void {
NSLog(_ backgroundTimer invoked。);
//我使用的线程是后台线程,dispatch_async将设置后台线程来执行块中的代码。
DispatchQueue.global(qos:.userInitiated).async {
NSLog(NSTimer将被安排......);
//定义NSTimer
self.timer = Timer.scheduledTimer(timeInterval:1,target:self,selector:#selector(self._backgroundTimerAction(_ :)),userInfo:nil ,重复:真实);
print(Starting timer)
//获取当前的RunLoop
let runLoop:RunLoop = RunLoop.current;
//将计时器添加到RunLoop
runLoop.add(self.timer,forMode:RunLoopMode.defaultRunLoopMode);
//手动调用RunLoop的run方法
NSLog(NSTimer scheduled ...);
runLoop.run();
}
}
@objc func _backgroundTimerAction(_ timer:Foundation.Timer) - > Void {
print(_ backgroundTimerAction(_ :))
time + = 1
NSLog(time count - > \ (时间));
}
覆盖func viewDidLoad(){
super.viewDidLoad()
print(viewDidLoad())
打印(隐藏按钮)
paused.isHidden = true
start.isHidden = false
print(清算标签)
outputLabel.text =
timerLabel.text =
print(\(timer))
timer.invalidate()
time = 0
}
}
这是视图的快照控制器,我想更新持续时间。
非常感谢任何人都能提供的任何帮助。
此致,
Kevin
不要试图在后台运行计时器,而是记录 startDate
锻炼开始并计算时间间隔VAL。这样,应用程序实际上不必在后台运行以跟踪锻炼时间。计时器仅用于更新用户界面。
暂停现在可以记录当前的锻炼间隔。当锻炼重新开始时,它会从 Date()
中减去当前的锻炼间隔,以获得新的调整 startDate
。 / p>
为进入后台和前台的应用添加通知,以便在锻炼处于活动状态时重新启动UI更新计时器:
import UIKit
enum WorkoutState {
case inactive
case active
case暂停
}
class ViewController:UIViewController {
var workoutState = WorkoutState.inactive
var workoutInterval = 0.0
var startDate = Date()
var timer = Timer()
@IBOutlet weak var outputLabel:UILabel!
@IBOutlet弱var start:UIButton!
@IBOutlet弱变暂停: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(格式:%02i:%02i:%02i,小时,分钟,秒)
}
func startButtonPressed(){
if workoutState == .inactive {
startDate = Date()
}否则如果exerciseState == .paused {
startDate = Date()。addingTimeInterval(-workoutInterval)
}
workoutState = .active
outputLabel.text =Workout Started
start .isHidden = true
paused.isHidden = false
updateTimerLabel()
_foregroundTimer(重复:true)
print(Calling _foregroundTimer(_ :))
}
func pausedButtonPressed(){
//记录w orkout持续时间
workoutInterval = floor(-startDate.timeIntervalSinceNow)
outputLabel.text =Workout Paused
workoutState = .paused
timer.invalidate()
pauseWorkout()
}
func pauseWorkout(){
paused.isHidden = true
start.isHidden = false
}
func _foregroundTimer(重复:Bool) - > Void {
NSLog(__ foregroundTimer invoked。);
//定义一个Timer
self.timer = Timer.scheduledTimer(timeInterval:1,target:self,selector:#selector(self.timerAction(_ :)),userInfo:nil ,重复:真实);
打印(开始计时器)
}
@objc func timerAction(_ timer:Timer){
print( timerAction(_ :))
self.updateTimerLabel()
}
@objc func observerMethod(通知:NSNotification){
如果notification.name == .UIApplicationDidEnterBackground {
print(app enter background)
//停止UI更新
timer.invalidate()
} else if notification.name == .UIApplicationDidBecomeActive {
print(app enter foreground)
if workoutState == .active {
updateTimerLabel()
_foregroundTimer(重复:true)
}
}
}
覆盖func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.default.addObserver(self,selector:#selector(observerMethod),name:.UIApp licationDidEnterBackground,object:nil)
NotificationCenter.default.addObserver(self,selector:#selector(observerMethod),name:.UIApplicationDidBecomeActive,object:nil)
print( viewDidLoad())
print(隐藏按钮)
paused.isHidden = true
start.isHidden = false
print(清除标签)
outputLabel.text =
timerLabel.text =
print(\(timer))
timer.invalidate()
}
}
原始答案
只需在主循环上调用 updateTimerLabel()
:
DispatchQueue.main.async {
self.updateTimerLabel()
}
全功能:
@objc func _backgroundTimerAction(_ timer:Timer){
print(_ backgroundTimerAction(_ :))
时间+ = 1
DispatchQueue.main.async {
self.updateTimerLabel()
}
NSLog(时间计数 - > \(时间))
}
注意:
- 在后台线程上运行计时器并没有给你带来任何好处,只是在设置时遇到麻烦。我建议只需在主线程上运行它。
- 无需将
- > Void
添加到Swift函数定义中;即默认。 - Swift通常不需要分号
;
,所以丢失它们。 -
self.time
已经是Int
,因此创建一个新的Int
来自它是不必要的。
替换:
let hours = Int(self.time)/ 3600
with:
let hours = self.time / 3600
Good Day,
I am developing a workout app that works fine except when moving it to the background. The timer suspends when it does. I found an example of a background timer that I have working but now I can't get the UILabel that displays the duration of the workout to work. In the console it states that I am accessing an object from the main thread which I understand. What I don't know how to do is to get the UILabel to update as the timer updates from within the background thread with the update label being in the main thread.
Here is what I have (print statements help me follow the code):
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
}
}
Here is a snapshot of the view controller and I want to update the Duration.
Any assistance anyone can provide is greatly appreciated.
Sincerely,
Kevin
Instead of trying to run a timer in the background, record the startDate
of the start of your workout and compute the time interval. That way, the app doesn't actually have to run in the background to keep track of the workout time. The timer will only be used to update the user interface.
Pausing now works by recording the current workout interval. When the workout restarts, it subtracts the current workout interval from the Date()
to get a new adjusted startDate
.
Add notifications for the app entering the background and foreground so that you can restart the UI update timer if the workout is active:
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()
}
}
Original Answer
Just call updateTimerLabel()
on the main loop:
DispatchQueue.main.async {
self.updateTimerLabel()
}
Full function:
@objc func _backgroundTimerAction(_ timer: Timer) {
print("_backgroundTimerAction(_:)")
time += 1
DispatchQueue.main.async {
self.updateTimerLabel()
}
NSLog("time count -> \(time)")
}
Notes:
- Running the timer on a background thread isn't buying you anything but trouble in setting it up. I'd recommend just running it on the main thread.
- There is no need to add
-> Void
to a Swift function definition; that is the default. - Swift typically doesn't need the semicolons
;
, so lose those. self.time
is already anInt
, so creating a newInt
from it is unnecessary.replace:
let hours = Int(self.time) / 3600
with:
let hours = self.time / 3600
这篇关于从后台计时器更新标签的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!