Swift newbie here. I have two questions:
First, how do I create a timer which AUTOMATICALLY counts down when a ViewController scene is opened? My problem is that the NSTimer ENDS automatically when the scene is opened.
For instance, whenever I go to the said scene, the TimerLabel says: “Time’s Up!”
Before my second question, below is my tweaked code from: makeapppie.com/…/swift-swift-using-nstimer-to-make-a-timer…/
var timer = NSTimer()
let timeInterval:NSTimeInterval = 0.05
let timerEnd:NSTimeInterval = 120.0 //Timer should end in 120 seconds
var timeCount:NSTimeInterval = 0.0
func timerDidEnd(timer:NSTimer){
timeCount = timeCount - timeInterval
if timeCount <= 0 {
TimerLabel.text = "Time's Up!"
timer.invalidate()
}
}
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
func StartTimer() { // Function called in viewDidLoad
if !timer.valid{
TimerLabel.text = timeString(timeCount)
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: true)
}
}
My second question is this: I do not want to use any UISwitch to choose whether to have the timer counting up, or counting down. I need a COUNTING DOWN timer only which AUTOMATICALLY counts down when view is opened. How do I do that?
Please take note that my time format is: “minutes:seconds” as described in the timeString function.
Please help.
Welcome to SO. I think you're in over your head here.
A timer doesn't count up or count down. It fires on a regular interval. You write a method that gets called each time the timer fires.
Note that 0.05 is a pretty short interval for a timer. Timers are not super-accurate, so you might be trying to get too much out of them.
Your code is a confused mess. You start timeCount out at 0. Your timer method, that gets called repeatedly, is called timerDidEnd even though you want your timer to keep running. Your timerDidEnd method subtracts "timeInterval" (.05 seconds) from timeCount when the timer fires, which makes timeCount negative. You then check to see if timeCount is less than 0 (it will be the very first time through this code) and if it is, you invalidate the timer. You also never do anything to display the value of timeCount.
If you want code that counts down, you need to start out with a value that's larger than 0, and subtract a small amount from it each time the timer fires. Then you need to do something with that timeCount value. If you're not displaying it somewhere, it really isn't doing anything useful.
P.S. On an unrelated subject, the avatar pictures on SO are square. You should post a square avatar picture (or create a square crop to post.) When you post a rectangular picture it gets really stretched-out and looks bad.
My problem is that the NSTimer ENDS automatically when the scene is opened.
It sounds like your putting your NSTimer and the code that manages it in a view controller. In that case, it's not surprising that the timer goes away when that view controller does. If the timer should persist from one view controller to the next, then you should manage it from an entirely separate object from the view controller. Since you don't want the timer object to have to keep track of all the view controllers that could possibly be interested in it, have it post notifications that interested view controllers can listen to.
Related
If user clicks button I hide view after 3 seconds using DispatchQueue.main.asyncAfter. However if user again clicks that button I wan't to postpone hiding activity for next more 3 seconds so previous call of hiding will be ignored.
I have tried following code but it hides view after first 3 seconds.
class MyView: UIView {
private var hideControls: DispatchWorkItem?
func displayControls() {
isHidden = false
hideControls?.cancel()
hideControls = DispatchWorkItem {
print("displayControls: DispatchWorkItem called ")
self.isHidden = true
}
if let hideControls = hideControls {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: hideControls)
}
}
}
If you want to ignore the previous work you can work with Timer, add the property timer,
var timer : Timer?
and on your method for hidden use this.
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false, block: {[weak self] (_) in
#add the action that you want
self?.isHidden = true
})
Br.
Instead of a dispatch work item or GCD, use a Timer:
The Timer is an instance property and is an Optional. It is initially nil.
The Timer is a one-time timer for 3 seconds, and its action method hides the button.
When the user presses the button, safely invalidate the Timer with self.timer?.invalidate() and replace it with a new scheduled Timer — self.timer=...
If you think about it, you will see that this solves the problem. No matter whether a timer was already running, pressing the button now means "regardless of what I may have said in the past, three seconds from now is when the button should be hidden."
I use this technique all the time. For instance, I have a game where the user is to be penalized for each ten seconds that goes by without making a move. So if the user moves, I replace the timer with a new ten-second timer. If the user doesn't within the (new?) ten-second period, the timer fires and the penalty is applied. (It happens that I then make a new timer, but that wouldn't be the case in your example.)
I have a editviewcontroller(secondVC)(which you access by tapping on the uitableviewcell task) where you get the option of setting a reminder. When you set the reminder; an icon appears in front of the task in the UITableViewCell in the firstVC.Now I want that once the reminder is triggered and a notification is sent the icon from the task is removed in realtime. Currently, the way I have set it; if you visit the editVC after the task has been reminded, i compare the current time to time set by the user and then updated a label which says "Time's up".
I want a similar thing to occur with the appropriate cell in the firstVC.
FIRSTVC:
When the time is up, it tells you that time is up and when you return to the firstVC the bell icon is removed. But I want it to happen in realtime even if you are in the firstVC and you don't have to go to the secondVC and then return to firstVC to get the changes.
In short, i want the bell icon to be removed when a task has been reminded to the user which is set in the secondVC. Thanks!
EditVC:
Code:
The following code is executed in the editVC in viewDidLoad. If the current time is more than the time selected, it changes the label to "Time's Up" and changes the bellicon tintcolor to white for that specific reminder.
guard let selectedDate = editnotes?.sSelectedDate,
var needsToRemind = editnotes?.sReminderState else {
print("nil")
return
}
if selectedDate <= Date() && needsToRemind {
editnotes?.sReminderDate = "Time's up"
editnotes?.belliconcolor = .white
reminderMsg.text = editnotes?.sReminderDate
}
You can use to fire post notification.post notification is used to perform an action without going to particular VC.
Do you know about post notification or Notification center? If yes, then it is easy to implement in your code otherwise you need some R&D on its. First of all, register post notification on first vc then after on secondvc fire this notification which is register on first vc. It's simple. If you can't get it then i will send some code for easily got it.
You can fire when your timer stop. And one more important things that when you fire notification , you must need to pass current stop time. Because this time is used to first vc method which is register. In this method, you can compare your reminder time and your current time which is passed by notification if both are same then you can hide the bell otherwise not. One more thing, please Manage array to accurate the code after then reload table.
One problem with your code that I can see right away is that you will only say "Time's up" in your edit VC's viewDidLoad. What happens if time is up a few seconds (or even a second) after viewDidLoad?
If this were my code, I would have a Timer property on BOTH the editVC and on the parent (or first) view controller. It would look like this:
var timesUpTimer : Timer?
Which you can set up in viewWillAppear:
if let selectedDate = editnotes?.sSelectedDate
{
self.timesUpTimer = Timer(fireAt: date, interval: 0, target: self, selector: #selector(doSomething), userInfo: nil, repeats: false)
RunLoop.main.add(timesUpTimer, forMode: RunLoopMode.commonModes)
}
// in the editVC you can have this:
func doSomething()
{
editnotes?.sReminderDate = "Time's up"
editnotes?.belliconcolor = .white
reminderMsg.text = editnotes?.sReminderDate
}
and you would do the appropriate thing in the doSomething you have in the firstVC.
Also, in viewWillDisappear for each view controller, you need to invalidate and set your Timer property to nil.
This is my code:
let playableCards = self.allPlayableCardsViews[0].allSubviews.flatMap { $0 as? UIButton }
var counter: Double = 0
for card in playableCards{
UIView.animate(withDuration: 0.3, delay: TimeInterval(counter), options: .init(rawValue: 0), animations: {
card.alpha = 1.0
print("hello")
}, completion: nil)
counter += (3.7/Double(16))
}
Normally in the print line there is a function. This function gets called the amount of loops which of course is good. However I want to add the same delay that is having my card to fade in. Now my function gets called without the delay, causing 16 functions to execute at the exact same time, which is I think weird because I clearly added a delay. I do not want to use completion since the function needs to be executed at the exact same time as the card fades in. How can it be that the card is fading in one after another and the function(print in this example) gets called without delays?
I now see in my debug session 16 times "hello" while the cards are still fading in.
Thank you.
The animation is delayed, but the block can be called at any time to figure out what properties are being animated -- these don't need to be at the same time.
Use a timer to call your function at the same time as the animation will go off. If you want it to be triggered by the animation actually happening, you may be able to use key-value observing (KVO) on the card.alpha property.
I would like to change from scene to scene after a certain amount of time on one scene in Swift. I am trying to create a survival type game that the player has to survive a level for so long before they can advance to the next level.
I know that after I get to func being called I will be using this to go to the next scene.
self.view?.presentScene(GameScene2())
I am sure that something along the lines of NSTimer is going to be used, but anything that can be given to point me in the right direction would be of great help. Thank you in advance.
An NSTimer is one option. Depending on what sort of accuracy you need and how long your duration will be you may or may not want to use an NSTimer. Example of an NSTimer ...
var myTimer = NSTimer()
func startMyTimer() {
myTimer = NSTimer.scheduledTimerWithTimeInterval(1.5, target: self, selector: "myFunction", userInfo: nil, repeats: true)
//1.5 is the time interval that you want to call the timer, in this case every 1.5 seconds
//the selector calls myFunction which is the function that you want to call every time the timer reaches its time interval
//if you want the timer to repeat and call myFunction() every 1.5 seconds then repeat should be true. if you only want it to be called once then repeat should be false
}
func myFunction(){
//do whatever i am supposed to do when the timer reaches my specified interval
}
Now this may not be solution you are looking for. Another way is to use GCD's (grand Central Dispatch) dispatch_after . A very nice and extremely easy to use function can be found here, dispatch_after - GCD in swift? , in the second answer down, compliments of stackoverflow's #matt
When your view is instantiated, you want to begin an NSTimer. See this page for help creating this: How can I use NSTimer in Swift?. This page may help as well: Using an NSTimer in Swift.
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(30.0, target: self, selector: "update", userInfo: nil, repeats: true)
Then, you can just put the
self.view?.presentScene(GameScene2())
in a func update like so:
func update() {
self.view?.presentScene(GameScene2())
Hope this helps. If so, please check off my answer. If not, comment and I will try and help again.
I have multiple views each associated with its own timer. However, only one view is running a time each time. I am accessing each view in a for ... in loop and if the previous timer in the previous view has stopped, which I invalidate in the selector method, I want to fire the time for the next view.
the code looks like this:
var timer = NSTimer()
let timeInterval:NSTimeInterval = 1
var views = [TimerView]()
var timeCountDown: NSTimeInterval = 0
override func viewDidLoad() {
//viewDidLoad create the necessary UIView and fill the views array then pass them to a call to startTimer:
startTimer(views)
}
//startTimer function create the countdown timer
func startTimer(timerViews: [TimerView]){
for timerView in timerViews {
if !timer.valid { // a view with its associated timer start only when the previous timer has run its course and is invalidated
//creating a timer to associate with the current view
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "timerDidEnd:",
userInfo: timerView,
repeats: true)
}
}
}
//Callback function
func timerDidEnd(timer: NSTimer){
let timerView = timer.userInfo as! TimerView
timeCountDown = timeCountDown - timeInterval
timerView.timeLabel.text = timeToString(timeCountDown)
timerView.curValue = CGFloat(timeCountDown)
if timeCount <= 0.0 {
resetTimeCount()
}
}
func timeToString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
func resetTimeCount(){
timer.invalidate()
}
With some print debugging for three views I am getting this output: "running the for loop", "calling view #1 by for loop", "calling the timer", "calling view #2 by for loop", "calling view #3 by for loop","countdown starting" ... That is the countdown only start after the for loop has terminated.
the problem I have is that the first timer run while the for loop calls all the views in my view array. How do I get the for loop to wait for the timer to be invalidated before it iterates to the next view?
What I am trying to achieve is this: the first view starts a countdown; when that countdown finished a second appears and run a countdown as well. Then the third view with a new countdown and so on until the last view. But it looks like the for loop in my code finish looping before the first timer associated with the first view in the collection actually starts
thanks
edit: I am wondering if the timer is not running on a different thread than the for loop?
Your code has a logic problem. It looks like you have a single instance variable, timer. Your for loop starts, and on the first pass, presumably timer is not valid. So you overwrite it with a new timer that IS valid. Now on the second pass through the array of timerViews, you check the same shared timer instance variable, but this time (And all subsequent times) timer is valid, so the body of the if statement doesn't fire.
If you really want a separate timer for each view then you will need to store all those timers. One way would be to have an array of timers that goes along with your array timerViews. Or you could create an array of tuples where each element contains a view and it's associated timer.
Stepping back from your problem, though, why do you want a separate timer for each field? Why not have a single timer, and each time it fires, loop through the array of fields and decide what you need to do with each one? You could have an array of structs that contains references to your views plus status information that lets your loop decide what to do with each one.