Timer Label Not Updated After Switching Views (Swift)

Swift Timer() will not update label after switching between views

You are using segues to transition between VC1 and VC2. When you return from VC2, you are creating an entirely new VC1 which is why you don't see your labels updating.

You should use an unwind segue to return to VC1 from VC2. See here for how to setup and use an unwind segue.

Since you are using swipe gestures to transition between views, you will need to call the unwind segue programmatically. See the second half of this answer for how to set up the unwind segue and give it an identifier so that you can call it with performSegue(withIdentifier:sender:) from your swipe handler function.

Update Label Text with Timer Values

I see a few issues:

  • The finance object goes out of scope when fireTimer() returns. It will be deallocated then. The timer will be setting a label text on a no longer existing view controller.
  • Instantiating a UIViewController with FinanceVC() doesn't display it on screen. After instantiating you need to explicitly show. You can do this for example by calling present(_:animated:completion:) from a parent view controller.
  • The timer updates dayLabelText which does not update dayLabel.text

Might be good to follow a basic YT tutorial on how to display a view controller.

Good luck, you'll get it soon enough!

New to swift: Why is the Timer not updating to the UILabel?

Your timer is working correctly. Your label is getting updated every second, but it is always getting updated to the same value.

The problem is that you are only computing date once when you create your MarsTime object.

One way to fix that is to make date a computed property that will always return the current time each time it is read:

var date: TimeInterval { return Date().timeIntervalSince1970 }

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

updating label variables from a running timer

Your main issue is that you aren't calling updateLabels to update your labels.

I would suggest using property observers (didSet) to set your labels as the values change instead of relying on a separate function to change them.

Also, you need to call increaseGold in your timer handler:

class ViewController: UIViewController {

@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var goldCounter: UILabel!
@IBOutlet weak var turnCounter: UILabel!

var seconds = 15 {
didSet {
timerLabel.text = String(seconds)
}
}

var timer = Timer()

var gold = 1000 {
didSet {
goldCounter.text = String(gold)
}
}

var turns = 1 {
didSet {
turnCounter.text = String(turns)
}
}

func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer),userInfo: nil, repeats: true)
}

@objc func updateTimer() {
seconds -= 1
if seconds == 0 {
seconds = 15
}
increaseGold()
}

func increaseGold () {
if seconds == 1 {
gold = gold + 1000
turns = turns + 1
}
}

override func viewDidLoad() {
super.viewDidLoad()
seconds = 15
gold = 1000
turns = 1
self.runTimer()
}
}

constraints are reapplied when timer is running and changes label text

It isn't a threading issue. It is an auto layout issue.

Presumably you have positioned the yellow square view in your storyboard using constraints.

You are then modifying the yellow square's frame directly by modifying the center property; this has no effect on the constraints that are applied to the view. As soon as the next auto layout pass runs (triggered by the text changing, for example) the constraints are reapplied and the yellow square jumps back to where your constraints say it should be.

You have a couple of options;

  1. Compute the destination point offset from the center of the view and then apply those offsets to the constant property of your two centering constraints

  2. Add the yellow view programatically and position it by setting its frame directly. You can then adjust the frame by modifying center as you do now.

iOS Swift Update text label after 1 second

You shouldn't use the sleep() function as this will suspend the main thread and cause your app to become non-responsive. NSTimer is one way to achieve this. It will dispatch a function at a specified time in the future.

For example -

var countdown=0
var myTimer: NSTimer? = nil

override func viewDidAppear(animated: Bool) {
countdown=5
myTimer = NSTimer(timeInterval: 1.0, target: self, selector:"countDownTick", userInfo: nil, repeats: true)
countdownLabel.text = "\(countdown)"
}

func countDownTick() {
countdown--

if (countdown == 0) {
myTimer!.invalidate()
myTimer=nil
}

countdownLabel.text = "\(countdown)"
}


Related Topics



Leave a reply



Submit