messageLostHandler not called on Google Nearby iOS when out of range - ios

I don't really understand when messageLostHandler is triggered on the subscriptionWithMessageFoundHandler method.
This is my code:
func suscribeToNearbyDevices(myUserId: String){
subscription = messageMgr?.subscription(messageFoundHandler: { (message: GNSMessage?) in
if let incomingMessage = message, let content = incomingMessage.content {
if let userIdEncoded = String(data: content, encoding: String.Encoding.utf8) {
NotificationCenter.default.post(name: Notification.Name(rawValue: CommunicationVariables.newUserNotificationKey), object: nil,
userInfo:userIdEncoded)
}}, messageLostHandler: { (message: GNSMessage?) in
if let incomingMessage = message, let content = incomingMessage.content {
if let userIdEncoded = String(data: content, encoding: String.Encoding.utf8) {
NotificationCenter.default.post(name: Notification.Name(rawValue: CommunicationVariables.exitUserNotificationKey), object: nil,
userInfo: [CommunicationVariables.userIdNotificationField:userIdEncoded])
}
}
}, paramsBlock:{(params:GNSSubscriptionParams?) in
guard let params = params else { return }
params.strategy = GNSStrategy(paramsBlock: { (params: GNSStrategyParams?) in
guard let params = params else { return }
params.allowInBackground = true
})
})
}
I have two iphones, If I have the two apps on the foreground, they can see each other. When I press home in one, the messageLostHandler is triggered, but if I walk out of range (like outside-of-the-house-out-of-range) the messageLostHandler is never triggered.
Why? What cause the messageLostHandler to be triggered?
Thanks!

I see that your subscription is configured to work in the background, but is the publication also configured for background? If not, then when you press the Home button on the publishing device, the app is backgrounded, causing the publication to be disabled, and the subscriber receives a call to the messageLostHandler block.
Regarding the out-of-range problem: BLE (Bluetooth Low Energy) works at a very high range (up to 100m outdoors, and less when there are obstacles). So you have to walk very far for the devices to be completely out of range. An alternative is to place one of the devices inside a fully closed metal box (a faraday cage), which blocks all radio transmissions. Within 2-3 minutes, the subscriber should receive a call to the messageLostHandler block. (Incidentally, the 2-3 minute timeout is rather long, and we're have a discussion of whether we should shorten it.)
Let me know if you need more help figuring out what's going on.
Dan

Related

How to set timeout for CallKit incoming call UI

I'm developing Video call feature in my app and using CallKit to be the incoming call UI. And i found an edge case like:
User A: call user B
User B:
The app is in terminated state. And CallKit incoming UI shows for user B.
User B doesn't notice (since silent mode) and let the incoming UI keep showing
User A doesn't end the call; or for some reasons, user A lost internet or quit the app (therefore my server doesn't send Cancel command via VoIP notification) so there's no way for user B to end the incoming UI until user B touch Cancel or Answer
So is there any way to set a timeout for an incoming UI of CallKit? For example: If i set the timeout is 60 seconds then the incoming UI just shows in 60 seconds the auto dismiss.
Here is my code to show the incoming UI:
let update = CXCallUpdate()
update.localizedCallerName = callerName
update.remoteHandle = CXHandle(type: .phoneNumber, value: myID)
update.hasVideo = true
self.provider.reportNewIncomingCall(with: uuid, update: update) { [weak self] error in
guard let self = self else { return }
if error == nil {
// Store my calls
let call = Call(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
}
}
Any help would be greatly appreciated. Thanks.
There's no way to set a timeout using the CallKit API. You just have to implement it by yourself.
In the class where you handle all the call logic, you should add something like the following:
private func startRingingTimer(for call: Call)
{
let vTimer = Timer(
timeInterval: 60,
repeats: false,
block: { [weak self] _ in
self?.ringingDidTimeout(for: call)
})
vTimer.tolerance = 0.5
RunLoop.current.add(vTimer, forMode: .common)
ringingTimer = vTimer
}
private func ringingDidTimeout(for call: Call)
{
...
self.provider.reportCall(with: call.uuid, endedAt: nil, reason: .unanswered)
...
}
Then you should call startRingingTimer(for: call) as soon as you successfully reported a new incoming call; and, of course, you have to invalidate the timer if the user answers the call.

CFNotificationCenter repeating events statements

I have been working on an enterprise iOS/Swift (iOS 11.3, Xcode 9.3.1) app in which I want to be notified if the screen changes (goes blank or becomes active) and capture the events in a Realm databse. I am using the answer from tbaranes in detect screen unlock events in IOS Swift and it works, but I find added repeats as the screen goes blank and becomes active:
Initial Blank: a single event recorded
Initial Re-activiation: two events are recorded
Second Blank: two events are recorded
Second Re-act: three events are recorded
and this cycle of adding an additional event recording each cycle.
This must be something in the code (or missing from the code) that is causing an additive effect but I can’t find it. And, yes, the print statements show the issue is not within the Realm database, but are actual repeated statements.
My code is below. Any suggestions are appreciated.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
Unmanaged.passUnretained(self).toOpaque(), // observer
displayStatusChangedCallback, // callback
"com.apple.springboard.hasBlankedScreen" as CFString, // event name
nil, // object
.deliverImmediately)
}
private let displayStatusChangedCallback: CFNotificationCallback = { _, cfObserver, cfName, _, _ in
guard let lockState = cfName?.rawValue as String? else {
return
}
let catcher = Unmanaged<AppDelegate>.fromOpaque(UnsafeRawPointer(OpaquePointer(cfObserver)!)).takeUnretainedValue()
catcher.displayStatusChanged(lockState)
print("how many times?")
}
private func displayStatusChanged(_ lockState: String) {
// the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification
print("Darwin notification NAME = \(lockState)")
if lockState == "com.apple.springboard.hasBlankedScreen" {
print("A single Blank Screen")
let statusString = dbSource() // Realm database
statusString.infoString = "blanked screen"
print("statusString: \(statusString)")
statusString.save()
return
}

iOS turn based match, push notifications not working, GKTurnBasedEventListener functions not called

In my iOS turn based match, I'm trying to receive notifications and to get the
public func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool)
to be called, with no success.
I register my view model to the local player
GKLocalPlayer.localPlayer().register(self)
and I would expect that to fire after the other player executes
func endTurn(withNextParticipants nextParticipants: [GKTurnBasedParticipant], turnTimeout timeout: TimeInterval, match matchData: Data, completionHandler: ((Error?) -> Swift.Void)? = nil)
but no success.
If I force a reload of the matchData then I will get the data the second player just submitted. So the endTurn works correctly.
Is there something I'm doing wrong?
Update:
So I create a new project, copied all my files over,
in the capabilities only Game Center was enabled.
When developing it was working perfect, I had two devices attached (with different apple IDs). Notifications were working and Turnbasedlistener was firing.
As soon as I released it for internal testing it stopped working!!!
I had very similar issue. My solution was to manually recheck my status while waiting for my turn.
FIrst, I defined global variable var gcBugTimer: Timer
In endTurn(withNextParticipants:turnTimeOut:match:completionHan‌​dler:) completion handler:
let interval = 5.0
self.gcBugTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(self.isMatchActive), userInfo: nil, repeats: true)
self.gcBugTimer.tolerance = 1.0
Code above also should be called in case when a player is joying to a new match and other player in a turn.
Then timer method:
func isMatchActive() {
// currentMatch - global variable contains information about current match
GKTurnBasedMatch.load(withID: currentMatch.matchID!) { (match, error) in
if match != nil {
let participant = match?.currentParticipant
let localPlayer = GKLocalPlayer.localPlayer()
if localPlayer.playerID == participant?.player?.playerID {
self.player(localPlayer, receivedTurnEventFor: match!, didBecomeActive: false)
}
} else {
print(error?.localizedDescription ?? "")
}
}
}
And I add following code at the very beginning of player(_:receivedTurnEventFor:didBecomeActive):
if gcBugTimer != nil && gcBugTimer.isValid {
gcBugTimer.invalidate()
}
What ended up working for me, was to test on an actual device, rather than in simulator. The receivedTurnEvents function doesn't seem to work in simulator.
Grigory's work around is great for testing with simulator.

Schedule and handle local notifications in iOS 10

I've been working on one of my project where I allow users to schedule multiple notifications at their desired time.
iOS 10 gave us the ability to use a DateComponents as the fire date for our notification but I'm kind of lost as to how I'm supposed to schedule and handle multiple notifications.
Each notification needs its own request which then in turn needs its own identifier else you cannot create multiple notifications.
I figured I had to work with the identifier to schedule and handle notifications, but now my code is such a mess that I've been seriously asking myself if there hasn't been an easier way of doing this.
According to my data model the notification unique identifier is composed of :
The id of my model
A number that increments each time a new notification is created
The above is separated by an underscore
So for example if I had to schedule 10 notifications for the object with the id 3 it would look like this : 3_1, 3_2, 3_3...
Every time I receive a notification I loop trough the received notifications to update my UI. And when the user desires to delete the received notifications for a specific model, I loop through the received notification's unique identifiers by checking the identifiers that starts with the same ID.
I don't really see how I could manage to do it otherwise because according to the documentation, the only way of deleting a delivered notification is by using the identifier : UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers:)
The thing is, it's creating all sort of problems and while I could easily correct them it looks very hacky. I'm not really proud of what I've done and I am looking for more clever ways of getting around it. I intentionally did not post any code because that's not the problem. The approach is the real problem.
I'm asking here because the UserNotifications framework is pretty new and I haven't been able to find ressources taking about this subject.
Any idea ? Thanks in advance.
Edit : Here's some code.
#available(iOS 10.0, *)
func checkDeliveredAndPendingNotifications(completionHandler: #escaping (_ identifierDictionary: Dictionary<String, Int>) -> ()) {
var identifierDictionary:[String: Int] = [:]
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
for notification in notifications {
let identifierArraySplit = notification.request.identifier.components(separatedBy: "_")
if identifierDictionary[identifierArraySplit[0]] == nil || identifierDictionary[identifierArraySplit[0]]! < Int(identifierArraySplit[1])! {
identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
}
}
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (requests) in
for request in requests {
let identifierArraySplit = request.identifier.components(separatedBy: "_")
if identifierDictionary[identifierArraySplit[0]] == nil || Int(identifierArraySplit[1])! > identifierDictionary[identifierArraySplit[0]]! {
identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
}
}
completionHandler(identifierDictionary)
})
}
}
#available(iOS 10.0, *)
func generateNotifications() {
for medecine in medecines {
self.checkDeliveredAndPendingNotifications(completionHandler: { (identifierDictionary) in
DispatchQueue.main.async {
self.createNotification(medecineName: medecine.name, medecineId: medecine.id, identifierDictionary: identifierDictionary)
}
})
}
}
#available(iOS 10.0, *)
func createNotification(medecineName: String, medecineId: Int identifierDictionary: Dictionary<String, Int>) {
let takeMedecineAction = UNNotificationAction(identifier: "TAKE", title: "Take your medecine", options: [.destructive])
let category = UNNotificationCategory(identifier: "message", actions: [takeMedecineAction], intentIdentifiers:[], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
let takeMedecineContent = UNMutableNotificationContent()
takeMedecineContent.userInfo = ["id": medecineId]
takeMedecineContent.categoryIdentifier = "message"
takeMedecineContent.title = medecineName
takeMedecineContent.body = "It's time for your medecine"
takeMedecineContent.badge = 1
takeMedecineContent.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
var takeMedecineIdentifier = ""
for identifier in identifierDictionary {
if Int(identifier.key) == medecineId {
let nextIdentifierValue = identifier.value + 1
takeMedecineIdentifier = String(medecineId) + "_" + String(nextIdentifierValue)
}
}
let takeMedecineRequest = UNNotificationRequest(identifier: takeMedecineIdentifier, content: takeMedecineContent, trigger: trigger)
UNUserNotificationCenter.current().add(takeMedecineRequest, withCompletionHandler: { (error) in
if let _ = error {
print("There was an error : \(error)")
}
})
}
In the checkDeliveredAndPendingNotifications method, I loop through all the pending and delivered notifications, so later on I can create an identifier that does not exist already. I haven't found another way to generate unique identifiers for each notification.
As most of the work is done on another async queue, I've also created a completion handler when he has finished its job. I then call the createNotification method on the main thread (because I'm using Realm, and I'm obliged too do this) which should create a notification.
The problem here is the func add(UNNotificationRequest, withCompletionHandler: (Error?) -> Void)? = nil) method which is also doing work asynchronously. So when I go back to my loop in generateNotifications the checkDeliveredAndPendingNotifications return incorrect data. Well not incorrect it's just that the notification hasn't been created yet...
I'm a total noob with threading and I'm stuck with these kind of operations and I don't know where to go. I'm not sure I'm approaching the problem the right way.
You can get all notification and set/Delete as you need. Have a look on this accepted answer for both Objc c and swift.
How to schedule a local notification in iOS 10 (objective-c)

Layer synchronizeWithRemoteNotification not getting called in iOS

I'm building an iOS app and making use of layer.
According to the documentation, when a push notification for a message comes in, you should not immediately switch to the conversation because the message might not have finished synching. So you call a synchronise method and pass it a completion function as follows:
self.layerClient!.synchronizeWithRemoteNotification(userInfo, completion: { (conversation, message, error) in
if (conversation != nil || message != nil) {
NSNotificationCenter.defaultCenter().postNotificationName(CXOConstants.Notifications.BadgeChatTabNotification.rawValue, object: nil, userInfo: userInfo)
let msgPart = message?.parts.first
if msgPart?.MIMEType == "text/plain"{
let msgText = String(data: msgPart!.data!,encoding: NSUTF8StringEncoding)
debugPrint("msgText:: \(msgText)")
} else {
debugPrint("The user sent an image")
}
completionHandler(.NewData)
} else {
completionHandler(.Failed)
}
})
Problem is, this method never gets called. I have tried implementing the same logic outside of the synchronizeWithRemoteNotification method, but when I switch to the conversation, it takes a couple seconds for the message to appear in the conversation.
Could use some help here figuring out why the method isn't getting called.
Thanks :)

Resources