Update Label from Background Timer

update label from background timer

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

Update label when returning from background

You need to add your observers inside viewDidLoad(), NOT in viewDidAppear

override func viewDidLoad() {
super.viewDidLoad()

NotificationCenter.default.addObserver(self, selector: #selector(teasGoBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(runTimer), name: UIApplication.didBecomeActiveNotification, object: nil)

}

SwiftUI update Textlabel from Background Timer

Change your body to.

var body: some View {
VStack {
Text("\(timeLeft)")

Button(action: {
self.start.toggle()
self.notifyTime = Date().addingTimeInterval(TimeInterval(10)) // add countdown time in sec
self.timeLeft = timeDifference(endTime: self.notifyTime)
self.sendNotification()
//Timer code - Start your timer
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
timeLeft -= 1

if timeLeft == 0 {
timer.invalidate()
//Add your new trigger event
}
}
}) { Text("Start Countdown (10s)")}

}.onAppear(perform: {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge,.sound,.alert]) { (_, _) in
}
})
}

How to run a timer on the background that updates a label in a viewmodel

You are wrongly assigning the event handler as seen in the docs.

Change

timer.Elapsed += new EventHandler(UpdateLabel);

to

timer.Elapsed +=UpdateLabel; //or 
timer.Elapsed +=System.EventHandler(UpdateLabel)

not sure from the top of my head

And

private async void UpdateLabel()

to

private static void UpdateLabel(Object source, System.Timers.ElapsedEventArgs e)

This should allow you to to trigger the Elapsed event. You problem was your confusing regular functions and event functions. You should read up on it here

Update label text with timer on UI Thread, without blocking the UI


to understand how i can run a method on my UI thread, and still interact with the UI. Therefore, using a Timer is not a possibility in this case i'm afraid.

You can use a thread (via Task.Run) and then use Device.InvokeOnMainThreadAsync or Device.BeginInvokeOnMainThread to execute UI updates within that "background" thread:

Task.Run(async() =>
{
while (true)
{
await Device.InvokeOnMainThreadAsync(() =>
{
test.Text = i;
});
await Task.Delay(10000);
i++;
}
});

Note: Also do not use Thread.Sleep as that will hang the entire thread, including its message pump (a really bad thing when it is the UI/main thread).

Updating Winforms Label with Timer and Thread, stock app

Let me point out several things in your implementation.

  1. You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
  2. The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
  3. Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
    public partial class Form1 : Form 
{
Task _runningTask;
CancellationTokenSource _cancellationToken;

public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}

private void onCancelClick()
{
_cancellationToken.Cancel();
}


public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;

}
catch
{
return "404";
}
}

private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}


public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}

Timer that updates label running in background

First of all keep in mind that you cannot perform UI changes in any other thread than the main thread. Having said that, you need the NSTimer to fire in the main queue, otherwise the program will crash when changing the UILabel. Have a look at this links http://bynomial.com/blog/?p=67 and this one http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/Reference/Reference.html

To my knowledge, if you schedule the timer in the for the NSRunLoopCommonModes it will ignore event updates and fire the timer jsut how you want it:

 NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:@selector(timerDidTick:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

-(void) timerDidTick:(NSTimer*) theTimer{
[[self myLabel] setText:@"Timer ticked!"];
}

How to update a UILabel timer after being in background

Store Date() in NSUserDefaults when you go to background in

func applicationDidEnterBackground(_ application: UIApplication) {}

then calculate difference between new Date when you go to foreground in

func applicationWillEnterForeground(_ application: UIApplication) {}

How update a label periodically on iOS (every second)?

The problem is that a scheduledTimer will not get called while the main thread is tracking touches. You need to schedule the timer in the main run loop.

So instead of doing

[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateLabel:) userInfo:nil repeats:YES];

use

NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];


Related Topics



Leave a reply



Submit