Launching app OR app store from Safari? - ios

I already know how to launch an app from safari, but is it possible to check if the app is installed before launching? I'm thinking to launch the app store if the app isn't currently installed on the iPhone.

It's not possible to check if app is installed from a web page. You could do it inside an other app by checking if your url scheme can be opened using UIApplication's -canOpenURL: method, but there is no javascript equivalent to this.
However, you can use the following workaround:
<script language="javascript">
function open_appstore() {
window.location='http://itunes.com/';
}
function try_to_open_app() {
setTimeout('open_appstore()', 300);
}
</script>
<a onClick="javascript:try_to_open_app();" href="yourappurl:">App name</a>
This code will set a timeout on the link that will call the open_appstore function if this timeout ends. Since your link is pointed at the app's custom url, Safari will try to open that link and if it can, it will open the app and stop the timer, so AppStore link will not be opened.
If the app link can't be opened, when timer runs out it will display an error popup saying it can't open the page (can't get rid of that), but it will immediately go to AppStore and dismiss that error.
iOS 9 adds a really nice feature that lets your app open a http/s url: Universal Links
In iOS 10 there is a popup saying "Open in [App Name]" when you tap the link and the app is installed. If the user does not tap on "Open" in the given timeout, this solution will use the fallback.
As 300ms is too short to tap anything, this solution always fails on iOS 10.

This worked for me with a similar situation: wherein I wanted to open gmaps app if it was supported - otherwise go to gmap site directly.
function mapLink(addy) {
addy = encodeURIComponent(addy);
var fallback = 'http://maps.google.com/?q=' + addy
, link = 'comgooglemaps://?q=' + addy;
try {
document.location = link;
} catch(err) {
document.location = fallback;
}
}
Seems to work pretty well for my use case.
Update:
If you want to do a new window on fallback, this still allowed the ios error message to pop up. To get around it try this.
try {
document.location = link;
} catch(err) {
window.location.reload(true);
window.open(fallback, '_blank');
}

The Solution from Apple:
From Apple Documentation
https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
If the app is already installed on a user's device, the banner intelligently changes its action, and tapping the banner will simply open the app. If the user doesn’t have your app on his device, tapping on the banner will take him to the app’s entry in the App Store. When he returns to your website, a progress bar appears in the banner, indicating how much longer the download will take to complete. When the app finishes downloading, the View button changes to an Open button, and tapping the banner will open the app while preserving the user’s context from your website.
Smart App Banners automatically determine whether the app is supported on the user’s device. If the device loading the banner does not support your app, or if your app is not available in the user's location, the banner will not display.
To add a Smart App Banner to our webpage, include the following meta tag in the head of each page where you’d like the banner to appear:
NOTE: We can also pass the app-argument: like myName,etc.,
Check that Providing Navigational Context to Your App Header in this Page
Updates:
1. Once you have closed the banner that showing up, then that will not be displayed again even though you had that meta tag in our html.
2. To reset that launch the settings App then navigate to General>Resent>Reset all settings

You can simply read the return value from the method -(BOOL)openURL:(NSURL)url*, if it's NO, it means that the target application isn't installed. Following code snipped gives an example using the navigon url scheme:
NSString *stringURL = #"navigon://coordinate/NaviCard/19.084443/47.573305";
NSURL *url = [NSURL URLWithString:stringURL];
if([[UIApplication sharedApplication] openURL:url]) {
NSLog(#"Well done!");
} else {
stringURL = #"https://itunes.apple.com/it/app/id320279293?mt=8";
url = [NSURL URLWithString:stringURL];
[[UIApplication sharedApplication] openURL:url];
}
Thanks to zszen for the correction.

Related

Xcode UI testing for external launching

Hope the title isn't too vague. In the app I am testing, certain app-flow launches external apps (like Safari or Facebook for example). How can I verify that the app launched them with a UI test? I can test for like an openURL with a unit test but is there an equivalent for UI?
I'm not trying to actually continue after leaving the app, just to test and ensure the appropriate new app or URL was launched. The simulator/recorder can select UI elements from the launched application, but the test breaks at that point of the code. I also tried getting a handle to something on the menu bar (always present in the app, like a hamburger button) while it was there and then checking for it after launching the other app (to make sure it wasn't there). But that broke the test as well.
Is there a work around? Or is this just something to be tested by a Unit Test?
As you mentioned, the UI framework can only test the given application. So I would do an assert to make sure that the screen you were previously on (before opening safari or facebook, etc) is no longer present. So for example:
XCTAssertFalse(app.tables.elementBoundByIndex(0).exists, "Found element, so app didn't open safari/facebook")
You're just asserting that the element isn't there, change app.tables.elementBoundByIndex(0).exists to be whatever element you're checking.
Xcode 13 and iOS 15 for safari : we could check if safari opens as below.
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
let isSafariBrowserOpen = safari.wait(for: .runningForeground, timeout: 30)
Then do what you want :
if isSafariBrowserOpen {
// Safari browser has opened, then additionally we could check if the url isn't nil
safari.textFields["Address"].tap()
let url = safari.textFields["Address"].value as? String
XCTAssertNotNil(url)
}
else {
// Safari browser hasn't opened
// do something here, if necessary
}
Note: I didn't check with other external apps.

iOS Safari does not recognize url schemes after user cancels

I'm noticing strange behavior in Safari recently.
I register a url scheme for the my app, and enter myapp:// into Safari.
This launches my app immediately.
Then I go back to Safari, and enter myapp:// into Safari again,
this time it prompts me "Open this page in "myapp"?" Cancel or Open.
My app will launch if I tap on open, and subsequent attempts the same alert shows. If I try tapping on cancel, my app will not launch. which is expected.
However, if I enter myapp:// into the URL bar again, I'm prompted "Cannot Open Page" "Safari cannot open the page because the address is invalid."
This will fail in the same way for all subsequent attempts, until I kill Safari and re-start it, or open another tab.
This is the same behavior with Youtube and Evernote. my guess is that Safari cached the URL as an invalid URL when the User taps on cancel. Is there official documentation on this behavior?
Bbserved in iOS 8.1.2 and iOS 6.1.3
In 9.1 the issue still exists. The solution for me is just restarting safari (swipe up to clear it from background).
I had the same problem. Once cancelled, it would give that error.
What I did was sending an extra parameter with a timestamp, so Safari would not cache it. So after the last param, I added a foo param with the number of milliseconds since midnight January 1, 1970. I use as3, but this should be readable for all developers:
var foo:Number = new Date().time; //The number of milliseconds since midnight January 1, 1970
var urlRequest:URLRequest = new URLRequest(url+"&foo="+foo);
relaunching the safari app, or opening a new tab solved this problem
When you call your url add a unique value such as timeStamp to your url call
double currentt = [[NSDate new] timeIntervalSince1970];
NSTimeInterval differ= [[NSDate dateWithTimeIntervalSince1970:currentt] timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:1296748524]];
NSLog(#"differ: %f", differ);
NSString *url =[NSString stringWithFormat: #"https://thisisawebsite&timestamp=%f", differ];
Will always then see the popup until you click "Okay"
Adding following code in AppDelegate solved my problem, hope it works for you too.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let notification = Notification(name: Notification.Name(rawValue: "AppNotificationLaunchString"), object: nil, userInfo: [UIApplicationLaunchOptionsKey.url:url])
NotificationCenter.default.post(notification)
return true
}
My observation is that it seems to be a setting associated with the Safari tab. If you open a new tab, the url scheme works.
Cancel disables the url scheme and saves the setting into the tab. If you swipe away Safari or even restart the phone, Safari will restore the tab and it still won't handle the scheme. However the url scheme works if you open a new tab.
I suppose that for consistent behavior, you would want to somehow get a new tab opened before the url scheme is used.

Detect if app has been downloaded from integrated app store IOS

I am displaying on the touch of a button inside my app, an app on the appstore. The app pops up in a SKStoreProductViewController with the content of the app store. Now, is there any method to detect if the user has pressed install on the shown app, or even better, be alerted if the user has pressed intall and the app has finished installing? Since the user in my app in this way is capable of buying the fill version, I want to quit the trial when the download is over.
I dont think you can detect if user has pressed install or an app is installed when using SKStoreProductViewController [docs]. Only API iOS exposes is loadProductWithParameters:completionBlock:.
But if you want to check if your app has installed or not there are other ways -
1) Using custom URL scheme. Define a custom URL scheme for your app and then check using UIApplication -canOpenURL: That will tell you only that an application able to open that url scheme is available, not necessarily which application that is. There's no publicly available mechanism to inspect what other apps a user has installed on their device. Custom URL scheme check can be done something like this -
BOOL fullApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:NSString* urlString = [NSString stringWithFormat:#"yourFULLAppURL://"]]];
       
if(!fullApp)
{
NSLog(#"INVALID URL"); //Or alert or anything you want to do here
}
2) If you control both apps you might also use a shared keychain or pasteboard to communicate between them in more detail.

Is there a notification sent when the user cancels a program install with the itms-services protocol

I am working on a way to update an enterprise app OTA. I am opening a url to a copy of my app from my own server inside the app when I detect a new version is available. This works well except I want to give the user the option of not updating the app if they don't want to. What I would like to know is if there is any sort of notification that is sent to the ios app if the user presses cancel in the dialog that pops up when the url is opened. In other words I want a way for the app to continue ONLY if the user cancels the update request.
Here is the code I am executing to update the app.
NSURL *url = [NSURL URLWithString:#"itms-services://?action=download-manifest&url=http://www.mywebsite.com/myapp.plist"];
if (![[UIApplication sharedApplication] openURL:url]) {
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
I wasn't able to find any information what-so-ever about the itms protocol in the apple developer docs. I was able to achieve the effect I wanted by presenting a custom UIAlertView asking the user if they wanted to update before opening the url, but this has the unfortunate side-effect of prompting the user twice if they want to update, and still doesn't really handle the case where they cancel the update after the first prompt.
Any help on the matter would be appreciated.

How programmatically restart an iPhone app in iOS

How programmatically restart an iPhone app in iOS?
I find this way http://writeitstudios.com/david/?p=54
But may be something simple.
The only way I know to do this is not ideal, but it works.
First, your app has to opt out of background execution (multitasking) The app has to quit when exited, not run as a background task. This is done with the plist key UIApplicationExitsOnSuspend.
Second, your app needs to register a custom URL scheme that can be used to launch the app.
Third, you need a web page hosted somewhere that when loaded will redirect to your app's custom URL scheme.
Forth, the user needs an active Internet connection.
To exit and restart, call UIApplication openURL on your hosted redirecting web page. Your app will exit and safari will launch and load your page. The page will redirect Safari to your custom URL scheme, prompting Safari to internally call openURL, causing iOS to launch your app.
my post that you linked to is referring to a Cocoa Application, not the iOS. On the iOS, you can quit an application (but Apple doesn't like this) by using exit(0); but I don't recommend that. You cannot restart iPhone apps though.
Unless you're developing for jailbroken devices, Apple won't even allow you to programatically terminate your app. So restarting the device is out of the question.
Your AppDelegate instance has a method
(void)applicationDidBecomeActive:(UIApplication *)application
{
}
In here, you can put logic to figure out if the app should restart, or continue doing whatever it was doing. For example you can have a BOOL variable appMustRestart that is false at first but gets triggered as true whenever something happens in your app that you'd like the next time to be a fresh relaunch.
if (appMustRestart)
{
[self resetVars]; // call a method that resets all your vars to initial settings
// INSERT CODE HERE TO TRANSFER FOCUS TO INITIAL VIEWCONTROLLER
}

Resources