Hi all,
I'm just a beginner wanting to create a client on MacOs for controlling a server (Hyperdeck). After a lot of beginners tutorials about basic Swift and SwiftUI, I started working on the client app.
It should be quite basic: Open connection to server via TCP, and once a connection is established send easy text data to the server.
After searching a lot, I figured Apple's Network framework (NWframework) would be quite useful. I found some examples and with those examples, i compiled the code below.
Now when running the application, I'm able to setup a connection and send a first datastring to the server by calling the initClient function. But then i try to send another string of data by calling functions as stopPlayout or sendData, but nothing happens. When adding client.start() before the client.connection.send() in the functions, it opens a new connection to the server and sends the string. But I want the client to send the data over the first connection which is still open. Hyperdeck only allows 1 connection.
Anyone can see what I'm doing wrong here?
Thanks a lot!
@Eskimo, thanks for your answer on swift forum. I did execute a package recording, nothing is recorded when calling the send function. So apparently no data is send when calling the send function. When initializing a connection and sending the first string in one function, packets are recorded.
Client.swift
import Foundation
import Network
@available(macOS 10.14, *)
class Client {
let connection: ClientConnection
let host: NWEndpoint.Host
let port: NWEndpoint.Port
init(host: String, port: UInt16) {
self.host = NWEndpoint.Host(host)
self.port = NWEndpoint.Port(rawValue: port)!
let nwConnection = NWConnection(host: self.host, port: self.port, using: .tcp)
connection = ClientConnection(nwConnection: nwConnection)
}
func start() {
print("Client started \(host) \(port)")
connection.didStopCallback = didStopCallback(error:)
connection.start()
}
func stop() {
connection.stop()
}
func send(data: Data) {
connection.send(data: data)
}
func didStopCallback(error: Error?) {
if error == nil {
exit(EXIT_SUCCESS)
} else {
exit(EXIT_FAILURE)
}
}
}
ClientConnection.swift
import Foundation
import Network
@available(macOS 10.14, *)
class ClientConnection {
let nwConnection: NWConnection
let queue = DispatchQueue(label: "Client connection Q")
init(nwConnection: NWConnection) {
self.nwConnection = nwConnection
}
var didStopCallback: ((Error?) -> Void)? = nil
func start() {
print("connection will start")
nwConnection.stateUpdateHandler = stateDidChange(to:)
setupReceive()
nwConnection.start(queue: queue)
}
private func stateDidChange(to state: NWConnection.State) {
switch state {
case .waiting(let error):
connectionDidFail(error: error)
case .ready:
print("Client connection ready")
case .failed(let error):
connectionDidFail(error: error)
default:
break
}
}
private func setupReceive() {
nwConnection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
}
if isComplete {
self.connectionDidEnd()
} else if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive()
}
}
}
func send(data: Data) {
nwConnection.send(content: data, completion: .contentProcessed( { error in
if let error = error {
self.connectionDidFail(error: error)
return
}
print("connection did send, data: \(data as NSData)")
}))
}
func stop() {
print("connection will stop")
stop(error: nil)
}
private func connectionDidFail(error: Error) {
print("connection did fail, error: \(error)")
self.stop(error: error)
}
private func connectionDidEnd() {
print("connection did end")
self.stop(error: nil)
}
private func stop(error: Error?) {
self.nwConnection.stateUpdateHandler = nil
self.nwConnection.cancel()
if let didStopCallback = self.didStopCallback {
self.didStopCallback = nil
didStopCallback(error)
}
}
}
ContentView.swift
import Foundation
import SwiftUI
import Network
struct ContentView: View {
@State var ipAdresHyperdeck1: String = "localhost"
@State var ipAdresHyperdeck2: String = ""
@State private var commandValue: String = ""
@State var dataValue: String = ""
let portHyperdeck: UInt16 = 8888
var body: some View {
VStack {
HStack {
Text("IP Hyperdeck 1")
.padding(.leading, 60.0)
TextField("IP adres", text: $ipAdresHyperdeck1)
.frame(width: 90.0)
Spacer()
}
HStack {
Button(action: {
self.initClient(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{ Text(" Connect Hyperdeck 1 ")}
.padding(.leading, 60)
Spacer()
Button(action: {
self.stopConnection(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{ Text(" Stop hyperdeck 1 ")}
.padding(.trailing, 55)
}
Button(action: {
self.stopPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{Text("STOP")}
Button(action: {
self.playPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{Text("PLAY")}
Button(action: {
self.goToInPlayout(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{Text("GO TO IN")}
HStack {
Text("Data")
TextField("Data you want to send", text: $commandValue)
Button(action: {
self.sendData(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)})
{Text(" Send Data ")}
}
}
}
func initClient(server: String, port: UInt16) {
let client = Client(host: server, port: port)
var command = ""
client.start()
switch (command){
case "CRLF":
command = "\r\n"
case "RETURN":
command = "\n"
case "exit":
client.stop()
default:
command = "hi server"
}
client.connection.send(data: (command.data(using: .utf8))!)
}
func stopConnection(server: String, port: UInt16) {
let client = Client(host: server, port: port)
client.stop()
}
func sendData(server: String, port: UInt16) {
let client = Client(host: server, port: port)
let command = String("\(commandValue)")
switch (command) {
case "exit":
client.stop()
commandValue = ""
default:
client.connection.send(data: (command.data(using: .utf8))!)
commandValue = ""
}
}
func stopPlayout(server: String, port: UInt16) {
let client = Client(host: server, port: port)
let command = String("stop")
client.connection.send(data: (command.data(using: .utf8))!)
}
func playPlayout(server: String, port: UInt16) {
let client = Client(host: server, port: port)
let command = String("play")
client.connection.send(data: (command.data(using: .utf8))!)
}
func goToInPlayout(server: String, port: UInt16) {
let client = Client(host: server, port: port)
let command = String("goto: clip: start")
client.connection.send(data: (command.data(using: .utf8))!)
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}