1

Basically, I'm trying to detect when the user has copied something to the clipboard and perform an action. In this case, I am trying to play a sound; I have imported the sound file to Xcode. However, It crashes because of the while loop and if I remove the while loop it still crashes because I restart the program in the end. How should I go about doing this because I always end up in a loop and eventually program crashes and can't detect a change in the NSPasteboard's changeCount. Also the sound file does not work, and I can't seem to figure out why. Any help would be awesome!!!! Writing it in Swift only.

Edit 1: I know why it's crashing, I just don't know any other way to do it.

import Cocoa
import AVFoundation

class ViewController: NSViewController {
let pasteboard = NSPasteboard.general

override func viewDidLoad() {
    super.viewDidLoad()

    let sound = URL(fileURLWithPath: Bundle.main.path(forResource: "sound", ofType: "mp3")!)

    var audioPlayer: AVAudioPlayer?

    //intializing audio player
    do
    {
        try audioPlayer = AVAudioPlayer(contentsOf: sound)

    }catch{
        print("fail")
    }


    let lastChangeCount=pasteboard.changeCount

    //keep looping until something is copied.
    while(pasteboard.changeCount==lastChangeCount){

    }

    //something is copied to clipboard so play audio
    audioPlayer?.play()

    //restart program
    self.viewDidLoad()

  }
Joe
  • 25
  • 6
  • 1
    Rather than this ugly `while` loop use a timer to poll the pasteboard once per second. And there's a URL related API `Bundle.main.url(forResource: "sound", withExtension: "mp3")`. And never, never, never call `viewDidLoad` yourself. – vadian Aug 07 '18 at 21:07
  • do you see the problem? Calling `viewDidLoad` within itself will create an infinite loop- with or without the `while` statement. – FullMetalFist Aug 07 '18 at 21:16
  • @vadian I tried that way too, but to no avail. However, my way of setting the URL is correct. I can't think of why the sound would not play. I do not understand what you mean by "poll the pasteboard once per second". – Joe Aug 07 '18 at 21:18
  • @FullMetalFist yeah I know that. But I needed a way to restart the program to find another detection. That's the best I could think of. – Joe Aug 07 '18 at 21:19

1 Answers1

0

Polling with while loops is very bad habit and you must not call viewDidLoad, never.

This version uses a Timer which fires once per second and checks the pasteboard in the closure.

For playing a simple sound AVFoundation is overkill

class ViewController: NSViewController {


    var lastChangeCount = 0
    var timer : Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        let pasteboard = NSPasteboard.general

        lastChangeCount = pasteboard.changeCount
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] timer in
            if pasteboard.changeCount != self.lastChangeCount {
                NSSound(named: NSSound.Name("sound"))?.play()
                self.lastChangeCount = pasteboard.changeCount
            }
        }
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thank you very much! I'm still learning Swift, and I was wondering if you can explain to me how the timer works. How does it reset the program for another copy detection? – Joe Aug 07 '18 at 22:15
  • There is no reset. The timer fires continuously (`repeats: true`) every second (`withTimeInterval: 1.0`) which calls the code in the closure. And if a count change is detected the `lastChangeCount` variable is set to the current value. That's all – vadian Aug 08 '18 at 04:26
  • Interesting. So it's essentially a way of creating an infinite loop that is always running in the background even after the program has finished executing without taxing the system's memory? – Joe Aug 08 '18 at 18:57
  • I disagree with the term *infinite loop*. Swift and Objective-C is method and thread based. All code is executed in the scope of methods. If no code is currently executed the app is idle. The timer notifies the app to run a method (or closure) in a background thread to keep the UI responsive. – vadian Aug 08 '18 at 19:03