While Loop and Touch Events Swift iOS - ios

I'm trying to create an iOS triggering app that sends a signal if triggered when the app is armed. I've generated the signal to be sent when triggered and a button that changes the app from armed to unarmed.
When I touch the button making the app armed, it then goes into a while loop that runs listening to see if the app has been triggered and sends the signal if triggered. However by doing this I am unable to touch the button that now says to unarm the app. This is what I have so far:
var trigger = AVAudioPlayer()
func playMySound(){
let triggerSoundURL = NSBundle.mainBundle().URLForResource("trigger", withExtension: "wav")!
trigger = try! AVAudioPlayer(contentsOfURL: triggerSoundURL)
trigger.prepareToPlay()
trigger.play()
}
var armed = 0
#IBAction func arm() {
if(armed==0){
armed=1
armButton.setTitle("Armed", forState: .Normal)
triggering();
}
else{
armed=0
armButton.setTitle("Arm", forState: .Normal)
}
}
func triggering() -> Void {
while(armed==1){
playMySound()
arm()
}
}

You are blocking main thread, thats why you can`t touch the button.
Try to change your "triggering" function to something like this:
func triggering() -> Void {
dipsatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)){
while(armed==1){
playMySound()
arm()
}
}
}

Related

DispatchGroup notify() doesn't get called

I have to wait for the completion until the user touches the Try Again button to call the same function again. All works fine the first time but when I press it again, the notfy() method doesn't get called. Here is the playground class to simulate that scenario:
import Foundation
class TokenManager{
private var alertAlreadyShown = false
static let shared = TokenManager()
var alreadyEnterGroup = false
let handlerTryAgainGroup = DispatchGroup()
func showRefreshAlert(onTryAgainPressed : #escaping()->Void){
//PREVENTS TO ENTER ON GROUP MUTIPLE TIMES (ASYNC CALLS)
if(!alreadyEnterGroup){
alreadyEnterGroup = true
self.handlerTryAgainGroup.enter()
}
//PREVENTS TO SHOW ALERT MUTIPLE TIMES (ASYNC CALLS)
if(!alertAlreadyShown){
alertAlreadyShown = true
//NOTIFY THE COMPLETION THAT USER TOUCH TRY AGAIN BUTTON
handlerTryAgainGroup.notify(queue: DispatchQueue.main) {
onTryAgainPressed()
}
}else{
//THIS IS JUST A TEST TO SIMULATE THE USER TAPS MUTIPLE TIMES ON BUTTON
TokenManager.shared.tryAgainPressed()
}
}
func tryAgainPressed() {
alreadyEnterGroup = false
handlerTryAgainGroup.leave()
}
}
func showAlert(){
TokenManager.shared.showRefreshAlert {
//CALLS THE SAME FUNCTION IF THE USER TAPS TRY AGAIN
showAlert()
}
}
//SHOW THE ALERT AND PRESS TRY AGAIN FOR THE FIRST TIME
showAlert()
TokenManager.shared.tryAgainPressed()
What is wrong at this case?

AVAudioPlayer doesn't stop while playing

I'm working on developing an app that plays narration by playing sentence-by-sentence sound file one after another.
With below code, it played as expected. However, after adding "Stop" button to stop what's playing, I found that "Stop" button didn't stop the sound.
I tested the "Stop" button before pressing "Play" button, which worked no problem (message was printed). However, after pressing "Play" and while NarrationPlayer is playing, "Stop" button didn't work (no message was printed).
Any idea what's wrong?
import UIKit
import AVFoundation
class ViewController: UIViewController,AVAudioPlayerDelegate {
var NarrationPlayer:AVAudioPlayer = AVAudioPlayer()
var soundlist: [String] = []
var counter = 0
}
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
#IBAction func play(_ sender: Any) {
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
playSound("first")
while counter < soundlist.count{
if NarrationPlayer.isPlaying == true{
}
else{
playSound(soundlist[counter])
counter += 1
}
}
}
#IBAction func StopPlay(_ sender: Any) {
print("stop button worked")
}
The problem you're running into is that this line here:
while counter < soundlist.count{
is hogging the main thread, keeping any click on your "Stop Playing" button from actually firing.
You've set a delegate method though, and one of the very handy things you can do here is increment your counter and play your next sound file each time a sound file finishes up.
Something like this:
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
playSound(self.soundlist[counter])
counter += 1
}
#IBAction func play(_ sender: Any) {
playSound("first")
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
}
One last piece of advice:
Change the name NarrationPlayer to narrationPlayer. Variables in Swift, like in Objective-C, should start with lowercase (also known as lowerCamelCase).

Getting MPRemoteCommandCenter.shared() to work in tvOS

Ok, maybe I missed something here. I want to use the black remote with my app and got this code essentially from the WWDC 2017 talk on the issue. It says ...
Consistent and intuitive control of media playback is key to many apps on tvOS, and proper use and configuration of MPNowPlayingInfoCenter and MPRemoteCommandCenter are critical to delivering a great user experience. Dive deeper into these frameworks and learn how to ensure a seamless experience whether your app is being controlled using Siri, the Siri Remote, or the iOS Remote app.
So I added these lines to viewDidLoad of my tvOS app and well they do nothing basically?
var commandCenter = MPRemoteCommandCenter.shared()
override func viewDidLoad() {
super.viewDidLoad()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
print("You Pressed play")
return .success
}
commandCenter.pauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
print("You Pressed pause")
return .success
}
}
I run the app, and try the play/pause button on the black remote and nothing is printed to the debugging console? Also added some code the plist related to background mode...Should this work or did I miss the point here somewhere?
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>external-accessory</string>
</array>
The commands in MPRemoteCommandCenter aren't triggered by the Siri Remote when your app is in the foreground. To get events from the remote when you're in the foreground, use UIGestureRecognizer like you're probably already used to.
These commands in MPRemoteCommandCenter are for other ways the system may want to interact with your playback, such as:
Your app is playing audio in the background, and the user presses the pause button on the remote: your app I'll be asked to pause playback.
The user is using the TV Remote app for iOS and is using that app's playback control screen.
Posted the question to Apple support; who pointed me in the right direction, need to use the GCMicroGamepad controller or its related GameKit frameworks. Than found a 2015 example posted by blauzahn who most certainly deserves the credit really for this post. Here is his code slightly modified for Swift 3.0, ios 10.x
import GameController
..
var gamePad: GCMicroGamepad? = nil
NotificationCenter.default.addObserver(self,
selector: #selector(gameControllerDidConnect),
name: NSNotification.Name.GCControllerDidConnect,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(gameControllerDidDisconnect),
name: NSNotification.Name.GCControllerDidDisconnect,
object: nil)
func gameControllerDidConnect(notification : NSNotification) {
if let controller = notification.object as? GCController {
if let mGPad = controller.microGamepad {
// Some setup
gamePad = mGPad
gamePad!.allowsRotation = true
gamePad!.reportsAbsoluteDpadValues = true
print("MicroGamePad connected...")
// Add valueChangedHandler for each control element
if gamePad?.buttonA.isPressed == true {
print("button A pressed")
}
if gamePad?.buttonX.isPressed == true {
print("button X pressed")
}
gamePad!.dpad.valueChangedHandler = { (dpad: GCControllerDirectionPad, xValue: Float, yValue: Float) -> Void in
print("dpad xValue = \(xValue), yValue = \(yValue)")
}
gamePad!.buttonA.valueChangedHandler = { (buttonA: GCControllerButtonInput, value:Float, pressed:Bool) -> Void in
print("\(buttonA)")
}
gamePad!.buttonX.valueChangedHandler = { (buttonX: GCControllerButtonInput, value:Float, pressed:Bool) -> Void in
print("\(buttonX)")
}
}
}
}
// Game controller disconnected
func gameControllerDidDisconnect(notification : NSNotification) {
if let controller = notification.object as? GCController {
if controller.microGamepad != nil {
self.gamePad = nil
print("MicroGamePad disconnected...")
}
}
}

How to use Stop button in iOS control center instead of pause button with swift

I want to build a radio app and so I would like to use the stop button instead of the pause button in the control center like Apple Radio does in the native music app :
Here is what I did in my RadioPlayer class :
private var shoutcastStream = NSURL(string: "http://shoutcast.com:PORT/;stream.mp3")
var playerItem:AVPlayerItem?
var player:AVPlayer?
let commandCenter = MPRemoteCommandCenter.sharedCommandCenter()
override init() {
super.init()
do {
// Allow background audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ as NSError {
}
// Disable Next, Prev and Pause
commandCenter.pauseCommand.enabled = false
commandCenter.nextTrackCommand.enabled = false
commandCenter.previousTrackCommand.enabled = false
// Enable Play
commandCenter.playCommand.enabled = true
commandCenter.playCommand.addTarget(self, action: #selector(RadioPlayer.play))
// Enable Stop
commandCenter.stopCommand.enabled = true
commandCenter.stopCommand.addTarget(self, action: #selector(RadioPlayer.stop))
} catch _ as NSError {
}
}
It's now working fine but the stop button isn't showing. Instead, I have the Pause button, which doesn't make sense for a radio player haha.
Note that in the above case, even if the control center is showing the pause button, nothing happens when pause button is pressed, because no target is attached to it (I attached it to the stopCommand).
So the question is: how to use that Stop button? Thank you.
EDIT:
I think the "stop" command is only displayed when MPNowPlayingInfoPropertyIsLiveStream = true (available only from iOS 10) /:
It does not matter if you disable the "pause" or "togglePlayPause" commands. From iOS 10, the "stop" command will be displayed if MPNowPlayingInfoPropertyIsLiveStream = true.
You may need to handle the "pause" or the "togglePlayPause" command too (for earlier versions). Good luck!
OK, I also had this doubt and did not find on the internet how to do what I wanted so I started reading more about MPRemoteCommandCenter and MPNowPlayingInfoCenter.
I tried disabling all the buttons I did not use. Also, I read about MPNowPlayingInfoPropertyIsLiveStream and I share in this post in case anyone finds it useful (look at the comments in the code):
Swift 3
MPNowPlayingInfoCenter (for metadata):
var songInfo = [:] as [String : Any]
if NSClassFromString("MPNowPlayingInfoCenter") != nil {
songInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image: UIImage(named: "your_image_name")!)
songInfo[MPMediaItemPropertyTitle] = "Title"
songInfo[MPMediaItemPropertyArtist] = "Artist name"
// If is a live broadcast, you can set a newest property (iOS 10+): MPNowPlayingInfoPropertyIsLiveStream indicating that is a live broadcast
if #available(iOS 10.0, *) {
songInfo[MPNowPlayingInfoPropertyIsLiveStream] = true
} else {
// Fallback on earlier versions
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = songInfo
} // end if MPNowPlayingInfoCenter
MPRemoteCommandCenter:
if #available(iOS 9.1, *) {
let center = MPRemoteCommandCenter.shared()
// Disable all buttons you will not use (including pause and togglePlayPause commands)
[center.pauseCommand, center.togglePlayPauseCommand, center.nextTrackCommand, center.previousTrackCommand, center.changeRepeatModeCommand, center.changeShuffleModeCommand, center.changePlaybackRateCommand, center.seekBackwardCommand, center.seekForwardCommand, center.skipBackwardCommand, center.skipForwardCommand, center.changePlaybackPositionCommand, center.ratingCommand, center.likeCommand, center.dislikeCommand, center.bookmarkCommand].forEach {
$0.isEnabled = false
}
// For "play" command
center.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
// play the song here
return MPRemoteCommandHandlerStatus.success
}
// For "stop" command
center.stopCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
// stop the song here
return MPRemoteCommandHandlerStatus.success
}
} else {
// Fallback on earlier versions
}
I have done. I hope I have helped you and others (:
according to this question-answer , Apparently ControlCenter isn't customizable(At least until now).

Problems playing multiple sounds with TheAmazingAudioEngine

I'm implementing a low-latency drum set with TheAmazingAudioEngine framework. I have a scene with a single button and a viewController with the methods below. This code works very well if I touch the button slowly. But if I touch it many times in a short period of time --- 10 times per second, for instance ---, the sound is not played in some touches, without error messages. The audio sample is short (less than 2 seconds).
Why does it happen? What is wrong in my implementation?
I choose TheAmazingAudioEngine instead of AVAudioPlayer to get low-latency between the touch and the sound.
override func viewDidLoad() {
super.viewDidLoad()
// Enable multiple touch for the button
for v in view.subviews {
if v.isKindOfClass(UIButton) {
v.multipleTouchEnabled = true
}
}
// Init audio
audioController = AEAudioController(audioDescription: AEAudioController.nonInterleavedFloatStereoAudioDescription())
audioURL = NSBundle.mainBundle().URLForResource("shortSound", withExtension: "wav")!
do {
try audioController?.start()
} catch {
print("AudioController start Error")
}
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
audioController?.stop()
}
#IBAction func playSound(sender: UIButton) {
do {
let player = try AEAudioFilePlayer(URL: audioURL)
player.removeUponFinish = true
audioController?.addChannels([player])
} catch {
print("Player start Error")
}
}

Resources