I have an issue trying to get the button to show the correct title. With the initial code from the chapter (with the usual corrections for Swift 4.1) when ‘Start Pinging’ was clicked the program ran as expected, the button title changed to the alternate title of ‘Stop Pinging’ and then back to ‘Start Pinging’ at the completion of the run. The problem started when clicking ‘Stop Pinging’ while iPing was running. It stopped the pinging, the button momentarily (maybe 1 second) showed ‘Start Pinging’ then reverted to ‘Stop Pinging’. The button still starts and stops the pinging, but the title remains ‘Stop Pinging’ until I let it run to the end, then it will change to ‘Start Pinging’.
So I added the following code to func togglePinging(sender: NSButton):
if startButton.state == NSControl.StateValue.off {
startButton.state = NSControl.StateValue.on
}
And had to change the startButton state to on instead of off in func receiveProcessTerminationNotification(notification: Notification).
import Cocoa
class ViewController: NSViewController {
@IBOutlet var outputView: NSTextView!
@IBOutlet weak var hostField: NSTextField!
@IBOutlet weak var startButton: NSButton!
var process: Process?
var pipe: Pipe?
var fileHandle: FileHandle?
@IBAction func togglePinging(sender: NSButton) {
// Is there a running Process?
if let process = process {
// A process is running, stop it
process.interrupt()
} else {
// A process is not running, start one
// toggle button to display correct title/alternate
if startButton.state == NSControl.StateValue.off {
startButton.state = NSControl.StateValue.on
switchTitle = true
}
// Create a new process
let process = Process()
process.launchPath = "/sbin/ping"
process.arguments = ["-c10", hostField.stringValue]
// Create a new pipe for standardOutput
let pipe = Pipe()
process.standardOutput = pipe
// Grab the fileHandle
let fileHandle = pipe.fileHandleForReading
self.process = process
self.pipe = pipe
self.fileHandle = fileHandle
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(self)
notificationCenter.addObserver(self,
selector: #selector(receiveDataReadyNotification),
name: FileHandle.readCompletionNotification,
object: fileHandle)
notificationCenter.addObserver(self,
selector: #selector(receiveProcessTerminationNotification),
name: Process.didTerminateNotification,
object: process)
}
process?.launch()
outputView.string = ""
fileHandle?.readInBackgroundAndNotify()
}
//--------------------------------------------------------------------------------------------------
func appendData(data: Data) {
let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
let textStorage = outputView.textStorage!
let endRange = NSRange(location: (textStorage.length), length: 0)
textStorage.replaceCharacters(in: endRange, with: string)
}
//--------------------------------------------------------------------------------------------------
@objc func receiveDataReadyNotification(notification: Notification) {
let data = notification.userInfo![NSFileHandleNotificationDataItem] as! Data
let length = data.count
print("received data: \(length) bytes")
if length > 0 {
self.appendData(data: data)
}
// If the process is running, start reading again
if let fileHandle = fileHandle {
fileHandle.readInBackgroundAndNotify()
}
}
//--------------------------------------------------------------------------------------------------
@objc func receiveProcessTerminationNotification(notification: Notification) {
print("process terminated")
process = nil
pipe = nil
fileHandle = nil
startButton.state = NSControl.StateValue.on
}
//--------------------------------------------------------------------------------------------------
}
Now the button will change properly when clicked between the start and stop titles, however, because I had to change the button state to ‘on’ in func receiveProcessTerminationNotification(notification: Notification) (to get the button switching to work), now the button stays ‘Stop Pinging’ when allowed to complete a run.
Any ideas what I’ve messed up?