Does WCSessionDelegate get an initial sessionReachabilityDidChange after activating WCSession? - watchos-2

When setting up a WCSession in a watchOS app, does the WCSessionDelegate's sessionReachabilityDidChange: method always get invoked immediately after calling activateSession? From my testing this seems to be true but I am not finding any confirmation of this in documentation.
I ask because if I can rely on sessionReachabilityDidChange: being called immediately after activating the session, I can remove some redundant code from applicationDidBecomeActive that checks for a reachable session and sends some initial messages to the iPhone app.

Why not just call it yourself after you are done doing all your set up? That way you don't rely on any undocumented behavior, yet you don't have to duplicate code in two places

Related

Can't call INPauseWorkoutIntent without home button

I have implemented INPauseWorkoutIntent, INStartWorkoutIntent or INResumeWorkoutIntent in my app in IOS 12 and XCode 10. I can use the commands correctly with using home button to open Siri but what I need is Hand off. I mean, I just want to say "Pause workout" and it should pause. I also don't want to use watch. How to achieve that, what am I missing?
Note: - (id)handlerForIntent:(INIntent *)intent not called anyways.
I believe you already have implemented resolve, confirm, and handler functions comforting to respective protocols.
I just check response code for workout and they have .continueInApp response code.
That being said, I believe you can pass response with code .continueInApp and save whatever parameters you have to use in UserActivity in handler's completion. UserAcitivity will be passed to application: continueUserActivity function in AppDelegate; so you can pause it from there.
If handlerForIntent is not called, then I believe intent is not understood by Siri

Open your own application via URL schemes [duplicate]

I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.

Put app on foreground programmatically on Swift

I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.

Best practice for initial loading in iOS app

Just starting out iOS development.
When starting my app, I'd like it to check if the user has a known account and if they do, "login" by acquiring an access token and then display the main / first view. If they don't have an account or if login fails, they should be redirected to a login / registration screen.
Initially I thought I'd hide this process (check for account + call to get access token) behind a splash screen, but apparently this is against Apples guidelines. Is there a common / recommended way to do this on iOS?
By referencing to application:didFinishLaunchingWithOptions: you can read that this method is good for initializing.
Use this method (and the corresponding
application:willFinishLaunchingWithOptions: method) to complete your
app’s initialization and make any final tweaks. This method is called
after state restoration has occurred but before your app’s window and
other UI have been presented. At some point after this method returns,
the system calls another of your app delegate’s methods to move the
app to the active (foreground) state or the background state. This
method represents your last chance to process any keys in the
launchOptions dictionary. If you did not evaluate the keys in your
application:willFinishLaunchingWithOptions: method, you should look at
them in this method and provide an appropriate response. Objects that
are not the app delegate can access the same launchOptions dictionary
values by observing the notification named
UIApplicationDidFinishLaunchingNotification and accessing the
notification’s userInfo dictionary. That notification is sent
shortly after this method returns.
There may be several ways. But using singleton for token and launching your app is one of the best ways. As you might not need to use that launcher class again. Just for checking user have token and if yes then show main view otherwise login view.
Hope it helps you !!

TouchID calls applicationWillResignActive and applicationDidBecomeActive

I am wondering if this is intended by Apple that these lifecycle methods are called upon using TouchID functionality.
Is there any possibility to check if the touchID process is calling these methods (I want to avoid things like a BOOL in app delegate which is set if touchID input is currently shown or not..)
br
Im guessing the problem you're having is that you have code in applicationWillResignActive and applicationDidBecomeActive that affects the view controller that requests Touch ID-validation and that it sets off a tricky loop.
What you need to do is move those calls to applicationDidEnterBackground and applicationWillEnterForeground, because they're not invoked when the Touch ID-mechanism is called.
To explain the sequence, when your app starts the following sequence executes:
applicationDidBecomeActive
..other stuff your app does
Your app invokes Touch ID, which fires:
applicationWillResignActive
... Your app is disabled until the user verifies fingerprint (fails or
succeeds) ...
applicationDidBecomeActive
If you have code in applicationDidBecomeActive -or- applicationWillResignActive that affects Touch ID, you will create an endless loop or worse, you will create code that is riddled with flags and special cases.
Instead you should invoke Touch ID in two cases:
When your app starts (usually in didFinishLaunchingWithOptions)
When your app's applicationWillEnterForeground is called.
You can create a static bool in your login script that you can check from your AppDelegate!
static var isShowingTouchID = false
Then before your context.evaluatePolicy call, you can set it to true, and in the callback function, set it to false. I believe you use the reply argument to set a callback to this.
Then in your AppDelegate, check the status of this bool.
Originally I was using a public variable in AppDelegate and setting that, but I feel similarly in that I didn't want to do that. Frankly, I don't like this solution either, but it was the only one I could come up.
I even tried overriding viewDidDisappear in my login script, but I quickly found out that it was not being called even when I tapped "Cancel" on the touch ID prompt.
If anyone has a better solution, I would love to know.

Resources