Hi all,
I'm just a beginner wanting to create a client on MacOs for controlling a server(Blackmagic 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!
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 private var commandValue: String = ""
@State var dataValue: String = ""
let portHyperdeck: UInt16 = 8888
var body: some View {
VStack {
TabView() {
VStack {
HStack {
Text("IP Hyperdeck 1")
.padding(.leading, 60.0)
TextField("IP adres", text: $ipAdresHyperdeck1)
.frame(width: 90.0)
}
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)
}
}
.tabItem { Text("Connect").tag(1) }
VStack {
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")
}
}
.tabItem { Text("Playout").tag(2) }
Text("Tab Content 3").tabItem { Text("Record").tag(3) }
VStack {
HStack {
Text("IP Adres Hyperdeck 1:")
Text("\(ipAdresHyperdeck1)")
}
}
.tabItem { Text("Status").tag(4) }
HStack {
Text("Data")
TextField("Data you want to send", text: $commandValue)
Button(action: {
self.sendData(server: "\(self.ipAdresHyperdeck1)", port: self.portHyperdeck)
}) {
Text(" Send Data ")}
}
.tabItem { Text("Send data").tag(5) }
}
.padding(.top, 20)
.frame(width: 500.0, height: 400.0)
}
}
func initClient(server: String, port: UInt16) {
let client = Client(host: server, port: port)
let welcomecommand = String("Hi server!")
client.start()
client.connection.send(data: (welcomecommand.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)")
client.connection.send(data: (command.data(using: .utf8))!)
}
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()
}
}
}