Initiate call from apple watch - ios

I am developing an apple watch extension for an already existing application.
My watch application has contact us section where customer can call toll free number.
My question is how can i start call in apple watch on click of button rather than keeping my application in foreground and starting the call.
Currently i am using this code to start call
+ (void)callWithNumberWithoutPrompt:(NSString *)phoneNo {
NSString *prefixedMobileNumber = [phoneNo hasPrefix:#"+"]?phoneNo:[NSString stringWithFormat:#"+%#",phoneNo];
NSString *phoneNumber = [#"tel://" stringByAppendingString:prefixedMobileNumber];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
}

Note: this was true on WatchOS 1, and may have changed with the release of WatchOS 2.
From Ray Wenderlich WatchKit FAQ:
Can third-party apps make phone calls from a watch app?
No. There is no public API that lets you initiate a phone call directly from a WatchKit extension. Since the companion iPhone app can’t be brought to the foreground either, the system silently ignores all phone call or openURL: requests from the companion iPhone app.

Using WatchConnectivity in WatchOS2 you can send data from the watch app back to the parent app and then attempt to initiate the call from the parent app. Here is an example:
//App Delegate in iOS Parent App
#pragma mark Watch Kit Data Sharing
-(void)initializeWatchKit{
if ([WCSession isSupported]){
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message{
DLog(#"%#", message);
[self callRestaurantWithNumber:[NSString formattedPhoneNumber:[message valueForKey:#"phone_number"]]];
}
-(void)callRestaurantWithNumber:(NSString *)formattedPhoneNumber{
[[UIApplication sharedApplication]
openURL:[NSURL URLWithString:[NSString stringWithFormat:#"tel:%#",
formattedPhoneNumber]]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self initializeWatchKit];
return YES;
}
Now inside the watchKit extension you can send the data back to the parent application like this:
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
let defaultSession = WCSession.defaultSession()
defaultSession.delegate = self
defaultSession.activateSession()
if defaultSession.reachable == true {
let phoneNumberDict = [ "phone_number": "123-456-7890"]
defaultSession.sendMessage(phoneNumberDict, replyHandler: nil, errorHandler: { (error) -> Void in
print("THERE WAS AN ERROR SENDING DATA TO THE IOS APP: \(error.localizedDescription)")
})
}
}
}
However the one limitation I've encountered with this approach is the parent application needs to be open to actually receive the data and make the call. The documentation seems to state that the application will be opened in the background when you send a message from the watch to the ios parent app. However in my testing so far, on both real devices (watch and iphone) and simulators, the parent app only receives the data when the parent ios app is open and foregrounded. Even when the ios parent app is in a background state it still does not seem to initiate the call. I'm running this on watch os2 and iOS 9.0.2.
From another post that also appears to be running into the same problem.
Another watchKit SO post
Link to the Apple documentation too:
Apple Doc sendMessage:

Related

Handling Push Notifications when App is Terminated

When my App is not running and receives a Push Notification, if I click on that notification, the App is launched - but then it doesn't prompt the user with the Alert-View I set up, asking them whether they want to view the Notification's contents or not. It just launches, and sits there.
The Push Notifications do work perfectly when the App is running - either as the Active app or while in the background - but nothing works correctly when the app is not running.
I tried logging-out the launchOptions NSDictionary in application: didFinishLaunchingWithOptions: to see what load its bringing - but it comes up as "(null)". So It basically contains nothing - which doesn't make sense cause shouldn't it contain the Notification's load?
Anybody have any ideas how to make Push Notifications work when they arrive while the App was NOT running?
I mean how to handle the Push notifications when the App is in not running state. What if, if you receive many notifications & you did not open the app, neither did you tap the system's notification panel. How are you preserving those push for a later retrieval.
1) When application is running in background and When application is running in foreground
application:didReceiveRemoteNotification: method will called as below.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if (application.applicationState == UIApplicationStateInactive)
{
// opened from a push notification when the app was on background
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
else if(application.applicationState == UIApplicationStateActive)
{
// a push notification when the app is running. So that you can display an alert and push in any view
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
}
2) When application is not launched (close) then application:didFinishedLaunchingWithOptions method will called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (launchOptions != nil)
{
// opened from a push notification when the app is closed
NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (userInfo != nil)
{
NSLog(#"userInfo->%#", [userInfo objectForKey:#"aps"]);
}
}
else
{
// opened app without a push notification.
}
}
3) There is no way to remove a specific notification as of. The way to remove all the notifications from your app so they don't show in the Notification Center when the user opens the app from one of them, is to set the app badge to 0.
As per your question, there is no way to hold all the notification when you open the app, better you call an api to get all notification as per time stamp from your back end/server that's how Facebook does.
You can retrieve notifications delivered to your app by using the getDeliveredNotifications(completionHandler:) method. Note that this only returns notifications currently displayed in the Notification Center and not the ones that have been manually cleared by the user.
UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
// notifications: An array of UNNotification objects representing the local
// and remote notifications of your app that have been delivered and are still
// visible in Notification Center. If none of your app’s notifications are
// visible in Notification Center, the array is empty.
// As said in the documentation, this closure may be executed in a background
// thread, so if you want to update your UI you'll need to do the following:
DispatchQueue.main.sync { /* or .async {} */
// update UI
}
}
The app does not process push notification in the background, what it really does the OS is wake up the app once you press in the notification. You can catch this moment in the following way:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]) {
// Your app has been awoken by a notification...
}
}
There is no way of handling this on application end.
You would need to maintain the unread badge count at the server.
When the app is killed the badge value is updated from the server.
So when you open the application any time , you would need to call a web service to get the required notifications and update the badges of tabbar(if used).
Perform actions after notification is received in the terminated state - iOS 13 - Scene Delegate
Recently I came across an issue where I received remote push notification when my app was in the terminated state. In the latest iOS versions Scene delegate is responsible to handle view life cycle methods rather than App Delegate.
Older iOS versions - Handled by App Delegate
When the app is terminated and remote push notification is received the payload is reflected in the didfinishLaunchingWithOptions method of App delegate. Using the launch parameter of this method it’s possible to get the payload data and perform any interaction.
New iOS version - Handled by Scene Delegate
Similarly when the app is terminated and remote push notification is received the payload is reflected in the scene(willConnectTo session) of the scene delegate. Using the connectingOption parameter of this method it’s possible to get the payload data and perform any interaction.
Hint: To perform any interaction or pass this payload to another view controller once the view controller is set as root view controller keep a delay of some seconds to pass the data.
Example code:
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
if defaults.string(forKey: UserDefaultsKeys.TenantID.rawValue) != nil && connectionOptions.notificationResponse != nil {
let rootViewController = UINavigationController(rootViewController: DashboardVC())
window?.rootViewController = rootViewController
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NotificationCenter.default.post(
name: .passApnsDataToDashboardNotification,
object: nil,
userInfo: connectionOptions.notificationResponse?.notification.request.content.userInfo)
}
}
You can display the alert after launching a previously terminated app from a notification like this:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
// display the alert
}
For your use case, a BGTask is the best solution which can call the server and get the data and update your database, so that next time, you have available data immediately.
BGTask scheduling is done by OS based on 7 factors (like app usage, batter life, rate limit etc), If your app does not execute any BGTask (you can check easily by saving it to UserDefalts), you can fetch the server data explicitly when the app is live.
Just FYI:
You can configure and Use UserNotificaions to receive a local/remote notification
https://developer.apple.com/documentation/usernotifications?language=objc
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)(void))completionHandler
https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649501-usernotificationcenter?language=objc

Keeping my WatchKit complications up to date when not running

I'm working on the WatchKit Extension of my app, and have some issues with complications.
I have a complication that displays a given total amount, which depends of what the user is doing on the iOS app. When the WatchKit Extension is running, the iOS app updates the watch app context using the -[WCSession updateApplicationContext:] method. It works fine, and then in the ExtensionDelegate of my Watch app, I manually update the complication with the new data.
But this is OK only when the extension is running (if it's not, it won't get the application context until the next launch).
So I edited my code to send the complication data directly to the Watch when user changed something in the iOS app, using the -[WCSession transferCurrentComplicationUserInfo:] method (it's written in the documentation that the ExtensionDelegate should be woken up to receive the user info in background).
I've implemented the -session:didReceiveUserInfo: method of the ExtensionDelegate to update the complication when it received data from the iOS app, but it doesn't work when the extension is not running... (and I don't know if it ever receives the user info as I can't log it)
How should I do to keep my complications up to date even when the extension is not running??
Thanks
PS: I'm using the Watch Simulator, and to "close" the extension I just Reboot the Watch (from the Hardware menu)
Edit: I managed to log out statements when the app is not running (by opening the Watch Simulator system log), and I get these lines when the iOS send a new complication user data to the watch extension:
Oct 18 18:08:11 pc16 WatchApp Extension[26615]: Extension received
request to wake up for complication support.
Oct 18 18:08:11 pc16 assertiond[26585]: assertion failed: 15A284 13S343: assertiond + 15398 [B48FCADB-A071-3A46-878B-538DC0AFF60B]: 0x1
So the watch receives well the user info dictionary, but seems to fail waking up the extension...
Edit 2: here is the part of code in the ExtensionDelegate that should receive the complication user info (but which is not called when the app is not running):
- (void) session: (WCSession *)session didReceiveUserInfo: (NSDictionary *)userInfo
{
NSLog(#"session:didReceiveUserInfo: %#", userInfo);
NSString *userInfoType = userInfo[KHWASessionTransferUserInfoType];
NSDictionary *userInfoContents = userInfo[KHWASessionTransferUserInfoContents];
// Complication Data
if ([userInfoType isEqualToString:KHWASessionTransferUserInfoTypeComplicationData]) {
// Store the complication data into user defaults
[[NSUserDefaults standardUserDefaults] setValue:userInfoContents[KHWAComplicationTotalBalance] forKey:KHWAComplicationTotalBalance];
[[NSUserDefaults standardUserDefaults] synchronize];
// And refresh the complications
CLKComplicationServer *complicationServer = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in complicationServer.activeComplications) {
[complicationServer reloadTimelineForComplication:complication];
}
}
}
Edit 3: the WCSession is set in the extension delegate applicationDidFinishLaunching method:
- (void) applicationDidFinishLaunching
{
// Setup the WatchConnectivity session
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
[...]
}
Wow, I finally resolved the issue!
It seems that, even if I didn't see it in the log files (see my last comment), the init method of WCExtensionDelegate is well called when waking up the app.
So I just had to move the WCSession setting bloc into the init method :
- (id) init
{
if (self = [super init]) {
// Setup the WatchConnectivity session
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
return self;
}
And for the while it works fine...

iOS: How to get all of the push notification userinfo while your App is completely closed and reopened?

I am not sure if this is possible, but I need to grab all of the push notification userinfo when the user opens up the App. I can get all of the push notification userinfo when the App is opened or in the background, but not when the App is completely closed. Any way around this? The code below is how I get the userInfo currently.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
id data = [userInfo objectForKey:#"data"];
NSLog(#"data%#",data);
}
Unfortunately, it's not currently possible client side with that method to query old notifications that have occurred while the app was completely closed. See this question: didReceiveRemoteNotification when in background.
A way around it is to keep track of which notifications you send from your server per user. When didReceiveRemoteNotification: is called, you can take that notification and compare it against the server's messages for the current user. If one of them matches, mark it some way on the server. That way, if there are messages sent when your app is backgrounded, you can query for messages that haven't been marked from the server and get all 'missed' notifications.
The method you are implementing cannot handle both cases. See the "Local and Push Notification Programming Guide":
If your app is frontmost, the application:didReceiveRemoteNotification: or application:didReceiveLocalNotification: method is called on its app delegate. If your app is not frontmost or not running, you handle the notifications by checking the options dictionary passed to the application:didFinishLaunchingWithOptions: of your app delegate...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Notifications
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(userInfo){
//open from notification message
}
return YES;
}
You can add this code to your AppDelegate's applicationWillEnterForeground method:
-(void)applicationWillEnterForeground:(UIApplication *)application {
// this method is called when staring an app that was closed / killed / never run before (after applicationDidFinishLaunchingWithOptions) and every time the app is reopened or change status from background to foreground (ex. returning from a mobile call or after the user switched to other app and then came back)
[[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
NSLog(#"AppDelegate-getDeliveredNotificationsWithCompletionHandler there were %lu notifications in notification center", (unsigned long)[notifications count]);
for (UNNotification* notification in notifications) {
NSDictionary *userInfo = notification.request.content.userInfo;
if (userInfo) {
NSLog(#"Processed a notification in getDeliveredNotificationsWithCompletionHandler, with this info: %#", userInfo);
[self showPushNotificationInAlertController:userInfo]; // this is my method to display the notification in an UIAlertController
}
}
UIApplication.sharedApplication.applicationIconBadgeNumber = 0;
}];
}
}
Remove this line from the method application didFinishLaunchingWithOptions: if you had included it there, because it clears the badge number and also all notifications in notifications center:
UIApplication.sharedApplication.applicationIconBadgeNumber = 0;
This is currently working in iOS 12, hadn't had the chance to test it in earlier versions.

how can I open a new application using [[UIApplication sharedApplication] openURL:] from a foreground app

I tried to open facebook app using this
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"fb://"]];
when my app is running in foreground , it works fine but if my app is in background it didn't work .
help me out .
I'm very new in programming
You are think impossible task. Think and first ask your self what are you trying.
Your app in to background it means (minimized). then where to trigger for calling open Facebook Native app method.
If app in background then you can't have any event for handle your app event from iPhone screen.
That bellow code for open Fb native app from app. but from background it out of logic think. that not relevant at all.
you can open facebook app using this bellow :-
- (IBAction)OpenFB: (id) sender
{
NSURL* facebookURL = [NSURL URLWithString: #"fb://profile/157893597658332"];
UIApplication* app = [ UIApplication sharedApplication ];
if( [ app canOpenURL: facebookAppURL ] ) {
[ app openURL: facebookAppURL ];
} else {
// url wrong
}
}
Here it is very nice Article
http://www.plungeinteractive.com/blog/2012/12/31/open-facebook-and-twitter-native-apps-from-your-ios-appgame/
UPDATE:-
I dont think this is suitable for your requirement but using Custom URL Scheme. you can able to Open your app from from safari browser or other app. just setting URL type--> setting URL schemes like bellow
and then try like i did in bellow image that open your app from browser:-
You cannot launch another app from the background mode because that would be pre-empting the user's current task without user interaction. There is only one application that is permitted to pre-empt the user's current action and that is the phone in the case of an incoming call.
What you could do is display a local notification and if the user actions that notification to launch your app - placing it back into foreground - then you could launch Facebook.
UILocalNotification* localNotification = [[UILocalNotificationalloc] init];
localNotification.fireDate = nil;
localNotification.alertBody = #"I want to launch Facebook";
localNotification.timeZone = nil;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
then in your app delegate -
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Handle launching from a notification
UILocalNotification *locationNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (locationNotification) {
// Open Facebook
[application openURL:[NSURL URLWithString:#"fb://"];
}
return YES;
}
But if the user chooses not to action your notification there is nothing you can do.

How to get a call event using CTCallCenter:setCallEventHandler: that occurred while the app was suspended?

The documentation for CTCallCenter:setCallEventHandler: states that:
However, call events can also take place while your application is
suspended. While it is suspended, your application does not receive
call events. When your application resumes the active state, it
receives a single call event for each call that changed state
The part relevant to this question is
When your application resumes the active state, it receives a single
call event for each call that changed state
Implying the app will receive a call event for a call that took place in the past while the app was suspended. And this is possible according to the answer to this question: How does the Navita TEM app get call log information?
My question is: if my app is suspended and a call takes place, then when my app resumes the active state how can it retrieve the call event for the call that took place?
I have tried many, many code experiments but have been unable to retrieve any call information when my app resumes the active state.
This is the most simplest thing I have tried:
1) Create a new project using the Xcode single view application template.
2) Add the code shown below to didFinishLaunchingWithOptions
3) Launch the app
4) Task away from the app
5) Make a call from another device, answer the call, hang up the call from either device
6) Bring the app back to the foreground thus resuming the active state.
The code to register for call events is:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler:^(CTCall *call)
{
NSLog(#"Event handler called");
if ([call.callState isEqualToString: CTCallStateConnected])
{
NSLog(#"Connected");
}
else if ([call.callState isEqualToString: CTCallStateDialing])
{
NSLog(#"Dialing");
}
else if ([call.callState isEqualToString: CTCallStateDisconnected])
{
NSLog(#"Disconnected");
} else if ([call.callState isEqualToString: CTCallStateIncoming])
{
NSLog(#"Incomming");
}
}];
return YES;
}
With this code I am able to get call events if the app is in the foreground when the call occurs. But if I task away from the app before making the call then I am unable to get a call event when my app next resumes the active state - as it states it should in the Apple documentation.
Other things I have tried:
1) The documentation states that the block object is dispatched on the default priority global dispatch queue, so I have tried placing the registration of setCallEventHandler within dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{})
2) Calling setCallEventHandler: in appBecameActive instead of didFinishLaunchingWithOptions
3) Adding background abilities to the app - via beginBackgroundTaskWithExpirationHandler and/or location updates using startUpdatingLocation or startMonitoringForSignificantLocationChanges.
4) Various combinations of the above.
The bounty will be awarded once I get code running on my device which is able to get call events that took place while the app was suspended.
This is on iOS 7.
I've found a solution but I have no idea why it's working. Only thing I can think of is a bug in GCD and/or CoreTelephony.
Basically, I allocate two instances of CTCallCenter like this
void (^block)(CTCall*) = ^(CTCall* call) { NSLog(#"%#", call.callState); };
-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
callCenter1 = [[CTCallCenter alloc] init];
callCenter1.callEventHandler = block;
callCenter2 = [[CTCallCenter alloc] init];
callCenter2.callEventHandler = block;
return YES;
}
Similar Code in Swift:
func block (call:CTCall!) {
println(call.callState)
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Declare callcenter in the class like 'var callcenter = CTCallCenter()'
callcenter.callEventHandler = block
return true
}
To test this I made a call, answered it and then hanged up it while app was in background. When I launched it I received 3 call events: incoming, connected, disconnected.
In my case, I was working on an enterprise app which doesn't need to be approved by Apple's app market - so if you develop an enterprise app this solution is for you.
Also, the chosen answer didn't work while the app is the background.
The solution is simple, basically you just need to add 2 capabilities (VOIP & Background fetch) in the Capabilities tab:
Your project target -> Capabilities -> Background Modes -> mark Voice over IP & Background fetch
Now, your app is registered to the iOS framework calls "delegate" so the OP code snip solution:
[self.callCenter setCallEventHandler:^(CTCall *call)
{
NSLog(#"Event handler called");
if ([call.callState isEqualToString: CTCallStateConnected])
{
NSLog(#"Connected");
}
else if ([call.callState isEqualToString: CTCallStateDialing])
{
NSLog(#"Dialing");
}
else if ([call.callState isEqualToString: CTCallStateDisconnected])
{
NSLog(#"Disconnected");
} else if ([call.callState isEqualToString: CTCallStateIncoming])
{
NSLog(#"Incomming");
}
}];
Would defiantly work and you will get notifications even if your app is in the background.

Resources