UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Stop and Start Buttons

Forums > SwiftUI

How do I get a button to be receptive to a tap while a function is running?

I am coding in Swift playgrounds on a blank page on my ipad.

To explain further: I am coding a simple drum machine. I have a stop and a start button. When I tap the start button a function called loop1 is called. A drum loop continues indefinitely in a "while" loop untill a boolean variable called "playing" is changed to false. So a tap of the stop button simply changes my "playing" variable to false and should stop the function , and hence the drum loop.

Unfortunately once I start my loop going the stop button doesn't react to a tap. I guess Swift is tied up in running the function.

Below I have extracted a simplified version of my code with just the buttons. I have ommitted Hstacks etc. I just copied the code and pasted it below. I don't know how to put the code in the nice format used on this site. Also I don't know how to include the (legal) drum file i used. This code works if it is pasted into a blank playground page.

Anyway I will paste the code below.

import SwiftUI import PlaygroundSupport import AVFoundation PlaygroundPage.current.wantsFullScreenLiveView = true // variable declarations

var playing:Bool = true var t2: Double = 1 var t1: Double = 1 var BPM:Double = 100 let screenWidth = UIScreen.main.bounds.width let screenHeight = UIScreen.main.bounds.height // end of variable declarations

struct DrumMachine: View{

@State var SnarePlayer: AVAudioPlayer?
var body: some View{
    Button(action: {// start button

        loop1()
    }) {
        Image(systemName: "play.circle")
            .foregroundColor(.green)
    }
    .frame(width: 0.2*screenHeight, height: 0.2*screenHeight)
    .scaleEffect(5)
    .background(Color.clear)
    //finish of play button
    Button(action: {// stop button
        playing = false
    }) {
        Image(systemName: "stop.circle")
            .foregroundColor(.red)
            .frame(width: 0.2*screenHeight, height: 0.2*screenHeight)
            .scaleEffect(5)
            .background(Color.clear)
            .disabled(false)
    }
    .onAppear(){Snare(vol: 0)}
}//end of some view

func Snare(vol: Float){
    if let  SnareURL = Bundle.main.url(forResource: "Snare Drum", withExtension: "mp3") {
        do {
            try self.SnarePlayer = AVAudioPlayer(contentsOf: SnareURL) /// make the audio player
            SnarePlayer?.volume = vol

            self.SnarePlayer?.play() /// start playing

        } catch {
            print("Couldn't play audio. Error: \(error)")
        }
        }
}//end of snare function

func loop1() {
    playing =  true
    while playing ==  true{
        for a in 1...4{
            t1 = CFAbsoluteTimeGetCurrent()
            Snare(vol: 2)
            t2 = CFAbsoluteTimeGetCurrent()
            while t2 - t1 < (1) {
                t2 = CFAbsoluteTimeGetCurrent()
            }
        }
    }
}

}//end of struct

PlaygroundPage.current.setLiveView(DrumMachine()) PlaygroundPage.current.wantsFullScreenLiveView = true

It would be good if some kind soul could help me sort this one.

2      

I'm not sure but looks like the playing variable is not @State

2      

Thanks I tried changing playing to an @variable. My code still worked but it made no difference. I still couldn't press my stop button the so I couldn't pass my boolean variable, playing and stop the loop.

I wonder if Swift doesn't check the ui while the while loop is running.

2      

That loop code is running on the main loop and so just continues in a loop. Also the Snare is loading an AVPlayer into memory everytime. All of that is expensive, so the main thread is blocked and you cant hit the stop button.

I dont really think that code is very efficient anyway, but I am no expert in AVAudioPlayer. But make AVAudioPlayer a global for sure. And move the loop to another thread.

2      

Thanks for the reply The only way I could find to play an mp3 is to recreate the avaudioplayer each time. This is quite a process and a few of them are needed to make a drum sound. as for threading that might be beyond me. I will investigate.

2      

Thanks for the reply The only way I could find to play an mp3 is to recreate the avaudioplayer each time. This is quite a process and a few of them are needed to make a drum sound. as for threading that might be beyond me. I will investigate.

2      

Thanks for your help eoinnorris, and NigelGee.

To make the function play in a background thread you call your function (Kick_Snare in this case.) inside a DispatchQueue.global as shown below. This thread runs in parallel with the main thread and the buttons are now pressable.

DispatchQueue.global(qos: .userInitiated).async { Kick_Snare()}

or name a dispatch queue like this:

let LoopThread = DispatchQueue(label: "BackGroundThread", qos: .background)

then call the function like this:

LoopThread.async{Kick_Snare()}

Also you don't have to create the avaudio players every time. They are played like this. (from another question I posted)

self.SnarePlayer?.currentTime = 0 
self.SnarePlayer?.play()

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.