How to make counting labels in swift? - ios

I have 2 counting labels
i.e. label1 and label 2
I want to start count from 0 to label count value in fixed duration.
I have tried using timer, but labels are not set in same time.
Code I have tried
let label1Value = 20
let label2Value = 30
var label1Count = 0
var label2Count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
if label1Count > label1Value && label2Count > label2Value {
timer.invalidate()
}
if label1Count <= label1Value {
label1.text = label1Count
label1Count += 1
}
if label2Count <= label2Value {
label2.text = label2Count
label2Count += 1
}
}
Help me if anything present to make counting labels same time in fixed duration

I don't see a way to do this with one Timer. Use a separate scheduledTimer object for each label.
let label1Value = 20.0
let label2Value = 30.0
var label1Count = 0.0
var label2Count = 0.0
let duration = 10.0 //This is the duration u want to finish within
let timer1 = Timer.scheduledTimer(withTimeInterval: duration/label1Value, repeats: true) {t in
if self.label1Count <= self.label1Value {
self.l1.text = "\(Int(self.label1Count))"
self.label1Count += 1
}
}
let timer2 = Timer.scheduledTimer(withTimeInterval: duration/label2Value, repeats: true) {t in
if self.label2Count <= self.label2Value {
self.l2.text = "\(Int(self.label2Count))"
self.label2Count += 1
}
}

Related

Swift - Re-add time back into Timer

I have a countdown Timer that shows seconds and milliseconds. The user can start/stop recording multiple times until the timer hits zero. The user can also delete a previous recording at which point I have to re-add that deleted time back into the initial 20 secs. There are 2 issues.
The first issue is when the timer is stopped, the remaining time that shows on the timer label doesn't match the time culmination of the recordings. From my understanding this might be a RunLoop issue and I don't think there is anything that I can do about the inaccuracies.
let initialTime = 20.0
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
print("assetDuration: ", assetDuration)
cumulativeTimeForAllAssests += assetDuration
}
print("\ncumulativeTimeForAllAssests: ", cumulativeTimeForAllAssests)
After starting/stopping 5 times, the remaining time on the timer label says 16.5 but the culmination of the assets time is 4.196666.... The timer label should say 15.8, it's 0.7 milli off. The more I start/stop the recording, the more inaccurate/further off the culmination time - the initial time and the timer label time is.
assetDuration: 0.7666666666666667
assetDuration: 0.9666666666666667
assetDuration: 0.7983333333333333
assetDuration: 0.7333333333333333
assetDuration: 0.9316666666666666
cumulativeTimeForAllAssests: 4.196666666666667
The second issue is because I'm using seconds and milliseconds in my timerLabel, when I add re-add the subtracted time back in via deleteAssetAndUpdateTimer(...), I use the parts of modf() to update the seconds and milliseconds. I couldn't think of another way to update the timer. I know there has to be a more accurate way to do it.
Timer code:
weak var timer: Timer?
var seconds = 20
var milliseconds = 0
let initialTime = 20.0
func startTimer() {
invalidateTimer()
if seconds == Int(initalTime) && milliseconds == 0 {
timerIsRunning()
}
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
}
func timerIsRunning() {
updateTimerLabel()
if milliseconds == 0 {
seconds -= 1
}
milliseconds -= 1
if milliseconds < 0 {
milliseconds = 9
}
if seconds == 0 && milliseconds == 0 {
invalidateTimer()
updateTimerLabel()
}
}
func invalidateTimer() {
timer?.invalidate()
timer = nil
}
func updateTimerLabel() {
let milisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = "\(secondsStr).\(milisecStr)"
}
Delete asset and update timer code:
// the timer is stopped when this is called
func deleteAssetAndUpdateTimer(_ assetToDelete: AVURLAsset) {
var cumulativeTimeForAllAssests = 0.0
for asset in arrOfAssets {
let assetDuration = CMTimeGetSeconds(asset.duration)
cumulativeTimeForAllAssests += assetDuration
}
let timeFromAssetToDelete = CMTimeGetSeconds(assetToDelete.duration)
let remainingTime = self.initialTime - cumulativeTimeForAllAssests
let updatedTime = remainingTime + timeFromAssetToDelete
let mod = modf(updatedTime)
self.seconds = Int(mod.0)
self.milliseconds = Int(mod.1 * 10)
updateTimerLabel()
// remove assetToDelete from array
}
The big issue here was I was using a Timer to countdown which was incorrect. Following #LeoDabus' comments, I instead used CACurrentMediaTime():
let timerLabel = UILabel()
let maxRecordingTime = 30.0
lazy var elapsedTime = maxRecordingTime
var startTime: CFTimeInterval?
var endTime: CFTimeInterval?
weak var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
updateTimerLabel(with: Int(maxRecordingTime))
}
#IBAction func recordButtonPressed(_ sender: UIButton) {
if startTime == nil {
startTimer()
} else {
stopTimer(updateElapsed: true)
}
}
func startTimer() {
if elapsedTime == 0 { return }
stopTimer()
startTime = CACurrentMediaTime()
endTime = startTime! + elapsedTime
print("startTime: \(startTime!) | endTime: \(endTime!)")
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { [weak self] _ in
self?.timerIsRunning()
}
}
func timerIsRunning() {
guard let startTime = startTime, let endTime = endTime else { return }
let currentTime = CACurrentMediaTime()
let remainingTime = currentTime - startTime
print("%2d %.3lf", elapsedTime, remainingTime)
if currentTime >= endTime {
print("stopped at - currentTime: \(currentTime) | endTime: \(endTime)")
stopTimer(updateElapsed: true, currentTime: currentTime)
return
}
let countDownTime: Double = elapsedTime - remainingTime
let seconds = Int(countDownTime)
updateTimerLabel(with: seconds)
}
func updateTimerLabel(with seconds: Int) {
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"
timerLabel.text = secondsStr
}
func stopTimer(updateElapsed: Bool = false, currentTime: Double? = nil) {
timer?.invalidate()
timer = nil
if updateElapsed {
updateElapsedTime(using: currentTime)
}
startTime = nil
endTime = nil
}
func updateElapsedTime(using currentTime: Double? = nil) {
guard let startTime = startTime else { return }
var timeNow = CACurrentMediaTime()
if let currentTime = currentTime {
timeNow = currentTime
}
var updatedTime = elapsedTime - (timeNow - startTime)
if updatedTime < 0 {
updatedTime = 0
}
elapsedTime = updatedTime
}
func resetElapsedTime() { // This is for a resetButton not shown here
elapsedTime = maxRecordingTime
}

Coreplot Animation of a sequence of plots

I have a number of plots, which depict (x,y) data at different time intervals, and wish to plot them in a sequence one after the other(like a gif file). My approach was generate all the plots, use a Timer.scheduledTimer and initially hide all plots, unhiding current plot and hiding previous plot at each fired schedule. The time between each plot hiding/unhiding shows a blank graph for more time than the plots are shown. Each plot has 32x32 data points. How can I speed this up, so I never see a blank graph? Another approach was to fade out one plot, whilst introducing the next, but I see the same effect.
#objc func tapAnimationButton(_ sender: Any) {
isAnimating = !isAnimating
plotSpacesUserInteraction = !plotSpacesUserInteraction
if let _animationButton = animationButton {
_animationButton.isSelected = isAnimating
previousAnimatedPlot = nil
if isAnimating {
animationCounter = 0
for i in 0..<plotDetails.count {
// fieldsplots[i].isHidden = true
fieldsplots[i].opacity = 0.0
}
animationTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(animateGraph(_:)), userInfo: nil, repeats: true)
if let _animationTimer = animationTimer {
animateGraph(_animationTimer)
}
}
else {
animationTimer?.invalidate()
animationTimer = nil
}
}
}
#objc func animateGraph(_ timer: Timer) {
if animationSeconds > 120.0 {
timer.invalidate()
self.animationTimer = nil
animationSeconds = 0.0
animationCounter = 0
previousAnimatedPlot = nil
}
else {
if let currentAnimatedPlot = self.graph.plot(at: animationCounter) {
// previousAnimatedPlot?.isHidden = true
// currentAnimatedPlot.isHidden = false
previousAnimatedPlot?.opacity = 1.0
let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
fadeOutAnimation.duration = 0.1
fadeOutAnimation.isRemovedOnCompletion = false
fadeOutAnimation.fillMode = CAMediaTimingFillMode.forwards
fadeOutAnimation.toValue = Float(0.0)
previousAnimatedPlot?.add(fadeOutAnimation, forKey: "animateOpacity")
currentAnimatedPlot.opacity = 0.0
let fadeInAnimation = CABasicAnimation(keyPath: "opacity")
fadeInAnimation.duration = 0.1
fadeInAnimation.isRemovedOnCompletion = false
fadeInAnimation.fillMode = CAMediaTimingFillMode.forwards
fadeInAnimation.toValue = Float(1.0)
currentAnimatedPlot.add(fadeInAnimation, forKey: "animateOpacity")
previousAnimatedPlot = currentAnimatedPlot
}
animationSeconds += 0.5
animationCounter += 1;
if animationCounter >= plotDetails.count {
animationCounter = 0
}
}
}
The fade in/out method actually works...not sure how I missed that.
As an add-on to answer, in order to produce a gif image one needs to hide/unhide the plots in sequence
for currentPlot in self.graph.allPlots() {
currentPlot.isHidden = true
}
var images: [UIImage] = []
var previousPlot: CPTPlot?
for currentPlot in self.graph.allPlots() {
if let _previousPlot = previousPlot {
_previousPlot.isHidden = true
}
currentPlot.isHidden = false
if let image = graph.imageOfLayer() {
images.append(image)
}
previousPlot = currentPlot
}
for currentPlot in self.graph.allPlots() {
currentPlot.isHidden = false
}
if images.count > 2 {
TwoDPlot_Utilities.GIFExport(with: images, plot: thisplot, plotIndex: plotIndex, frameDelay: 0.5)
}

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()
}

timeInterval Variable won't work

I'm trying to change the timeInterval in a scheduledTimer. I'm trying to do this by changing a variable to the interval and than setting the timeInterval to this variable. I don't get any errors but the timeInterval won't change. Can someone help me?
var enemyTimer = Timer()
var playTime = 0
var enemySpawnTime: Double = 3
enemyTimer = Timer.scheduledTimer(timeInterval: Double(enemySpawnTime), target: self, selector: #selector(GameScene.enemySpawn), userInfo: nil, repeats: true)
playTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GameScene.ingameTimer), userInfo: nil, repeats: true)
func enemySpawn() {
let enemy = SKSpriteNode(imageNamed: "Enemy")
let minValue = self.size.width / 8
let maxValue = self.size.width - 20
let spawnPoint = UInt32(maxValue - minValue)
enemy.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)), y: self.size.height)
let action = SKAction.moveTo(y: -70, duration: 5)
enemy.run(action)
self.addChild(enemy)
}
func ingameTimer() {
playTime += 1
if(playTime >= 10 && playTime < 30){
enemySpawnTime = 2
print(enemySpawnTime)
}else
if(playTime >= 30 && playTime < 60){
enemySpawnTime = 1
print(enemySpawnTime)
}else
if(playTime >= 60 && playTime < 120){
enemySpawnTime = 0.75
print(enemySpawnTime)
}else
if(playTime >= 120 && playTime < 180){
enemySpawnTime = 0.5
print(enemySpawnTime)
}else
if(playTime >= 180 && playTime < 240){
enemySpawnTime = 0.25
print(enemySpawnTime)
}
}
I hope someone can help me!
Thanks!
The reason why your code doesn't work is because the Timer object doesn't know that its interval needs to be in sync with your enemySpawnTime. The solution is simple, just recreate the timer when you change the enemy spawn time.
But...
You should NEVER use Timer (NSTimer prior to Swift 3) or GCD to delay stuff when you're using SpriteKit. See this for more details.
The correct way to do this is to create a sequence of SKActions.
Assuming self is a subclass of SKScene, you can do this:
override func didMove(to view: SKView) {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
where somePlaceholderNode is just a node that does nothing but run the action. I'll explain this later.
And you should do this for the other timer as well.
Now whenever you change the timer interval, also do this:
somePlaceholderNode.removeAllActions()
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
Here I first remove the action that the node was running, and tell it to run almost the same action, but with a different time interval. You can add this block of code to the didSet block of enemySpawnTime:
var enemySpawnTime: Double = 3 {
didSet {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
}
Now your code should work!
The reason why we want a placeholder node here is because when we remove the action by calling removeAllActions, we don't want to remove all the actions that is running.

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.

Resources