Repeating timer with New Time Intervals - swift

Hi this is my first app and I have been trying to figure out how to do this simple task but am getting overloaded with information and I have a feeling there may be an easier way to do it.
I am trying to build a random word generator that will select from an array of words at random time intervals. The time needs to change after the timer fires. The timer can keep running the whole time the app is on.
here is my code so far:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#objc weak var paraTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
let randomNum = Int.random(in: 1..<5)
paraTimer = Timer.scheduledTimer(timeInterval: TimeInterval(randomNum), target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true)
}
/*
This invalidates my timer, not sure if I need to start a new timer now or if I can reset this?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
paraTimer?.invalidate()
*/
#objc func runTimedCode() {
let array = ["Apple", "Banana", "Pear" ]
label.text = array.randomElement()
}
}
So the issue that I need to fix is it will select a random number from 1-5 and then not select a new number and just keep running every x seconds. Also should I invalidate the timer or leave that off?
Thank you. Like I said, this is my first time working on an app that doesn't just follow a straight tutorial.

To restart a timer with different intervals you have to recreate the timer.
First of all declare the timer strong and #objc is not needed
var paraTimer: Timer?
I recommended to create an extra method. Declare the timer non-repeating and use the block based API of Timer. The timer restarts itself.
func restartTimer() {
let randomNum = Int.random(in: 1..<5)
paraTimer = Timer.scheduledTimer(timeInterval: TimeInterval(randomNum), repeats: false) { [weak self] _ in
let array = ["Apple", "Banana", "Pear" ]
self?.label.text = array.randomElement()
self?.restartTimer()
}
}
Start the timer in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
restartTimer()
}
and stop it in viewDidDisappear
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
paraTimer?.invalidate()
paraTimer = nil
}

Related

UITextField + Timer Issue - Checking for "Editing Changed" but Only Once to Initiate Timer?

Full Disclosure: I am extremely new to everything swift/UIKit-related so I hope you can excuse my assured ignorance.
So the basic action I am trying to preform is having a Timer immediately begin a countdown the moment any character is entered into a UITextField (NOT triggered just by being focused).
Everything looks and functions 90% as I am intending except for an interaction where every time a character is entered into text field, the timer recursively initiates it's timeInterval.
After 7+ characters are typed, my 60-Second timer is far past 0 in less than a few seconds.
Using editingDidBegin "fixes" the issue but, in this parameter, the timer starts when the text field is tapped and focused and that isn't ideal for what I am trying to do.
My ViewController Code:
import UIKit
class SecondViewController: UIViewController, UITextFieldDelegate {
var textField: UITextField?
#IBOutlet weak var countDownLabel: UILabel!
var seconds = 60
var timer = Timer()
#IBAction func inputTextBar(_ sender: Any) {
func inputTextBar() {
self.becomeFirstResponder()
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(SecondViewController.counterb), userInfo: nil, repeats: true)
}
#objc func counterb() {
seconds -= 1
countDownLabel.text = String(seconds)
if seconds == 0 {
timer.invalidate()
countDownLabel.text = "Time's Up!"
}
}
var count = 10
override func viewDidLoad() {
super.viewDidLoad()
textField?.delegate = self
textField?.becomeFirstResponder()
}
}
Other Noted Confusion: If only one character is typed, the timer proceeds as normal and prints "Times Up!" at 0sec. and stops.
But if it's on the speed-pileup I mentioned, after a few characters are typed, the timer flies past 0 and never invalidates.
I assume that's a refresh issue.
Edit:
Okay, I figured out that making the timer variable weak I was able to get the timer to not keep stacking on itself. But, with the weak variable, the timer doesn't run at all while the UITextField is being typed in!
Semi-Fixed Code:
weak var timer: Timer?
#IBAction func inputTextBar(_ sender: AnyObject) {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(SecondViewController.loop), userInfo: nil, repeats: true)
}
#objc func loop() {
seconds -= 1
countDownLabel.text = String(seconds)
if self.seconds == 0 {
timer?.invalidate()
countDownLabel.text = "Time's Up!"
}
}
First change your timer declaration to optional. Then you can simply check if it is nil, if true schedule your timer. Second you should never rely on a timer to measure elapsed time. Just create an end date adding the desired time interval from now. This way you can simply check if now is greater or equal than the endDate when invalidating your timer. Use the timer only to update the UI and you can simply check the time interval since now when updating your label. The code below was written in the browser so it might have some mistakes but you can have an idea what to do to accomplish your goal.
class SecondViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var countDownLabel: UILabel!
var endDate: Date? = nil
var timer: Timer? = nil
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
textField.becomeFirstResponder()
}
#objc func editingChanged(_ textField: UITextField) {
if timer == nil {
timer = .scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(counterb),
userInfo: nil,
repeats: true)
endDate = Date().addingTimeInterval(60)
}
}
#objc func counterb() {
countDownLabel.text = String(format: "%.0f", endDate!.timeIntervalSinceNow.rounded(.up))
if Date() >= endDate! {
timer?.invalidate()
timer = nil
endDate = nil
countDownLabel.text = "Time's Up!"
}
}
}

How to count Timer on Swift

I'm a newbie in swift and I tried to make a Timer. It should normally count the seconds and print them in the debugger.
I tried this:
var timer = Timer()
#IBAction func killTimer(_ sender: AnyObject) {
timer.invalidate()
}
#objc func processTimer() {
print("This is a second")
}
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.processTimer), userInfo: nil, repeats: true)
}
I don't know how the timer count seconds.. With this code i get an fail message:
#objc func processTimer() {
print("This is second \(Timer + 1)")
}
Thanks for your help.
A
You need a counter variable which is incremented every time the timer fires.
Declare the variable Timer as optional to invalidate the timer reliably (only once).
var timer : Timer?
var counter = 0
#IBAction func killTimer(_ sender: AnyObject) {
timer?.invalidate()
timer = nil
}
#objc func prozessTimer() {
counter += 1
print("This is a second ", counter)
}
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval:1, target:self, selector:#selector(prozessTimer), userInfo: nil, repeats: true)
}
You need to start your timer, you have only initialised it in your code so in viewDidLoad add
timer.fire()
I am not sure what you want to print but if it is a timestamp then you could do add a property to your class
let formatter = DateFormatter()
and configure it to show time in seconds and milliseconds
formatter.dateFormat = "ss.SSS" //in viewDidLoad
and use it in your print statement
print("This is a second \(formatter.string(from(Date())")
If you want to print the time that passed use this (If that's what you want):
print("time passed: \(Date().timeIntervalSince1970 - timer.fireDate.timeIntervalSince1970)")

Having UILabel update based on Audio Player current time

I am attempting to have my UILabel update based on the duration of what the AudioPlayer current time is. I don't want to use a timer, because within my app people will be recording themselves with small durations & the half seconds are important, so I wouldn't really want to use timer(). Instead what I am attempting to do is just have the label update based on what the current time is automatically. I can't however seem to figure it out...
Here is how I am going about it, but clearly I am not doing it correctly
var audioPlayer : AVAudioPlayer!
var audioTime = UILabel() {
didSet {
do {
try audioPlayer = AVAudioPlayer(contentsOf: audioURL!)
let currentTime = Int(audioPlayer.currentTime)
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
audioTime.text = (NSString(format: "%02d:%02d", minutes,seconds) as String)
} catch {
}
}
}
This might help you! Connect Outlet to showTimeLbl and try.
class ViewController: UIViewController {
#IBOutlet weak var showTimeLbl: UILabel!
var timeCount:Int = 0
var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if(timer != nil)
{
timer.invalidate()
}
timer = Timer(timeInterval: 1.0, target: self, selector: #selector(ViewController.timerDidFire), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
}
func timerDidFire()
{
timeCount += 1
showTimeLbl.text = NSString(format: "%02d:%02d:%02d", timeCount/3600,(timeCount/60)%60,timeCount%60) as String
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
timer?.invalidate()
timer = nil
}

Swift 3 UIButton multiple events not detected within a less than 16 millisecond window

This is my first question on StackOverflow. I am trying to measure reaction time to stimuli and need multiple players and UIButtons for the players. The problem is if the two players in the code example are within 16 millisecond or less of each other both UIButton events come together and therefore the same time. I just did this in Android and had no problem.
I did try override of the touches_began() and the same problem. It seems as if the two UIButtons are being handled in the same interrupt as if they were a multi-touch which I have turned off for the two buttons.
I know the example is dumb but it does illustrate the 16 millisecond
issue. Just tap a bunch of times and see how many ties occur. I looked at the results of testing and I got an approximate 16 millisecond issue.
import UIKit
class ViewController: UIViewController {
weak var timer: Timer?
var startTime: Double = 0
var time: Double = 0
#IBOutlet weak var timeValueLabel1: UILabel!
#IBOutlet weak var timeValueLabel: UILabel!
#IBAction func stop1Button() {
let timeString = String(format: "%.3f", time)
timeValueLabel1.text = timeString
}
#IBAction func stopButton() {
let timeString = String(format: "%.3f", time)
timeValueLabel.text = timeString
}
override func viewDidAppear(_ animated: Bool) {
startTime = Date().timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(timeInterval: 0.001,
target: self,
selector: #selector(advanceTimer(timer:)),
userInfo: nil,
repeats: true)
}
override func viewWillDisappear(_ animated: Bool) {
timer?.invalidate()
}
func advanceTimer(timer: Timer) {
time = Date().timeIntervalSinceReferenceDate - startTime
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

The timer is terminated when the view changes

I am not a native English speaker and I am writing with a translator.
I want you to understand if the context is strange.
My Question is,
When the button is pressed, the timer operates, but when the view is switched, the timer is not operating.
I wonder how the timer can be maintained even if the view changes.
var time : Int = 0
var onehour : Int = 60
var timer = Timer()
#IBAction func pressSuncream(_ sender: Any) { // Suncream 버튼
useSuncream.setImage(UIImage(named: "test"), for: UIControlState.normal)
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.title = "hello"
content.body = "timer"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false)
let request = UNNotificationRequest(identifier: "timerdone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(QuestView.updatetime), userInfo: nil, repeats: true)
} else {
// Fallback on earlier versions
}
}
func updatetime() {
time += 1
if time == 60 {
time = 0
onehour -= 1
DispatchQueue.main.async {
self.suncreamTime.text = String(self.onehour)
}
}
}
when you switch to the mainView,you can store the value of onehour in the UserDefaults at the override func viewWillDisappear(_ animated: Bool) { }.
when you into the timerView,you can get the value at the override func viewWillAppear(_ animated: Bool) {}
eg:
override func viewWillDisappear(_ animated: Bool) {
let userDefault = UserDefaults.standard
userDefault.set(onehour, forKey: "onehour")
}
override func viewWillAppear(_ animated: Bool) {
if let onehour = UserDefaults.standard.value(forKey: "onehour") {
self.onehour = onehour as! String
}
}
One way that you could do this, is to make the timer variable global. This is probably a "dirty" way to do it, and if there really are terrible ramifications of doing this, someone let me know and I'll take this down.
Essentially all you would need to do is define it outside of any class like so:
var timer: NSTimer?
class MyClass: UIViewController {
...
}
This way you can access it from any file. I would say that you are generally discouraged from doing this unless you have to, so it's more of a last-resort option.