iOS Pusher client not automatically reconnecting - ios

I'm using the Pusher client for iOS, installing it via CocoaPods (pod 'libPusher', '~> 1.5').
It all sets up fine and the events come through fine. However if the device (iPhone 6s running iOS 9.0.2) loses the internet connection (caused by me going to airplane mode) then regains it a minute later (me coming out of airplane mode), Pusher never regains a connection.
I've added a some UIAlertViews to test out what it's doing based on it's delegate method.
Initially connectionWillConnect and connectionDidConnect are shown.
When airplane mode is turned on connectionWillConnect then connectionWillAutomaticallyReconnection afterDelay of 0.0 are shown.
(Then left for a minute or so without the internet.)
Then nothing, even after connection back to the internet. And the events are no longer received properly.
-
Here is the class I use for all the Pusher things (written in Swift 2.0), which works well until connection is lost.
class PusherInterface: NSObject, PTPusherDelegate {
// MARK: - PusherInterface Shared Instance
/// Singleton instance of the PusherInterface class.
private static let sharedInstance = PusherInterface()
// MARK: - Pusher Credentials
private static let pusherAppId = "MY_APP_ID"
private static let pusherKey = "MY_KEY"
private static let pusherSecret = "MY_SECRET"
/// The connected client used by Pusher to connect to event channels
private static var client: PTPusher = {
let pusherClient = PTPusher.pusherWithKey(pusherKey, delegate: PusherInterface.sharedInstance)
pusherClient.connect()
return pusherClient as! PTPusher
}()
// MARK: - Setup Pusher
static func startListening() {
client.subscribeToChannelNamed("MY_CHANNEL")
client.bindToEventNamed("MY_EVENT") { pusherEvent in
// Does some stuff with the data back
}
}
// MARK: - Pusher Delegate
func pusher(pusher: PTPusher!, connectionDidConnect connection: PTPusherConnection!) {
NSOperationQueue.mainQueue().addOperationWithBlock {
UIAlertView(title: "connectionDidConnect", message: "", delegate: nil, cancelButtonTitle: "Dismiss").show()
}
}
func pusher(pusher: PTPusher!, connectionWillAutomaticallyReconnect connection: PTPusherConnection!, afterDelay delay: NSTimeInterval) -> Bool {
NSOperationQueue.mainQueue().addOperationWithBlock {
UIAlertView(title: "connectionWillAutomaticallyReconnect", message: "afterDelay \(delay)", delegate: nil, cancelButtonTitle: "Dismiss").show()
}
return true
}
func pusher(pusher: PTPusher!, connectionWillConnect connection: PTPusherConnection!) -> Bool {
NSOperationQueue.mainQueue().addOperationWithBlock {
UIAlertView(title: "connectionWillConnect", message: "", delegate: nil, cancelButtonTitle: "Dismiss").show()
}
return true
}
}
Any ideas on why it's not working?
All ideas & theories would be very much appreciated! Thank you :)

It wasn't working for me either with Swift. I installed a swift implementation of Pusher and is working fine now.
let pusher = Pusher(key: "c984997151153c177dc2")
pusher.connect()
let channel = pusher.subscribe("test_channel")
channel.bind("my_event") { (event: AnyObject?) -> Void in
print("my_event push received ")
}

Related

Using reachability library to Swiftui based app to notify when network is lost

I'm very much new to ios && using cocoapods.
I scanned over SO to find easiest way to detect network status and lots of answers directed me to Reachability git by AshleyMills.
I'm writing my webView app in swiftui & I want to pop up an alert/notifier when user's internet connection is lost. (So they don't sit idle while my webview tries to load)
It would be best if listner to network changes keeps running in the background while the app is on.
Most of the answers in SO seem to be Swift-based (appdelegate, ViewDidload, etc), which I don't know how to use because I started off with SwiftUI
Thanks in advance.
Edit(With attempts for Workaround that Lukas provided)
I tried the below. It compiles and runs BUT the alert wouldn't show up. Nor does it respond to connection change.
I have number of subViews in my ContentView. So I called timer && reachability on app level.
#State var currentDate = Date()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let reachability = try! Reachability()
#State private var showAlert: Bool = false
It seems to be not working:
WindowGroup {
ContentView()
.onAppear() {
if !isConnected() {
self.showAlert = true
}
}
.onReceive(timer) { _ in
if !isConnected() {
self.showAlert = true
}else{
self.showAlert = false
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Error"), message: Text("Your internet connection is too slow."), dismissButton: .default(Text("ok")))
}
}
NWPathMonitor was introduced in iOS 12 as a replacement for Reachability. A naive implementation would be as follows.
We could expose the status that comes back from pathUpdateHandler, however that would require you to import Network everywhere you wanted to use the status - not ideal. It would be better to create your own enum that maps to each of the cases provided by NWPath.Status, you can see the values here. I’ve created one that just handles the connected or unconnected states.
So we create a very simple ObservedObject that publishes our status. Note that as the monitor operates on its own queue and we may want to update things in the view we will need to make sure that we publish on the main queue.
import Network
import SwiftUI
// An enum to handle the network status
enum NetworkStatus: String {
case connected
case disconnected
}
class Monitor: ObservableObject {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "Monitor")
#Published var status: NetworkStatus = .connected
init() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
// Monitor runs on a background thread so we need to publish
// on the main thread
DispatchQueue.main.async {
if path.status == .satisfied {
print("We're connected!")
self.status = .connected
} else {
print("No connection.")
self.status = .disconnected
}
}
}
monitor.start(queue: queue)
}
}
Then we can use our Monitor class how we like, you could use it as a StateObject as I have done below, or you could use it as an EnvironmentObject. The choice is yours. Ideally you should have only a single instance of this class in your app.
struct ContentView: View {
#StateObject var monitor = Monitor()
// #EnvironmentObject var monitor: Monitor
var body: some View {
Text(monitor.status.rawValue)
}
}
Tested in Playgrounds on an iPad Pro running iOS 14.2, and on Xcode 12.2 using iPhone X (real device) on iOS 14.3.
I worked one time with rechabiltiy and had the same problem. My solution is a work-around but in my case it was fine.
When the user get to the view where the connection should be constantly checked you can start a time(https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-a-timer-with-swiftui). The timer calls a function in an interval of your choice where you can check the connection with the following code:
let reachability = try! Reachability()
...
func isConnected() -> Bool{
if reachability.connection == .none{
return "false" //no Connection
}
return true
}
}
You can also call this function .onAppear

Swift: In NWConnection class when does receiveMessage(completion:) gets called

I wanna have UDP connection with a wifi module as a server and an iOS as a client and send/receive data (obviously).First i wanted to use Sockets but then i realized Apple has introduced Network Framework; So i used NWConnection which is a class from Network Framework for my purpose and i was successful at sending the data to the device but unable to receive the response (which i am sure exists as i monitor devices I/O packets via serial port monitor).Here is a test version of the code where i use netcat as server to test the connection:
ViewController Class
import UIKit
import Network
class ViewController: UIViewController
{
var network: UDPNetwork!
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func button(_ sender: Any)
{
self.network = UDPNetwork(host: "192.168.200.4", port:"4210")!
self.network.connect()
}
}
UDPNetwork Class
import Foundation
import Network
class UDPNetwork {
var hostUDP: NWEndpoint.Host
var portUDP: NWEndpoint.Port
private var connection: NWConnection?
private var queue = DispatchQueue(label: "NetworkQuue", qos: .utility)
init?(host: String, port: String) {
guard !host.isEmpty, let portUDP = NWEndpoint.Port(port) else {
return nil
}
self.hostUDP = NWEndpoint.Host(host)
self.portUDP = portUDP
}
func connect()
{
connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
connection?.stateUpdateHandler =
{
(newState) in switch (newState)
{
case .ready:
//The connection is established and ready to send and recieve data.
print("ready")
self.sendPaket("Hello")
self.receive()
case .setup:
//The connection has been initialized but not started
print("setup")
case .cancelled:
//The connection has been cancelled
print("cancelled")
case .preparing:
//The connection in the process of being established
print("Preparing")
default:
//The connection has disconnected or encountered an error
print("waiting or failed")
}
}
connection?.start(queue: self.queue)
}
func sendPaket(_ packet:String)
{
let packetData = packet.data(using: .utf8)
self.connection?.send(content: packetData, completion: NWConnection.SendCompletion.contentProcessed(({ (error) in
if let err = error {
print("Sending error \(err)")
} else {
print("Sent successfully")
}
})))
}
func receive()
{
self.connection?.receiveMessage(completion:
{
(data, context, isComplete, error) in
print("Got it")
if let err = error {
print("Recieve error: \(err)")
}
if let rcvData = data,
let str = String(data:rcvData, encoding: .utf8) {
print("Received: \(str)")
}
self.receive()
})
}
}
Apple Documentation says:
receiveMessage(completion:)
Schedules a single receive completion handler for a complete message
completion :
A receive completion is invoked exactly once for a call to receive.
My Question is:
How can we call to receive ?
Assuming the receiveMessage(completion:) method is the Call to receive and also after receiving complete message calls the completion itself, What could be the problem if it doesn't get invoked?
Related code and more detail about my use case can be found here:
Swift: Receiving UDP packets from server on serial port monitor but not in ios app
With these assumptions :
We call to receive on the same connection that we send data to
UDP is used method for connection
Server is tested for swapping ports correctly, meaning it responds on the same IP,Port that data is sent to
My Setup:
13" MacBookPro Early 2015 with MacOS Catalina 10.15
Xcode Version 11.0 (11A420a)
Swift 5.1
target iOS 12+
And this is the result from Wire Shark:

App crashes on Launch in Airplane Mode

I am currently using Ashley Mill's Reachability Class. If the application launches with network connectivity then I am able to toggle between connectivity availability without any issues and able to display a network connectivity Alert Controller properly. However if the application is launched when the app starts without internet connection/on airplane mode it abruptly crashes.
override func viewDidLoad()
{
super.viewDidLoad()
setUpReachability (nil)
}
func setUpReachability(hostName: String?)
{
do
{
let reachability = try hostName == nil ? Reachability.reachabilityForInternetConnection() : Reachability(hostname: hostName!)
self.reachability = reachability
try! self.reachability?.startNotifier()
}
catch ReachabilityError.FailedToCreateWithAddress(let address)
{
print("\(address)")
return
} catch {}
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.reachabilityChanged(_:)), name: ReachabilityChangedNotification, object: reachability)
}
func reachabilityChanged(notification: NSNotification)
{
let reachability = notification.object as! Reachability
if reachability.isReachable()
{
if reachability.isReachableViaWiFi()
{
connected = true
}
else
{
connected = true
}
}
else
{
let alert = UIAlertController( title: "No Network Connection Available", message:"Try Again", preferredStyle: .Alert)
alert.addAction(UIAlertAction( title: "Will Do!" , style: .Default) { _ in } )
presentViewController ( alert, animated: true ) {}
connected = false
}
}
What can be done to allow the iPhone application to launch and display an alert saying there is no network connection rather than abruptly crash?
Error Message:
fatal error: unexpectedly found nil while unwrapping an Optional value
But I would think that reachability changed would catch this in the else statement and pop the error message up?
Shouldn't the else in the reachability.isReachableViaWiFi() if statement be:connected = false ?
The error was that I was in fact trying to download data at the launch of the app instead of first allowing the initialization of the app to finish to then send a request to the server to access information.

WCSession.sendMessage works 50/50

Lately, I am working on a project is related to Watch/iPhone communication again. But my code works sometimes and doesn’t work sometimes which is kind of weird to me because I think the code should either work or not. It cannot be 50/50. Therefore, I have no idea what goes wrong.
setup WCSession on iPhone:
class WatchCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("iPhone does not support WCSession")
}
}
... ...
}
similar WCSession setup on Watch:
class PhoneCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("Watch does not support WCSession")
}
}
... ...
}
send out message on Watch:
func sendGesture(gesture : GKGesture){
// if WCSession is reachable
if session!.reachable { // it is reachable
// create the interactive message with gesture
let message : [String : AnyObject]
message = [
"Type":"Gesture",
"Content":gesture.rawValue
]
// send message
session!.sendMessage(message, replyHandler: nil, errorHandler: nil)
print("Watch send gesture \(gesture)")
} else{ // it is not reachable
print("WCSession is not reachable")
}
}
related enum:
enum GKGesture: Int {
case Push = 0, Left, Right, Up, Down
}
receive message on iPhone:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//retrieve info
let type = message["Type"] as! String
let content = message["Content"]
switch type {
case "Gesture":
handleGesture(GKGesture(rawValue: content as! Int)!)
default:
print("Received message \(message) is invalid with type of \(type)")
}
}
func handleGesture(gesture : GKGesture){
print("iPhone receives gesture \(gesture)")
var notificationName = ""
switch gesture {
case .Up:
notificationName = "GestureUp"
case .Down:
notificationName = "GestureDown"
case .Left:
notificationName = "GestureLeft"
case .Right:
notificationName = "GestureRight"
case .Push:
notificationName = "GesturePush"
}
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil)
}
somehow I can’t debug my Watch app on Xcode, the debug session just won’t attach. I don’t know why. Therefore, I debug one-sided with just the iPhone.
sometimes I got "receives gesture” print out, and sometimes not. And the same for getting the notification.
I don't know if Int would be wrapped around to NSNumber while being transfer within WCSession. If it would be, then that must be why when I use Int as the base class of the enum it won't work and works when String is the base class.
Connectivity Known Issue Your app may crash when using NSNumber and
NSDate objects with the WCSession API.
Workaround: Convert an NSNumber or NSDate object to a string before
calling WCSession APIs. Do the opposite conversion on the receiving
side.
Watch OS 2 Beta 4 release note
My guess is your call to sendMessage is returning an error in the cases where it fails, but you haven't implemented the error handler!! For now while you are getting up and running you can get away with just printing the error, but if this is shipping code you really ought to handle the appropriate errors:
// send message
session.sendMessage(message, replyHandler: nil, errorHandler: { (error) -> Void in
print("Watch send gesture \(gesture) failed with error \(error)")
})
print("Watch send gesture \(gesture)")
Your flow is correct but the difficulty is to understand how to debug:
Debug Watch:
Run the iPhone target and when it is done hit the Stop button.
Open the iOS app inside the simulator (run it manually from the simulator and not from Xcode) and let it hang there.
Switch to the Watch target (yourAppName WatchKit App), put the relevant breakpoint and run it.
The iOS app will be put automatically in the background and then you will be able to use sendMessage method (at the Watch target) to send whatever you need and if you have a replayHandler in your iOS app you will even receive the relevant messages inside the sendMessage at your Watch target (i.e InterfaceController)
Small Swift example:
Sending a Dictionary from Watch to iOS app:
if WCSession.defaultSession().reachable == true {
let requestValues = ["Send" : "From iWatch to iPhone"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues,
replyHandler: { (replayDic: [String : AnyObject]) -> Void in
print(replayDic["Send"])
}, errorHandler: { (error: NSError) -> Void in
print(error.description)
})
}
else
{
print("WCSession isn't reachable from iWatch to iPhone")
}
Receiving the message from the Watch and sending a replay from the iOS app:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print(message.values)
var replyValues = Dictionary<String, AnyObject>()
replyValues["Send"] = "Received from iphone"
// Using the block to send back a message to the Watch
replyHandler(replyValues)
}
Debug iPhone:
The exact opposite of debug watch
Also, the answer by #sharpBaga has an important consideration.

iOS swift application using GCDAsyncUdpSocket

I'm very new to building iOS apps.
I'm trying to make a class to receive udp multicast messages, but I can't seam to get it working at all...
class Discovery : GCDAsyncUdpSocketDelegate{
var udpSocket:GCDAsyncUdpSocket!;
init() {
udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
var e:NSErrorPointer = nil;
//Binding to port
if(!udpSocket.bindToPort(2025, error: e)){
println(e);
return;
}
//Joining multicast group
if(!udpSocket.joinMulticastGroup("239.5.6.7", error: e)){
println(e);
return;
}
//Begin recieve
if(!udpSocket.beginReceiving(e)){
println(e);
return;
}
println("UDP socket was opened!")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
println("Got data!");
}
}
Can anyone see where I'm making a mistake? I'm getting the UDP socket was opened msg, but not receiving any packages. I know they are being sent as I'm capturing them with wireshark.
The discovery is called from my view controller
class ViewController: UIViewController, CLLocationManagerDelegate {
let peer = Peer(id: NSUUID.new())
let uuid = NSUUID.new()
let discovery:Discovery = Discovery()
let locationManager = CLLocationManager()
let region = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "8DEEFBB9-F738-4297-8040-96668BB44281"), identifier: "Roximity")
let com = OconCom()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self;
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedAlways) {
locationManager.requestAlwaysAuthorization()
}
locationManager.startMonitoringForRegion(region) //Background
locationManager.startRangingBeaconsInRegion(region) //Foreground
}
Any suggestion will be helpful.
Is your receiver on the same network as the broadcaster. Generally multicast has a low TTL and can't make it too far unless the routers your packets traverse are configured to allow it.
I was running into the same issue in my custom class. Simply add #obj public to your delegate functions. Now the delegate will be called properly.

Resources