从后台计时器更新标签 [英] update label from background timer

查看:142
本文介绍了从后台计时器更新标签的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

美好的一天,



我正在开发一个锻炼应用程序,除非将其移动到后台,否则效果很好。计时器暂停时。我找到了一个我工作的背景计时器的例子,但现在我无法获得显示锻炼持续时间的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(时间计数 - > \(时间))
}






注意:


  1. 在后台线程上运行计时器并没有给你带来任何好处,只是在设置时遇到麻烦。我建议只需在主线程上运行它。

  2. 无需将 - > Void 添加到Swift函数定义中;即默认。

  3. Swift通常不需要分号; ,所以丢失它们。

  4. 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:

  1. 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.
  2. There is no need to add -> Void to a Swift function definition; that is the default.
  3. Swift typically doesn't need the semicolons ;, so lose those.
  4. self.time is already an Int, so creating a new Int from it is unnecessary.

    replace:

    let hours = Int(self.time) / 3600
    

    with:

    let hours = self.time / 3600
    

这篇关于从后台计时器更新标签的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆