Is it possible to open my iOS app remotely? - ios

I'm pretty sure I already know what the outcome of this attempt is going to be, but before I start going through a lot of effort for nothing, I should probably just ask someone about this. Here's what I want to try:
I'm developing an iOS app in Swift, and I just wrote a PHP script to send a silent push notification to my device. In this silent notification, I'm passing along instructions to have my app delegate open the app using the UIApplication.shared.openURL(url:) method. This PHP script will only be run if a user taps a certain button on an online web page, which can only be accessed when the user is using Safari web browser on his iPhone device with my app already running in the background, so there's no chance that anyone will be able to trigger the PHP script from any other device than an iPhone which already has my app installed and running in the background. If you're wondering why I would use this workaround while I can also just use something as simple as URL schemes or deep linking, well, it's quite simple: first of all, deep linking requires the user to confirm that he wants my app to open before it actually opens. I don't want this, instead, I want my app to open automatically as soon as the button is tapped. Second, after the app is opened through deep linking or universal links, there's this really annoying breadcrumb button in the status bar, which shouldn't be there anymore after my user transitions from the web page to my app. I've tried everything to get rid of the confirmation prompt and breadcrumb button, and this is the last thing I can come up with. Is it possible to trigger the openURL method using a remote notification, even when the app is already open and running in the background?

you can't open / bring to foreground an app without user interaction

You can use silent push notification ( Pushkit ).
It will also work in background and with app terminated mode.
Once you receive silent push notification, you have to schedule local notification with interactive buttons in notification.
As per user tap, your app will be open and with tracking on didFinishLaunchingWithOption you can open specific URL
Note - Without user interaction, you would not be able to open specific URL in safari.
You can refer below code for same.
import UIKit
import PushKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
self.PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
}
}
Refer some more material for push kit integration.

Related

How do I write a UI test that launches the app with a push notification payload and verifies that you are routed to the correct view?

I'm implementing push notifications in an iOS app, and as I'm doing so, I want to write a UI test that verifies that the app does the correct thing when it is launched with a certain push notification payload (i.e. the app navigates to the correct table view and highlights the correct cell).
Can this be done? I can't seem to find anyone who has done this before or has asked this question before.
Thankful for any pointers.
With Xcode 9 you can now actually test Remote Notification handling in a UITest. I implemented that using a framework called NWPusher
I wrote a long blogpost about my implementation and added a demo project to github.
Here is a short description of what I did:
Preparation
Add NWPusher to your UITest target (I used Carthage)
Download a APN Development Certificate for your app from Apple's Dev Center
Open that certificate in Keychain and export it as p12 file
Add this file to the IUTest target
Make the deviceToken available to the UITestRunner
Write the Test
The test does the following steps:
Create a reference to the app and the Springboard
Launch the app and close it by tapping the home button (dismiss the system dialog asking for permission if it pops up)
Trigger a remote notification (using NWPusher)
Query the remote notification banner from the Springboard and tap it
Test if the remote notifications has been handled correctly by your app
Close the app and test the next type of remote notification
In my demo the different types of notifications trigger differently colored modal View Controller in the app. So my test class looks like this
import XCTest
import PusherKit
class PushNotificationUITests: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = false
}
func testPushNotifications() {
let app = XCUIApplication()
app.launchArguments.append("isRunningUITests")
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
app.launch()
// dismiss the system dialog if it pops up
allowPushNotificationsIfNeeded()
// get the current deviceToken from the app
let deviceToken = app.staticTexts.element(matching: .any, identifier: "tokenLabel").label
// close app
XCUIDevice.shared.press(XCUIDevice.Button.home)
sleep(1)
// trigger red Push Notification
triggerPushNotification(
withPayload: "{\"aps\":{\"alert\":\"Hello Red\"}, \"vcType\":\"red\"}",
deviceToken: deviceToken)
// tap on the notification when it is received
springboard.otherElements["PUSHNOTIFICATION, now, Hello Red"].tap()
// check if the red view controller is shown
XCTAssert(app.staticTexts["Red"].exists)
// dismiss modal view controller and close app
app.buttons["Close"].tap()
XCUIDevice.shared.press(XCUIDevice.Button.home)
sleep(1)
// trigger green Push Notification
triggerPushNotification(
withPayload: "{\"aps\":{\"alert\":\"Hello Green\"}, \"vcType\":\"green\"}",
deviceToken: deviceToken)
// tap on the notification when it is received
springboard.otherElements["PUSHNOTIFICATION, now, Hello Green"].tap()
// check if the green view controller is shown
XCTAssert(app.staticTexts["Green"].exists)
// dismiss modal view controller and close app
app.buttons["Close"].tap()
XCUIDevice.shared.press(XCUIDevice.Button.home)
sleep(1)
// trigger blue Push Notification
triggerPushNotification(
withPayload: "{\"aps\":{\"alert\":\"Hello Blue\"}, \"vcType\":\"blue\"}",
deviceToken: deviceToken)
// tap on the notification when it is received
springboard.otherElements["PUSHNOTIFICATION, now, Hello Blue"].tap()
// check if the blue view controller is shown
XCTAssert(app.staticTexts["Blue"].exists)
// dismiss modal view controller
app.buttons["Close"].tap()
}
}
extension XCTestCase {
func triggerPushNotification(withPayload payload: String, deviceToken: String) {
let uiTestBundle = Bundle(for: PushNotificationUITests.self)
guard let url = uiTestBundle.url(forResource: "pusher.p12", withExtension: nil) else { return }
do {
let data = try Data(contentsOf: url)
let pusher = try NWPusher.connect(withPKCS12Data: data, password: "pusher", environment: .auto)
try pusher.pushPayload(payload, token: deviceToken, identifier: UInt(arc4random_uniform(UInt32(999))))
} catch {
print(error)
}
}
func allowPushNotificationsIfNeeded() {
addUIInterruptionMonitor(withDescription: "“RemoteNotification” Would Like to Send You Notifications") { (alerts) -> Bool in
if(alerts.buttons["Allow"].exists){
alerts.buttons["Allow"].tap();
}
return true;
}
XCUIApplication().tap()
}
}
This only works on a physical device, because remote notifications do not work in the simulator.
Based on joern amazing article, I took a step forward and found a way to programmatically interact with the received notification, as it is identified by the XCTest framework as a XCUIElement.
As we can get a reference to the Sprinboard
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
For example, when putting the app on the background we could get a reference to the received notification (while it is being displayed in the top of the screen) like this:
let notification = springboard.otherElements["NotificationShortLookView"]
Allowing us to tap the notification:
notification.tap()
Pull it down to see its actions (if any available. Also by doing this could allow us to see the content of a Rich Notification):
notification.swipeDown()
Interact with its actions:
let action = springboard.buttons["ACTION BUTTON TITLE"]
action.tap()
And even interact with a Text Input notification action (in the example, by getting a reference to the notification textfield by its placeholder, which you can define in your code):
let notificationTextfield = springboard.textFields["Placeholder"]
notificationTextfield.typeText("this is a test message")
At last, you can also get a reference to the notification's close button in order to dismiss it:
let closeButton = springboard.buttons["Dismiss"]
closeButton.tap()
By being able to automate this interactions we could test, for example analytics, as described in this article.
You can send notification with this library. Unfortunately, you should add test-related code into your AppDelegate-class. I use custom preprocessor macros in separate application target (UITEST=1).
Somewhere in your code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// ...
#if UITEST
// setup listening
#endif
}

How do you replace one push notification with another on the receiving device?

I'm trying to replicate how WhatsApp signals the callee's device about an incoming call when the caller starts a call to a user who has this WhatsApp closed. According to the lock screen, the call receiver's device seems to be repeatedly receiving push notification with an interval of approximately 1 second, saying "Call from UserName". But most notably, the notifications do not pile up. It appears that every notification about an incoming call is replaced by the next such notification. And when the caller drops the call, the last incoming-call notification on the callee end is replaced by "Missed call" notification.
How can I achieve push notification replacement/deletion in this way?
You can use the "apns-collapse-id ". it will replace the Notification contents with the same id
https://developer.apple.com/documentation/usernotifications/unnotificationrequest/1649634-identifier
https://medium.com/the-guardian-mobile-innovation-lab/how-to-replace-the-content-of-an-ios-notification-2d8d93766446
WhatsApp uses silent notifications to trigger the display of local notifications. Local notifications can be replaced by the app. This was the last time I reversed engineered their process. They probably use Push Kit messages now, as they are a VoIP app.
Whatsapp, skype or any other VOIP related app uses push kit.
Use content-available = 1 in payload to make it silent push notification.
Silent push notification will invoke your app in background even if your app is in kill state ( Terminated state ), so it will allow you to schedule local notification.
Once you have payload for incoming call schedule local notification
Once you get payload for missed call, cancel incoming call local notification and schedule missed call local notification
Note - Incoming or missed call local notification object, always keeps in NSUserDefault to ensure its availability to cancel even if you restart your device.
Payload related details you can keep in localnotification.userInfo
Pushkit code
import UIKit
import PushKit
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate{
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
self. PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
// As per payload schedule local notification / cancel local notification
}
}
Kind of pushkit payload
{
"aps": {
"content-available": 1,
"screen": "IncomingCall",
"alertTitle": "Mr ...",
"alertBody": "Call from ...",
"category": "INCOMINGCALL_CATEGORY",
"data": "Any specific data you want to pass"
}
}

Send push notification to a specific test device using OneSignal push notification service

I added push notification support to my iOS app using OneSignal some time before. The app is made in Xcode with Swift.
I want to send a test push notification only to my test device(s). I the documentation I found the following manual: How do I send a notification to a single user?
I managed to create the segment but I don't know where to put this peace of code: OneSignal.sendTag("is_test", "true")
Does anybody know where I have to put this piece of code to make it working as I described above?
I uploaded my code here: https://codeshare.io/DxcNn
Thanks,
David.
Update:
OneSignal now also supports to set a device as test device without doing something in the code. You can also download your own app from App Store and use it as test device. Just select you device from devices list one OneSignal and mark it as test device. You can find your device in the list by model, version and/or time added.
The sendTag method is from the device sdk. In your case iOS.
https://documentation.onesignal.com/docs/ios-native-sdk#section--sendtag-
You should do this anytime after initWithLaunchOptions in the app delegate. Updated code based on comments
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
let oneSignal = OneSignal(launchOptions: launchOptions, appId: "here_is_my_onesignal_app_id") { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
// Check for and read any custom values you added to the notification
// This done with the "Additonal Data" section the dashbaord.
// OR setting the 'data' field on our REST API.
if let customKey = additionalData["customKey"] as! String? {
NSLog("customKey: %#", customKey)
}
}
}
OneSignal.defaultClient().sendTag("is_test", value: "true")
// Override point for customization after application launch.
return true
}

CLCircularRegion and wake up app

In application we have mechanism like native Reminder app in iOS with firing notifications when user enter or exit in some region.
But two devices behave differently (5 and 5s) in same time. All devices have enable notifications, and allow use locations.
Two devices have a some "travel" and in the route created 10 points. First device (5) when came to finish received only 6 notifications, (5s) don't receive any notification.
But my question is how I can know when my app is restart in background or continue working. Because, all log in app I redirect into a file, and after download container and analyze what happened in app in travel time.
I noticed app restart in same times when device is enter to region and my log marks fired in the file but notifications don't receive. This is happended when app try to get some information from web service in didFinishLaunchingWithOptions
And maybe this is problem. How to know distinguish restart app or continue working. Thx.
Are you checking UIApplicationLaunchOptionsLocationKey in didFinishLaunchingWithOptions similar to (sorry, Swift is what I have now):
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsLocationKey] != nil {
// app was launched in response to incoming location event
}
}
Additionally, if you're not already doing this you may need to create notifications differently if app is in background:
// Show an alert if application is active
if UIApplication.sharedApplication().applicationState == .Active {
if let message = notefromRegionIdentifier(region.identifier) {
if let viewController = window?.rootViewController {
showSimpleAlertWithTitle(nil, message: message, viewController: viewController)
}
}
}
else {
// Otherwise present a local notification:
let notification = UILocalNotification()
notification.alertBody = notefromRegionIdentifier(region.identifier)
notification.soundName = "Default";
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}

How to get notification additionaldata(payloadData) that was at AppDelegate' didFinishLunchingWithOptions if the user didn't open the notification

I am currently using OneSignal for notification service to my app.I really need a help with accessing notification additionaldata(payload data) from AppDelegate inside didFinishLunchingWithOption where OneSignal API can give me like this.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var data : [NSObject : AnyObject]!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let oneSignal = OneSignal(launchOptions: launchOptions, appId: "48755d3d-abc0-4bac-8f71-095729bb3a65") { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
self.data = additionalData
print(self.data)
}
}
oneSignal.enableInAppAlertNotification(true)
return true
}
}
but,I can only get the data if user click notification when appear or open it from notification center.So,if user neglect that notification without tapping when appear or without swiping or tapping from notification center,how do i get the additional data?
Actually,I want to store all payload data every time it comes into my device at realm database and fetch the data from my server according to that payload data.
You should use application(_:didReceiveRemoteNotification:fetchCompletionHandler:).
If you have enabled remote notifications background mode most of your notifications will be delivered even if the app is not running in the foreground. The only caveat being that the app must have been launched (since notifications are being pushed the user has done this) and it mustn't have been force-quit by the user.
More info in Apple's docs about that specific method.
Or in the "Local and Remote Notification Programming Guide's" notification handling chapter
You can extract all the payload in did finishLaunching by following method..
Let data = launchOptions.objectForKey(UIApplicationLaunchOptionsUIApplicationLaunchOptionsRemoteNotificationUIApplicationLaunchOptionsUIApplicationLaunchOptionsRemoteNotificationKey)

Resources