Using CocoaAsyncSocket to receive UDP in Swift iOS - ios

I am trying to receive broadcast UDP packets on iOS. I've verified that the packets are being sent (I can see them in another iOS app on the same device).
CocoaAsyncSocket seemed like the most straightforward library to do this with. I added the framework to my project using Carthage. Following the example in Objective-C, I have an iOS app with not much else in it, and this view being loaded:
import UIKit
import CocoaAsyncSocket
class FirstViewController: UIViewController
var sock:AsyncUdpSocket?
override func viewDidLoad() {
super.viewDidLoad()
if (sock == nil){
sock = AsyncUdpSocket(delegate: self)
}
do{
try sock!.bindToPort(54545)
try sock!.enableBroadcast(true) // Also tried without this line
sock!.receiveWithTimeout(10,tag: 0)
} catch {
print("error")
}
print("View Loaded...")
}
func onUdpSocket(cbsock:AsyncUdpSocket!,
didReceiveData data: NSData!){
print("Recv...")
print(data)
cbsock.receiveWithTimeout(10, tag: 0)
}
}
In either the Simulator, or on a device, in the debug console, I see the "View loaded..." message, but nothing from the callback, neither the "Recv..." or the data. It seems the callback isn't being called, but I am not getting any errors either. I've tried other timeouts, and various tweaks like making functions and classes public, but no luck.
I've written a receiver in Python, but this has me stumped.
EDIT: sock now used as a property at paulw's suggestion

Related

How to detect if iOS devices is connected using wifi in Swift?

In an old, Objective-C based project I have been using the below code to detect if the iOS devices is currently connected using Wifi (not cellular).
My attempts to translate this code into Swift 5 failed due to the Objective-C pointers. Is there a clean way to use this solution in Swift?
Or are are there better ways to solve this nowerdays? I found solutions using the Reachability port to Swift or NWPathMonitor(). While they seem to work in general, these solution are used to monitor the connection state and send notifications on changes while one time checks are not (well) supported.
Event though these solution could be used to get the current connection state, this is done using delegate callback methods or closures. Thus it is not possible to use these solutions in existing code which was created to work "synchronously" (without callbacks/closures).
Is there a simply way to use localWiFiAvailable in Swift?
The code:
+ (BOOL)localWiFiAvailable {
struct ifaddrs *addresses;
struct ifaddrs *cursor;
BOOL wiFiAvailable = NO;
if (getifaddrs(&addresses) != 0) return NO;
cursor = addresses;
while (cursor != NULL) {
if ((cursor -> ifa_addr -> sa_family == AF_INET) && !(cursor -> ifa_flags & IFF_LOOPBACK)) { // Ignore the loopback address
// Check for WiFi adapter
#if TARGET_IPHONE_SIMULATOR
wiFiAvailable = true;
break;
#else
if (strcmp(cursor -> ifa_name, "en0") == 0) {
wiFiAvailable = YES;
break;
}
#endif
}
cursor = cursor -> ifa_next;
}
freeifaddrs(addresses);
return wiFiAvailable;
}
Details on why NWPathMonitor() cannot be used:
As #baronfac pointed out in his comment NWPathMonitor() can also deliver the current state, but this can only be done using its .pathUpdateHandler closure.
I am using a third-party library where I can override a souldSendData() -> Bool method. Sending the data should not be allowed on mobile connection but only on WiFi. The methodes requires an instant decision to return true or false. Waiting for the closure is thus not possible.
So, I am limited by the existing class here. Yes, connection could change any second, however this is a different problem. e.g. NWPathMonitor can be used to cancel the transfer when connection changes to mobile.
Solving this problem in Objectiv-C was no problem using the code shown above. The question is simply, if such a "direct" solution is possible in Swift as well. While using the Objectiv-C code in the Swift project would be possible I would prefer to keep the project Swift only.
As mentioned by Paulw11, the recommended approach is using NWPathMonitor. A common practice is the following within a UIViewController - class:
private var monitor: NWPathMonitor?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { [weak self] path in
if !path.isExpensive { // this means the device is connected via WiFi
// enter your code here
}
}
let queue = DispatchQueue(label: "Monitor")
monitor?.start(queue: queue) // start to monitor the connection
}
override func viewWillDisappear(_ animated: Bool) {
monitor?.cancel() // end to monitor the connection
super.viewWillDisappear(animated)
}
EDIT:
Thanks to FLichter and Rob Napier for the clarification. Maybe it helps to use this approach:
func shouldSendData() -> Bool {
let monitor = NWPathMonitor()
return !monitor.currentPath.isExpense
}

OBEXFileTransferServices doesn't connect

I'm trying to write a macOS app that would connect to already paired the bluetooth phone and retrieves the list of address book entries and call records. This information should be available via standard OBEX interface. I'm relatively new to macOS development (although have enough experience with iOS development) and I have a feeling that I'm doing something wrong on a very basic level.
Here are snippets of my code:
First I'm finding particular paired Bluetooth device by its address
let paired = IOBluetoothDevice.pairedDevices()
let device = paired?.first(where: { (device) -> Bool in
return (device as? IOBluetoothDevice)?.addressString == "some_address"
}) as? IOBluetoothDevice
This actually works fine and I'm getting back valid object. Next, I'm picking up address book service and creating BluetoothOBEXSession for it
let service = device!.getServiceRecord(for: IOBluetoothSDPUUID(uuid32:kBluetoothSDPUUID16ServiceClassPhonebookAccess.rawValue))
let obexSession = IOBluetoothOBEXSession(sdpServiceRecord: service!)
This also works fine, I'm getting proper service object and session is created.
Next step (I would assume) is to create an OBEXFileTransfer session and do something (like checking current directory or retrieving the content of telecom/cch which supposed to have the list of combined outgoing and incoming calls:
let ftp = OBEXFileTransferServices(obexSession: obexSession!)
ftp!.delegate = self
if ftp!.connectToFTPService() == 0 {
NSLog("\(ftp!.currentPath())") // -- empty
ftp!.changeCurrentFolderForward(toPath: "telecom/cch")
NSLog("\(ftp!.currentPath())") // -- empty
ftp!.retrieveFolderListing()
}
I have added the following delegate's method to my view controller (to receive callbacks from OBEX FTS but they never get called:
override func fileTransferServicesRetrieveFolderListingComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError, listing inListing: [Any]!) {
NSLog("Listing complete...")
}
override func fileTransferServicesConnectionComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Connection complete...")
}
override func fileTransferServicesDisconnectionComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Disconnect complete...")
}
override func fileTransferServicesAbortComplete(_ inServices: OBEXFileTransferServices!, error inError: OBEXError) {
NSLog("Abort complete...")
}
What am I doing wrong here?
I also could not find any good Bluetooth examples for macOS either, if somebody has good links, please do share.

Getting Red5Pro Live Streaming to function properly on iOS

So I'm working on allowing users to begin a livestream (visible to those subscribed to them) from our application. We are using a Red5Pro server. I have followed the instructions from Red5's iOS page, and when it runs on the phone the camera screen comes up, our really nice looking UI comes up, everything looks great.
But when I push the button to begin recording a livestream, the app either
1) crashes abruptly
2) claims it is taking a livestream, but it won't show up on Red5's "Check if your server has a stream being broadcasted currently" page.
Anyone with Red5Pro experience wanna glance over my code and possibly point to something wrong? We are using Swift 2 still (not my choice) at the moment, and there are no error messages on Xcode's side of things. Thanks!
import UIKit
import R5Streaming
class PublishViewController : R5VideoViewController, R5StreamDelegate{
var config : R5Configuration!
var stream : R5Stream!
override func viewDidLoad() {
super.viewDidLoad()
config = R5Configuration()
config.host = Defaults.sharedDefaults.localHost
config.port = Int32(Defaults.sharedDefaults.hostPort)
config.contextName = "live"
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.stop()
}
func preview(isBackCamera: Bool) {
let cameraDevice: AVCaptureDevice = isBackCamera ? AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo).first as! AVCaptureDevice : AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo).last as! AVCaptureDevice
let camera = R5Camera(device: cameraDevice, andBitRate: 512)
camera?.orientation = (camera?.orientation)! + 90
let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
let microphone = R5Microphone(device: audioDevice)
let connection = R5Connection(config: config)
stream = R5Stream.init(connection: connection)
stream.attachVideo(camera)
stream.attachAudio(microphone)
stream.delegate = self
self.attachStream(stream)
self.showPreview(true)
}
func start() {
self.showPreview(false)
stream.publish("red5prostream", type:R5RecordTypeLive)
}
func stop() {
stream.stop()
stream.delegate = nil
}
func onR5StreamStatus(stream: R5Stream!, withStatus statusCode: Int32, withMessage msg: String!) {
print("Stream: \(r5_string_for_status(statusCode)) - \(msg!)")
}
}
First, make sure you download the latest iOS SDK and Red5 Pro server.
Red5 Pro Accounts Site
Your code looks good except that you have the iOS code pointing at "localhost" for your config.
config.host = Defaults.sharedDefaults.localHost
What that line is trying to do is connect your iOS device to itself. You need to point this at your Red5 Pro server. You should go to the machine where your server is running and issue ifconfig to determine what the local IP address of the server is or the WAN IP Address where you deployed the server. Then use that as the host in your iOS config host property.
You can additionally check out the "Getting Started with iOS" section of our developer series in order to get a feel for how we set up a similar application. https://www.red5pro.com/docs/developerseries/03/gsios.html
You can also join our slack channel from the accounts page as well as submit tickets for any issues you observe.
Hope this helps!

Nearby Bluetooth devices using Swift 3.0

I'm looking for a way to programmatically list any nearby Bluetooth devices (discoverable) that my device finds. I have not been able to find any information or tutorials regarding performing this call in Swift 3.0. This Q-A post discusses finding these devices using Swift 1.0 and building in Xcode 6, rather than the latest version 8.
I did my best to try to make my code into the 3.0 Syntax from the 1.0, but while running the following code, nothing is returned in the Playground:
import Cocoa
import IOBluetooth
import PlaygroundSupport
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry, error: IOReturn, aborted: Bool) {
aborted
print("called")
let devices = sender.foundDevices()
for device : Any? in devices! {
if let thingy = device as? IOBluetoothDevice {
thingy.getAddress()
}
}
}
}
var delegate = BlueDelegate()
var inquiry = IOBluetoothDeviceInquiry(delegate: delegate)
inquiry?.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Using IOBluetooth the Correct Way
The following code works flawlessly in Xcode Version 8.2.1 (8C1002), Swift 3.0. There are a few lines that aren't required, such as the entire method of deviceInquiryStarted.
Update: These usages still work as of Xcode 9.2 (9B55) and Swift 4.
Playground
import Cocoa
import IOBluetooth
import PlaygroundSupport
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) {
print("Inquiry Started...")
//optional, but can notify you when the inquiry has started.
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) {
print("\(device.addressString!)")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry!, error: IOReturn, aborted: Bool) {
//optional, but can notify you once the inquiry is completed.
}
}
var delegate = BlueDelegate()
var ibdi = IOBluetoothDeviceInquiry(delegate: delegate)
ibdi?.updateNewDeviceNames = true
ibdi?.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Project-Application Usage
import Cocoa
import IOBluetooth
import ...
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) {
print("Inquiry Started...")
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) {
print("\(device.addressString!)")
}
}
//other classes here:
//reference the following outside of any class:
var delegate = BlueDelegate()
var ibdi = IOBluetoothDeviceInquiry(delegate: delegate)
//refer to these specifically inside of any class:
ibdi?.updateNewDeviceNames = true
ibdi?.start() //recommended under after an action-button press.
Explanation
The issue I was originally faced with was trying to access the information as the inquiry was still in process.
When I accessed it, under many different occasions my playground would hang and I would be forced to force quit both Xcode.app, and com.apple.CoreSimulator.CoreSimulatorService from the Activity Monitor. I lead myself to believe that this was just a Playground bug, only to learn that my application would crash once the inquiry finished.
As Apple's API Reference states:
Important Note: DO NOT perform remote name requests on devices from delegate methods or while this object is in use. If you wish to do your own remote name requests on devices, do them after you have stopped this object. If you do not heed this warning, you could potentially deadlock your process.
Which entirely explained my issue. Rather than directly asking for the IOBluetoothDevice information from the sender.foundDevices() method (which I believe may not have been updating..?) I simply used the parameters built into the function to mention that it was indeed an IOBluetoothDevice object, and simply to ask for that information to be printed.
Final Note
I hope that this Q/A I've created helps others in need when using IOBluetooth in Swift. The lack of any tutorials and the high amounts of outdated, Objective-C code made finding this information very challenging. I'd like to thank #RobNapier for the support on trying to find the answer to this riddle in the beginning. I'd also like to thank NotMyName for the reply on my post on the Apple Developer Forums.
I will be exploring the usage of this in an iOS device more sooner than later!

GameCenter Multiplayer Stuck on "Starting Game..."

I am currently working on my game and I have decided to enable multiplayer via GameCenter in the Game to allow users to play their friend. I have followed a tutorial by RayWinderLinch, but ran into a problem.
My problem is that when I load up the GKMatchMakingViewController and hit the big Play Now button on both devices it will find each other (which is meant to happen) and under the set game center user name it will say Ready.
This means that GameCenter has found each player and is ready to start the match which it should, but in my case the match never begins. It is stuck on a loop that says Starting Game... and nothing happens. It appears that the
func matchmakerViewController(viewController: GKMatchmakerViewController!, didFindMatch theMatch: GKMatch!)
and the
func match(theMatch: GKMatch!, player playerID: String!, didChangeState state: GKPlayerConnectionState)
method's are never ran. I am completely lost on what is going on. I have tried this many times over and over to fix the problem but nothing worked. I will attach an image that show's the screen of the application where my problem persists and I will also attach the code I am using.
I am using a framework based of of the GameKitHelper.h In the
mentioned tutorial above. It is written in swift and is called
GCHelper
Code
The code for GCHelper can be found using the GitHub link mention earlier
I have cut out code that is unnecessary for this problem
class GameScene : SKScene, GameKitHelper, MultiplayerNetworkingProtocol {
override func didMoveToView () {
GCHelper().authenticateLocalUser() //Authenticate GameCenter User
println("\n \n \n Authenticating local user \n \n \n")
}
func startMultiplayer () {
var vc = self.view?.window?.rootViewController
GameKitHelper().findMatchWithMinPlayers(2, maxPlayers: 2, viewController: vc!, delegate: self); //Find match and load GKMatchMakerViewController
}
func matchStarted() {
//Delegate method
println("match started")
}
func matchEnded() {
//Delegate method
println("match ended")
}
func match(match: GKMatch, didReceiveData: NSData, fromPlayer: String){
//Delegate Method
println("Did receive data")
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == multiplayer //SKSpriteNode {
//User clicked on multiplayer button, launch multiplayer now!
println("Loading multiplayer")
startMultiplayer()
}
}
Image
UPDATE
I have noticed that when I test using my iPhone and the simulator, on the iPhone the status will go from Ready to Disconnected but on the simulator the status is still Ready and then I will get the following message in the console for the iPhone
Warning matchmakerViewController:didFindMatch: delegate method not implemented`
Even though it is implemented in the GCHelper.swift file. This does not happen when I test on my iPhone and iPad Mini it just keeps on saying Starting Game...
Any help will be appreciated.
Prerequisites
Both players must be in the same environment (Sandbox for testing)
The authenticationChanged in GCHelper.swift must not be private. You may have to remove that keyword.
There are a few delegates involved, and in your example, there are a few competing protocols. My recommendation is to create a new App using minimalistic code to track down the startMultiplayer issue.
Gamekit Multi Player Step by Step Tutorial using GCHelper
Create a new project (Xcode > File > New > Project... > Single View Application > ... > Create) using the very same Product Name & Organization Name as your game, so that it matches both App Bundle Identifier and iTunes Game Center parameters. This will allow you to run tests without overhead.
Use this Podfile:
platform :ios, '8.0'
use_frameworks!
target 'SO-31699439' do
pod 'GCHelper'
end
Use a GCHelperDelegate
Create a UIViewController with just the bare minimum (a Start Multiplayer button), and connect it to this action:
#IBAction func startMultiplayerAction(_ sender: AnyObject) {
GCHelper.sharedInstance.findMatchWithMinPlayers(
2,
maxPlayers: 2,
viewController: self,
delegate: self);
}
Here is the crux: the delegate you pass must adopt GCHelperDelegate. It does not have to be the same class, but in your example above it is, and the present rule was not respected. For this example, ViewController adopts GCHelperDelegate:
import UIKit
import GCHelper
import GameKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GCHelper.sharedInstance.authenticateLocalUser()
}
}
Implement required GCHelperDelegate methods in an extension
Since ViewController adopts GCHelperDelegate, the three methods below must be in that same class, and will be invoked:
extension ViewController: GCHelperDelegate {
func matchStarted() {
print("matchStarted")
}
func match(_ match: GKMatch, didReceiveData: Data, fromPlayer: String) {
print("match:\(match) didReceiveData: fromPlayer:\(fromPlayer)")
}
func matchEnded() {
print("matchEnded")
}
}
Execution
Tested: built, linked, ran, successful match.
Launch app, tap Start Multiplayer button, tap Play Now on both devices (or iPhone Simulator + real device).
Log:
Authenticating local user...
Authentication changed: player not authenticated
Ready to start match!
Found player: SandboxPlayer
matchStarted
► Find this solution on GitHub and additional details on Swift Recipes.

Resources