UILabel Text label and timer function issue on table view refresh - ios

I have created a timer function in a subclass of UITableViewCell, which updates the label of itself (the label text) using a timer:
func handleCountdown(){
if timerIsRunning == false { //check if timer is NOT running, otherwise update timer
timerIsRunning = true
startTime = activity.countdownValue
runTimer()
}
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer(){
let hours = Int(startTime) / 3600
let minutes = Int(startTime) / 60 % 60
if startTime > 0 {
startTime -= 1
print("The start time for " + String(activity.name) + "is " + String(startTime))
switch startTime {
case 0:
activity.isActive = false
activity.timerIsRunning = false
activity.countdownValue = 0
timer.invalidate()
activitiesRef = Database.database().reference().child("Users/\(userID)/Activities")
activitiesRef.child(self.activity.id).setValue([ "id": self.activity.id,
"name": self.activity.name,
"isActive": self.activity.isActive,
"locString": self.activity.locationString,
"locLat": self.activity.locLat,
"locLong": self.activity.locLong,
"privacySetting": self.activity.privacySetting,
"timerIsRunning": self.activity.timerIsRunning,
"countdownValue": self.activity.countdownValue])
returnToInactiveDelegate?.activeToInactive(data: activity)
case 1..<60: //display seconds
let count = String(Int(startTime))
countdownTimer.text = count
print("The start time for " + String(activity.name) + "is " + String(startTime))
//countdownTimer.text = String(Int(startTime)) + "s"
case 60..<3600: //display minutes
print("The start time for " + String(activity.name) + "is " + String(startTime))
countdownTimer.text = String(Int(minutes)) + "m"
case 3600..<86400: // display hours and minutes
print("The start time for " + String(activity.name) + "is " + String(startTime))
countdownTimer.text = String(Int(hours)) + "h \(minutes)m"
default:
timer.invalidate()
activity.countdownValue = 0
}
}
When the timer reaches zero, a delegate function deletes the appropriate cell and moves it to a different section in the table view, as follows:
func activeToInactive(data: Activity) {
if let i = activeActivities.index(where: { $0.isActive == false }) {
print("array index to remove: should be where it is NOT active = \(i)")
activeActivities.remove(at: i)
}
inactiveActivities.insert(data, at: 0)
self.tableView.reloadData()
}
Finally, upon table view reload, it does one last check to see if the timer is still running or not (to see if it should start the timer from the initial start time, or not do anything and let it handle the countdown) as follows:
let inactiveCell = tableView.dequeueReusableCell(withIdentifier: inactiveIdentifer , for: indexPath) as! InactiveCell
let activeCell = tableView.dequeueReusableCell(withIdentifier: activeIdentifier, for: indexPath) as! ActiveCell
if indexPath.section == 0 && self.activeActivities.count != 0 {
activeCell.name.text = self.activeActivities[indexPath.row].name
activeCell.location.text = self.activeActivities[indexPath.row].locationString
activeCell.activity = self.activeActivities[indexPath.row]
activeCell.returnToInactiveDelegate = self
if activeCell.activity.isActive == true && activeCell.timerIsRunning == false {
activeCell.activity.countdownValue = self.activeActivities[indexPath.row].countdownValue
print(String(self.activeActivities[indexPath.row].countdownValue))
activeCell.handleCountdown()
}
activeCell.countdownTimer.text = String(self.activeActivities[indexPath.row].countdownValue)
//activeCell.countdownTimer.text = self.activeActivities[indexPath.row].
return activeCell
The issue I am having is that the countdown label text is not updating, despite it showing the correct output in terminal console. My error is repeatable via this example: there are two active activities, with countdowns functioning independently of each other.
activeActivity[0] runs to zero, then is handled by case 0 in the ojbc func updateTimer, and passed through the delegate that then deletes it from active and moves it to inactive. The remaining one (what used to be activeActivity[1], is now activeActivity[0]) moves "up" in the queue and the timer continues running according to the terminal log, but the countdownLabel.text is stuck at 1s, despite the terminal showing the execution of updateTimer, with it printing the correct start time that SHOULD be displayed in the table view cell.

Related

Synchronous start of the event

Is there a way to run the code simultaneously on different devices? Let's say I want that when I click on a button on one of the devices, the function starts simultaneously on both the first and the second device? I tried to use a timer with a time check for 3 seconds ahead, but the function is triggered with a delay of 0.5 seconds on the second device
func getEventTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = (UInt64(interval) + (3)) * 1000
return result
}
func getCurrentTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = UInt64(interval * 1000)
return result
}
func startTimer(time : UInt64){
Timer.scheduledTimer(withTimeInterval: 0.0001, repeats: true) { timer in
switch getCurrentTime() {
case time - 1000 :
DispatchQueue.main.async {
countdownImageTimer.image = UIImage(named: "Start")
}
break
case time :
DispatchQueue.main.async {
countdownImageTimer.removeFromSuperview()
}
self.setShips()
timer.invalidate()
break
default:
break
}
}
}

how to remove cell index when timer gets complete after 5 min ios swift 5 , when called api not repeat timeragain of same index

I want to implement timer logic, when 5 min gets complete then my Tableview reload and its remove that particular index, I have tried not gets works, and timer get fast
//Timer ACtion Method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
Set Timer value nil, And check when API called then the timer will not pass any selector method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
timer = nil
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
=====================================
2nd method to implement timer:-
Initialize Variable
var timer:Timer?
var totalMinut:Int = 2
var totalSecond:Int = 120
var timeLeft = 120
Add timer function
func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
#objc func onTimerFires() {
var minutes: Int
var seconds: Int
if totalSecond == 1 {
timer?.invalidate()
timer = nil
}
totalSecond = totalSecond - 1
minutes = (totalSecond) / 60
seconds = (totalSecond) % 60
timerLabel.text = String(format: "%02d:%02d", minutes, seconds)
}
Call "setUpTimer" method where you have required. In my case, I have called it in the "viewDidLoad" method of a view controller
override func viewDidLoad() {
super.viewDidLoad()
setupTimer()
}

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0) when timer calls a function

I got two problems, the first one is I'm getting the error message described above (it includes a beautiful crash) every time a Timer calls this function:
func updateTime() {
let currentTime = Int(player.currentTime)
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
if seconds < 10 {
tempo.text = String(minutes) + ":0" + String(seconds)
} else {
tempo.text = String(minutes) + ":" + String(seconds)
}
durationSliderOutlet.value = Float(player.currentTime)
if currentTime == Int(player.duration - 1) {
playpauseOutlet.setTitle("Play", for: .normal)
isPlaying = false
}
print("currentTime: \(currentTime)")
print(Int(player.duration))
print("minutes: \(minutes)")
print("seconds: \(seconds)")
print(isPlaying)
}
I'm just trying to display the current time of an audio file in a label, and if I comment out this code
if seconds < 10 {
tempo.text = String(minutes) + ":0" + String(seconds)
} else {
tempo.text = String(minutes) + ":" + String(seconds)
}
...everything works just fine!
The second one throws the same crash and error when I press a button...
#IBAction func playpauseButton(_ sender: UIButton) {
if isPlaying == false {
playpauseOutlet.setImage(#imageLiteral(resourceName: "pause.png"), for: .normal)
player.play()
isPlaying = true
} else {
playpauseOutlet.setImage(#imageLiteral(resourceName: "play.png"), for: .normal)
player.pause()
isPlaying = false
}
}
Note: (#imageLiteral(resourceName: "play.png") appears as a little white rectangle. And if I try something like playpauseOutlet.setImage(UIImage(named: "play.png"), for: .normal) it also crashes.
What I'm pretending to do with this code is to change the "play" image of a button to "pause" image or vice versa depending of the isPlaying state. Thank in advance!

Why is the function to update my progress slider called 3 times using addPeriodicTimeObserverForInterval in AVPlayer?

Hello I've been trying to figure out this issue and don't know where to look. I'm using AVPlayer to play videos, and I have a UISlider whose value updates every second based on the progresss of the video using addPeriodicTimeObserverForInterval.
When I press pause, the UISlider thumb stops immediately as expected. However, immediately after I press play to resume the video, the thumb moves slightly before it continues progressing as normal every second.
I can't figure out why it's doing this. I'd like to have the UISlider thumb progress fluently, i.e. if I paused the video at 1.5 seconds in, and I play the video again, it SHOULD wait 0.5 seconds before the thumb moves again.
Here's a snippet of my code:
override func viewDidLoad()
{
super.viewDidLoad()
....
isPlaybackSliderTouched = false
isPauseButtonTouched = false
setUpPlayerControls()
....
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(true)
let timeIntervalOne: CMTime = CMTimeMakeWithSeconds(1.0, 10)
playbackSliderTimer = avPlayer.addPeriodicTimeObserverForInterval(timeIntervalOne,
queue: dispatch_get_main_queue()) { (elapsedTime: CMTime) -> Void in
self.observeTime(elapsedTime)
}
....
playPauseButton.addTarget(self, action: "onClick:", forControlEvents: .TouchUpInside)
}
func observeTime(elapsedTime: CMTime)
{
let duration = CMTimeGetSeconds(avPlayer.currentItem!.duration)
if isfinite(duration) && avPlayer.rate == 1
{
print("ENNNTNTTERRR")
let elapsedTime = CMTimeGetSeconds(elapsedTime)
updatePlaybackSlider(elapsedTime: elapsedTime, duration: duration)
}
}
func updatePlaybackSlider(elapsedTime: Float64, duration: Float64)
{
if isPlaybackSliderTouched == false
{
let sliderValue: Float = Float(elapsedTime / duration)
print("sliderValue = \(sliderValue)")
self.playbackSlider.setValue(sliderValue, animated: true)
self.currentTimeLabel.text = self.convertSecondsToHHMMSS(elapsedTime)
self.endTimeLabel.text = self.convertSecondsToHHMMSS(duration - elapsedTime)
print("currentTimeLabel.text = \(currentTimeLabel.text)")
print("endTimeLabel.text = \(endTimeLabel.text)")
}
}
func convertSecondsToHHMMSS(seconds: Float64) -> String
{
let time: Int = Int( floor(seconds) )
print("time = \(time)")
let hh: Int = time / 3600
let mm: Int = (time / 60) % 60
let ss: Int = time % 60
print("seconds = \(ss)")
if hh > 0
{
return String(format: "%02d:%02d:%02d", hh, mm, ss)
}
else
{
return String(format: "%02d:%02d", mm, ss )
}
}
deinit
{
avPlayer.removeTimeObserver(playbackSliderTimer)
}
func onClick(sender: UIButton)
{
print("onClick")
if sender == playPauseButton
{
print("playPauseButton touched")
let playerIsPlaying:Bool = avPlayer.rate > 0
if (playerIsPlaying)
{
isPauseButtonTouched = true
avPlayer.pause()
sender.selected = true
}
else
{
isPauseButtonTouched = false
avPlayer.play()
sender.selected = false
}
}
}
Here's a sample output immediately after I press pause from play state:
setUpPlayerControls
viewDidAppear
ENNNTNTTERRR
sliderValue = 0.0
time = 0
seconds = 0
time = 39
seconds = 39
currentTimeLabel.text = Optional("00:00")
endTimeLabel.text = Optional("-00:39")
ENNNTNTTERRR
sliderValue = 4.76564e-05
time = 0
seconds = 0
time = 39
seconds = 39
currentTimeLabel.text = Optional("00:00")
endTimeLabel.text = Optional("-00:39")
ENNNTNTTERRR
sliderValue = 0.0252767
time = 1
seconds = 1
time = 38
seconds = 38
currentTimeLabel.text = Optional("00:01")
endTimeLabel.text = Optional("-00:38")
ENNNTNTTERRR
sliderValue = 0.0505207
time = 2
seconds = 2
time = 37
seconds = 37
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:37")
handleSingleTap
onClick
playPauseButton touched
pause touched
Here's the continuing output when I tap the play from pause state:
onClick
playPauseButton touched
play touched
ENNNTNTTERRR
sliderValue = 0.0718539
time = 2
seconds = 2
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.0722224
time = 2
seconds = 2
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:02")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.0757659
time = 3
seconds = 3
time = 36
seconds = 36
currentTimeLabel.text = Optional("00:03")
endTimeLabel.text = Optional("-00:36")
ENNNTNTTERRR
sliderValue = 0.101012
time = 4
seconds = 4
time = 35
seconds = 35
currentTimeLabel.text = Optional("00:04")
endTimeLabel.text = Optional("-00:35")
As you can see the end of the first output when I press pause to the beginning of the second output when I press play, there is 2 unnecessary calls for time 00:02 to observeTime(elapsedTime), which in turns call my updatePlaybackSlider, which in turn slightly shifts the slider value to the right a bit before it goes back to calling observeTime(elapsedTime) like normal, i.e. every 1 second.
What are some suggestions for me to fix this?
Thanks.

unable to loop through a Counter in Swift

I guys I have a working countdown timer which I can set manually and countdown from this value.
What I'm trying to achieve now though is set my INTERVALS counter to say 3 and loop through the countdown timer the amount of times that I set in this counter then when the last interval is complete the timer finishes
sounds straight forward in my head I know I need a loop somewhere but everything I have tried just hast worked this is my current code before adding a loop. Have tried adding a while statement instead of the if statement but that didn't do the job
I've now added this loop but still no joy. Anyone know how to do this?
func runIntervalTimer() {
timerForIntervals = NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: Selector("updateTimeInterval"), userInfo: nil, repeats: true)
}
func updateTimeInterval() {
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
intervalHasBeenSet = false
intervalCountdown = 0
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func InterValTimerIncrease(sender: AnyObject) {
intervalCountdown++
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalTimerDecrease(sender: AnyObject) {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalStart(sender: AnyObject) {
runIntervalTimer()
}
You're probably in an infinite loop here:
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
Your condition is intervalHasBeenSet == true but it's never being set to false inside the loop, so it can never exit.
If you want the timer to count down each interval, I think all you need to do is:
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
}
Because the updateTimeInterval should be called each interval by NSTimer, you shouldn't need a loop inside this function.
To have the timer count down multiple times, you need an extra variable (intervalNum):
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 && intervalNum > 0 {
// countdown in the current interval
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
else
{
// move on to the next interval
intervalNum--
intervalCountdown = 20 // or grab the original value from somewhere
}
if (intervalNum == 0)
{
// stop the timer
timerForIntervals.invalidate()
}
}

Resources