Having UILabel update based on Audio Player current time - swift

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
}

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!"
}
}
}

background run timer swift

I want the timer to run even when I close the application. I want it to work in the background counter. the timer goes back one second when I run it.(counter) How can I do that?
class TimerViewController: UIViewController {
var selectedDay: String?
var seconds =
var timer = Timer()
#IBAction func start(_ sender: AnyObject) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
sliderOutlet.isHidden = true
startOutlet.isHidden = true
}
#objc func counter() {
seconds -= 1
favoriteDayTextField.text = String(seconds) + " Seconds"
var bgTask = UIBackgroundTaskIdentifier(rawValue: seconds)
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
if (seconds == 0) {
timer.invalidate()
if self.button.isOn {
updateState()
} else {
updateState1()
}
}
}
}
I am not clear what you want to achieve. Suppose you want to update the label after the timer has started each 1 second. Then one approach will be:-
Start the timer in view did load if the duration is remaining.
Register for applicationWillTerminate
In application will terminate save the passed duration and terminated time to calculate remaining time in next launch.
var remainingDuration: TimeInterval!
override func viewDidLoad() {
super.viewDidLoad()
let remainingDurationFromLastLaunch = UserDefaults.standard.object(forKey: "duration") as? TimeInterval ?? 0
let lastTerminatedTime = UserDefaults.standard.object(forKey: "lastTerminatedDate") as? Date ?? Date()
if Date().timeInterval(since: lastTerminatedTime) > remainingDurationFromLastLaunch {
remainingDuration = remainingDurationFromLastLaunch - Date().timeInterval(since: lastTerminatedTime)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
NotificationCenter.default.addObserver(self, selector: #selector(TimerViewController.applicationWillTerminate), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
} else { //Duration is passed....Do whatever you want
}
}
#objc func counter() {
remainingDuration -= 1
if remainingDuration == 0 { //Duration is passed....Do whatever you want
timer.invalidate()
timer = nil
} else {
favoriteDayTextField.text = String(remainingDuration) + " Seconds"
}
}
#objc func applicationWillTerminate() {
if timer != nil {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
UserDefaults.standard.set(remainingDuration, forKey: "duration")
UserDefaults.standard.set(Date(), forKey: "lastTerminatedDate")
}
self?.endBackgroundTask()
}
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
The only way for your iOS application to perform some action even while it is in the background is to use Background Modes .
However you cannot perform anything and everything while your
application is in background
There are certain limitations to the type of tasks that you can perform . I have attached a really good article for your reference
Background Modes Tutorial
However, I am not sure if you can initiate and continue a timer sort of functionality while your application is in background
Though, keep in mind , once your application is closed (i.e. by double tapping the home button and swiping the application window up to close it completely) , not even Background modes work at that point because the user does not want to run your app anymore, even in the background

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)")

Zero amplitude and frequency from audiokit tracker on device but works on simulator

AudioKit
I have two view controllers that are doing different audio related function. The first view viewController, needs to record audio and also do real-time processing of the amplitude and frequency of the sound being recorded. The second view controller needs to play the recorded audio, and do some stuff with the playhead.
My problem is that when I go back and forth a few times between the two screens on my iphone 6 device, the amplitude and frequency of the tracker only equal 0. This behavior does not happen on my simulator. Similarly, I've tried different combinations of starting + stopping the tracker and/or the audio engine at different points in the workflow but then I just get crashes along the lines of https://github.com/audiokit/AudioKit/issues/643. One thing I have been trying to figure out is how to make a singleton instance of the aukiokit so I can only start the engine once, but I can't get it from examples.
Here are the key sections of code that use the audiokit, microphone and tracker:
First view controller in the workflow
class ViewController: UIViewController {
#IBAction func analyzeSpeechAction(_ sender: Any) {
//Audiokit.stop()
stopTimers()
let vc = AnalysisViewController(nibName: "AnalysisViewController", bundle: nil)
print("ANALYZING")
do {
try player.reloadFile()
} catch { print("Errored reloading.") }
let recordedDuration = player != nil ? player.audioFile.duration : 0
if recordedDuration > 0.0 {
recorder.stop()
}
tracker.stop()
navigationController?.pushViewController(vc, animated: true)
}
func stopTimers(){
if timerTest != nil {
timerTest!.invalidate()
timerTest = nil
}
if timer != nil {
timer!.invalidate()
timer = nil
}
}
var mic: AKMicrophone!
var tracker: AKFrequencyTracker!
var silence: AKBooster!
var mainMixer: AKMixer!
var timerTest : Timer?
var micMixer: AKMixer!
var recorder: AKNodeRecorder!
var player: AKAudioPlayer!
var tape: AKAudioFile!
var micBooster: AKBooster!
override func viewDidLoad() {
super.viewDidLoad()
AKAudioFile.cleanTempDirectory()
AKSettings.bufferLength = .medium
do {
try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP)
} catch {
AKLog("Could not set session category.")
}
AKSettings.defaultToSpeaker = true
AKSettings.audioInputEnabled = true
mic = AKMicrophone()
tracker = AKFrequencyTracker(mic)
tracker.start()
silence = AKBooster(tracker, gain: 0)
micMixer = AKMixer(mic)
micBooster = AKBooster(micMixer)
// Will set the level of microphone monitoring
micBooster.gain = 0
recorder = try? AKNodeRecorder(node: micMixer)
if let file = recorder.audioFile {
player = try? AKAudioPlayer(file: file)
}
mainMixer = AKMixer(micBooster)
do {
try recorder.record()
} catch { print("Errored recording.") }
AudioKit.output = mainMixer
}
viewDidAppear(){
AudioKit.stop()
AudioKit.output = silence
AudioKit.start()
if timer == nil{
timer = Timer.scheduledTimer(timeInterval: 1, target:self,
selector: #selector(ViewController.updateCounter), userInfo: nil, repeats: true)
}
}
#objc func updateUI() {
frame = frame + 1
print("AMP")
print(tracker.amplitude)
print(tracker.frequency)
}
}
Second View Controller
class AnalysisViewController: UIViewController {
#IBOutlet weak var barChartView: LineChartView!
#IBAction func recordSpeechNavigation(_ sender: Any) {
let vc = ViewController(nibName: "ViewController", bundle: nil)
//AudioKit.stop()
player.stop()
navigationController?.pushViewController(vc, animated: true)
}
#IBAction func playButtonAction(_ sender: UIButton) {
playAudio()
}
override func viewDidLoad() {
super.viewDidLoad()
//move to completion handler
player.completionHandler = {() -> Void in
self.playerStatusButton.setTitle("Play", for: .normal)
self.timerTest!.invalidate()
self.relativePlayheadPosition = 0.0
self.movePlayhead()
print("complete")
}
}
}
I kind of mashed up a bunch of examples to get this far so I apologize if I am improperly using some of the AudioKit methods. The key behavior occurs between viewDidLoad(), viewDidAppear() and updateUI() of the first view and recordSpeechNavigation() of the second view.
Does anyone have any insight into what might be happening or how I can properly implement this stuff?

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.