Triggering event only on a change of variable output - swift

I have created the following coin toss program which also triggers an audio file with each coin toss every second. I'd like to know how to change this so that the audio only triggers when the result changes from a heads to a tails or vice versa. Not every second as it is now. Any help is greatly appreciated.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
var coinFlip : Int = 0
var coinResult : String = ""
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() -> String {
if coinFlip == 1 {
coinResult = "Heads"
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
coinResult = "Tails"
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}
I've now changed it to add a new variable output from result(), coinResult. Not sure if this helps but hopefully might.

If I understood well, when the coin flips, you want to play a song, in this case you can simply change your coinFlip declaration to that:
var coinFlip : Int = 0 {
didSet {
result()
}
}
didSet is a property observer that observes and respond to changes in property's value.
didSet is called immediately after the new value is stored.
You can read more about property observers in Apple's documentation.
EDIT: Your code will be like this:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
// Change this
var coinFlip : Int = 0 {
didSet {
result()
}
}
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() {
if coinFlip == 1 {
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}

Related

AudioKit 4.9.5 Microphone doesn't work, all 0 readings

I've been trying to use the microhpone with AudioKit.
The code compiles and runs it also request permission to access to microphone but all the frequency and amplitude readings are all 0.
Here is the class I wrote to dispatch Microphone readings as events.
I've seen other questions which could have been relavant and tried them all like I've tried to change input but no luck.
My guess is either i'm not understanding the AudioKit lifecycle correctly or #objc tags changes the behaviour.
This code used to work on older ios versions but on the 13.3 seems like there are changes. (reason for #objc is that I need to use this with react native bridge)
I thought this could be related to info.plist but I have configured the info.plist for microphone privacy with a string but still no luck.
Am I missing something?
Thanks
please check the code here:
import Foundation
import AudioKit
#objc(AudioKitWrapper)
class AudioKitWrapper: NSObject {
#objc var mic: AKMicrophone!
#objc var timer: Timer!
#objc var tracker: AKFrequencyTracker!
override init(){
super.init()
do {
try AKSettings.setSession(category: .playAndRecord)
} catch {
AKLog("Could not set session category.")
}
AKSettings.defaultToSpeaker = true
if let inputs = AudioKit.inputDevices {
do {
print(inputs)
try AudioKit.setInputDevice(inputs[0])
AKSettings.audioInputEnabled = true
mic = AKMicrophone()
try mic?.setDevice(inputs[0])
}
catch {
print("microphone not supported")
}
}
try? AudioKit.start()
mic?.stop()
self.tracker = AKFrequencyTracker.init(mic, hopSize: 4_096, peakCount: 20)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.loop), userInfo: nil, repeats: true)
}
#objc func start() {
if (mic?.isStarted == true) {
print("stop")
mic?.stop()
try! AudioKit.stop()
}
else {
print("start")
mic?.start()
// try! AudioKit.start()
var silence = AKBooster(tracker, gain: 0)
AudioKit.output = silence
}
}
#objc static func requiresMainQueueSetup() -> Bool {
return false
}
#objc func loop() {
if (mic?.isStarted == true){
print(self.tracker.amplitude, self.tracker.frequency)
EventEmitter.sharedInstance.dispatch(name: "onChange",
body: ["amplitude": tracker.amplitude, "frequency":tracker.frequency])
}
}
}
I watched the videos to go from sandbox to production and changed few things.
I realised the main issue with my code was not using AKBooster. change the variable declarations to what the videos mentioned and it started to work.
Here is swift code that works:
import Foundation
import AudioKit
#objc(AudioKitWrapper)
class AudioKitWrapper: NSObject {
#objc var mic: AKMicrophone!
#objc var timer: Timer!
#objc var tracker: AKFrequencyTracker!
#objc var silence: AKBooster!
#objc var micCopy1: AKBooster!
#objc override init(){
super.init()
mic = AKMicrophone()
micCopy1 = AKBooster(mic)
do {
try AKSettings.setSession(category: .playAndRecord)
AKSettings.defaultToSpeaker = true
} catch {
print("Could not set session category.")
return
}
if let inputs = AudioKit.inputDevices {
do {
try AudioKit.setInputDevice(inputs[0])
try mic.setDevice(inputs[0])
}
catch {
print("microphone not supported")
return
}
}
do {
tracker = AKFrequencyTracker.init(micCopy1, hopSize: 4_096, peakCount: 20)
silence = AKBooster(tracker, gain: 0)
AudioKit.output = silence
try AudioKit.start()
mic.stop()
}
catch {
print("AudioKit did not start")
}
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(loop), userInfo: nil, repeats: true)
}
#objc func start() {
print("started?", mic?.isStarted == true)
if (mic?.isStarted == true) {
print("stop")
mic?.stop()
}
else {
print("start")
mic.start()
}
}
#objc static func requiresMainQueueSetup() -> Bool {
return true
}
#objc func loop() {
if (mic.isStarted == true){
print("dispatch", tracker.frequency, tracker.amplitude)
EventEmitter.sharedInstance.dispatch(name: "onChange",
body: ["amplitude": self.tracker.amplitude, "frequency": self.tracker.frequency])
}
}
}
Also for those interested to make AudioKit work on React Native, here are the objectiveC code:
AudioKitBridge.m
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#interface RCT_EXTERN_MODULE(AudioKitWrapper, NSObject)
RCT_EXTERN_METHOD(start)
#end
AudioKitBridge.h
#ifndef AudioKitBridge_h
#define AudioKitBridge_h
#import <React/RCTBridgeModule.h>
#interface AudioKitBridge: NSObject
#end

Reporting changes from child ViewController

If one ViewController inherits from another, how do I update stuff in the child ViewController as the variable changes in the parent ViewController?
class ViewControllerOne: UIViewController {
var timer = Timer()
var number: Int = 0
func updateNumber() {
number += 1
}
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateNumber), userInfo: nil, repeats: true)
}
}
class ViewControllerTwo: ViewControllerOne {
// So in this class I want to print to the console when number is 3.
// How do I check for that from this class?
}
Override updateNumber in ViewControllerTwo.
override func updateNumber() {
super.updateNumber()
if number == 3 {
// do something
}
}

Running a Timer that counts down in background when app isnt the focus? Swift

I want my countdown timer to suspend and then resume when the app leaves / returns to focus, using the time away to calculate how much time should be deducted.
I am using the app delegate file (not sure thats the right location? or if they are meant to be in the view controllers file as functions of their own?)
Issue is im getting a lot of errors such as:
Value of type 'AppDelegate' has no member 'restTimer'
Use of unresolved identifier 'nextFireDate'
Use of unresolved identifier 'selector'
restTimer was declared as a timer in my view controllers file but when i tried these blocks in that file i got an equal number of errors for unresolved identifiers
and using the following 2 code blocks
func applicationWillResignActive(_ application: UIApplication) {
guard let t = self.restTimer else { return }
nextFireDate = t.fireDate
t.invalidate()
and
func applicationDidBecomeActive(_ application: UIApplication) {
guard let n = nextFireDate else { return }
let howMuchLonger = n.timeIntervalSinceDate(NSDate())
if howMuchLonger < 0 {
print("Should have already fired \(howMuchLonger) seconds ago")
target!.performSelector(selector!)
} else {
print("should fire in \(howMuchLonger) seconds")
Timer.scheduledTimerWithTimeInterval(howMuchLonger, target: target!, selector: selector!, userInfo: nil, repeats: false)
}
}
UPDATE: Added full views code due to issue incorporating the answer
import Foundation
import UIKit
class RestController: UIViewController {
#IBOutlet weak var restRemainingCountdownLabel: UILabel!
#IBOutlet weak var setsRemainingCountdownLabel: UILabel!
#IBOutlet weak var numberOfSetsLabel: UILabel!
#IBOutlet weak var numberOfRestLabel: UILabel!
#IBOutlet weak var adjustSetsStepper: UIStepper!
#IBOutlet weak var adjustRestStepper: UIStepper!
var startDate: Date!
let startDateKey = "start.date"
let interval = TimeInterval(20)
var restTimer: Timer!
var restCount = 0
var setCount = 0
var selectedTime = 1
var selectedSets = 1
private let resignDateKey = "resign.date"
#IBAction func endSetPressed(_ sender: Any) {
if (setCount > 0){
setCount -= 1
setsRemainingCountdownLabel.text = String(setCount)
}
handleTimer()
}
#IBAction func setStepperValueChanged(_ sender: UIStepper) {
numberOfSetsLabel.text = Int(sender.value).description
self.setCount = Int(sender.value)
self.selectedSets = setCount
setsRemainingCountdownLabel.text = String(setCount)
}
#IBAction func restStepperValueChanged(_ sender: UIStepper) {
numberOfRestLabel.text = Int(sender.value).description
let timeMinSec = timeFormatted(totalSeconds: Int(sender.value)*60)
restRemainingCountdownLabel.text = timeMinSec
self.selectedTime = Int(sender.value)
restCount = self.selectedTime * 60
}
#IBAction func resetSetsButton(_ sender: Any) {
setCount = Int(adjustSetsStepper.value)
setsRemainingCountdownLabel.text = String(setCount)
}
override func viewDidLoad() {
super.viewDidLoad()
numberOfSetsLabel.text = String(selectedSets)
numberOfRestLabel.text = String(selectedTime)
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func willResignActive(notification: Notification) {
print("resigning")
guard restTimer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
restTimer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
#objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
restTimer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
func handleSets() {
if (setCount > 0) {
self.restCount = self.selectedTime * 60
}
handleTimer()
}
func handleTimer() {
if (restTimer?.isValid ?? false) {
restTimer?.invalidate()
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
} else {
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
}
}
func updateTimer() {
if (restCount > 0){
restCount -= 1
} else if (restCount == 0){
restTimer?.invalidate()
}
restRemainingCountdownLabel.text = timeFormatted(totalSeconds: restCount)
}
func timeFormatted(totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
return String(format: "%02d:%02d", minutes, seconds)
}
I think you cannot rely on the fact that the app will remain in memory while it is in the background. Thus, you should archive all the data you need to recreate the timer at the exact point.
For example, in applicationWillResignActive
UserDefaults.standard.set(value: t.nextFireDate forKey:"NextFireDate")
and in applicationWillEnterForeground
if let fireDate = UserDefaults.standard.object(forKey: "NextFireDate") {
// setup a timer with the correct fire date
}
You don't have to use the AppDelegate for this because it also posts notifications. You can use the AppDelegate if you want. Here is code using notifications:
class ViewController: UIViewController{
private let startDateKey = "start.date"
private let interval = TimeInterval(20)
private var startDate: Date!
private var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func willResignActive(notification: Notification) {
print("resigning")
guard timer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
timer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
#objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
timer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
}

Swift: AudioPlayerDidFinished will not be called

My AudioPlayerDidFinishPlaying will not be called after audio has finished. I know it has something to do with my delegate, but I can't fix it on my own.
Can somebody give me some tips? I Googled a lot and I found other questions here with the same issue but it didn't work for me.
Thanks
import UIKit
import Parse
import AVFoundation
class ViewControllerMies: UIViewController, AVAudioPlayerDelegate {
var timer = NSTimer()
var player: AVAudioPlayer = AVAudioPlayer()
var currentStateAudio = ""
var oldAudio = String()
func startTimer() {
if player.playing == false {
print("Tijd voor de Spice Girls")
}
let query = PFQuery(className:"CurrentState")
query.getObjectInBackgroundWithId("9r61TRaRqu") {
(objects: PFObject?, error: NSError?) -> Void in
if error == nil && objects != nil {
self.currentStateAudio = objects!.objectForKey("currentState") as! String
print(self.currentStateAudio)
} else {
print(error)
}
}
if (oldAudio != self.currentStateAudio)
{
let audioPath = NSBundle.mainBundle().pathForResource(self.currentStateAudio, ofType: "mp3")!
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
} catch {
// Process error here
}
player.play()
oldAudio = self.currentStateAudio
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("startTimer"), userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool)
{
print("Finished Playing")
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
you're not setting yourself as the player's delegate
before calling play() do player.delegate = self
if you are using private delegate then its will not call too use this for resolving this issue
//MARK:- This is correct delegate working fine (use this)
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
audioRecorder = nil
}
//MARK:- Insted of this (this will not call)
private func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
audioRecorder = nil
}

How using class func to call a method from another class in SWIFT

I would to call from the GameViewController my timer method (written in my GameScene) in order to pause the game with an UIButton initialized in GameViewController class.
I'm trying to use a class func like this :
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
GameScene.startTimer()
}
class func startTimer(){
timerCount = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt:NSTimer){
counter--
counterGame++
if counter<0{
timerCount.invalidate()
removeCountDownTimerView()
} else{
labelCounter.text = "\(counter)"
}
if counterGame>20{
balloon.size = CGSizeMake(50, 50)
}
if counterGame>40{
self.physicsWorld.gravity = CGVectorMake(0, -0.8)
}
if counterGame>60{
self.physicsWorld.gravity = CGVectorMake(0, -1)
}
}
func removeCountDownTimerView(){
defaults.setInteger(balloonDestroyed, forKey: "score")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let settingController: UIViewController = storyboard.instantiateViewControllerWithIdentifier("GameOverViewController") as UIViewController
let vc = self.view?.window?.rootViewController
vc?.presentViewController(settingController, animated: true, completion: nil)
}
}
But this code return an error :
[Funfair_balloon.GameScene updateTimer:]: unrecognized selector sent to class 0x10b13d940
When I don't use the class func the app works perfectly but I can't stop the timer with my UIButton.
What did I do wrong?
Please note that when using class func, you cannot use the variables that were initialized in that class.
So for example, you cannot use the variable timerCount if it was initialized in the class GameScene.
I'm not sure why you want to use class functions. The following should work using just the current instance of GameScene. Note that the var timerCount is an optional (since you can't easily override the init) until such times as you create it in startTimer(), and so you will have to unwrap it when you eventually invalidate it.
class GameScene: SKScene, SKPhysicsContactDelegate {
var timerCount: NSTimer? = nil
var counter = 100 // or whatever
override func didMoveToView(view: SKView) {
self.startTimer()
}
func startTimer() {
self.timerCount = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt: NSTimer) {
// Do stuff
counter--
if counter < 0 {
self.timerCount!.invalidate() // or dt.invalidate()
self.removeCountDownTimerView()
} else {
// update counter label
}
// Do more stuff
}
func removeCountDownTimerView() {
// Do stuff
}
}