I have got two buttons on my today extension. One is dedicated to open MainViewController and the second one need to navigate to second ViewController.
So I made a appURL:
let appURL = NSURL(string: "StarterApplication://")
and then on a first button I call:
self.extensionContext?.open(appURL! as URL, completionHandler:nil)
Which opens app on MainViewController.
How can I open MainViewController and performSegue to my SecondViewController when tapping second button on Today Widget?
I made a second URL scheme for that specific ViewController. I saw in other simmilar topics that it can be done by calling:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
}
in AppDelegate but don't have any idea how to use it.
Use one URL scheme. You can add different paths or arguments for different task.
For example, I've an app that displays multiple items in a today extension. If you tap an item the app is opened.
- (void)_openItem:(SPItem *)item
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"myapp://localhost/open_item?itemID=%#", item.itemID]];
if (url != nil)
{
[self.extensionContext openURL:url completionHandler:nil];
}
}
As you've already mentioned in you question, you need to implement - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
In my case it more or less looks like this:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
BOOL ret;
if ([url.scheme isEqualToString:#"myapp"])
{
if ([url.path isEqual:#"open_item"])
{
#try
{
NSDictionary *query = [url.query queryDictionary];
NSString* itemID = query[#"itemID"];
[self _gotoItemWithID:itemID completionHandler:nil];
}
#catch (NSException *exception) {
}
ret = YES;
}
}
else
{
ret = NO;
}
return ret;
}
As you can see, I first check, if the url scheme. If it's the one I expect, I check the path. By using different paths I'm able to implement different commands the today extension is able to execute. Each command may have different arguments. In case of the "open_item" command, I expect the parameter "itemID".
By returning YES, you tell iOS you were able to handle the URL your app was called with.
In my app [self _gotoItemWithID:itemID completionHandler:nil] does all the need tasks to display the item. In your case you would need a function to display the second view controller.
Edit:
I forgot to mention that queryDictionary is a method in an NSString extension. It takes a string (self) and tries to extract URL parameter and return them as dictionary.
- (NSDictionary*)queryDictionary
{
NSCharacterSet* delimiterSet = [NSCharacterSet characterSetWithCharactersInString:#"&;"];
NSMutableDictionary* pairs = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:self];
while (![scanner isAtEnd])
{
NSString* pairString;
[scanner scanUpToCharactersFromSet:delimiterSet
intoString:&pairString] ;
[scanner scanCharactersFromSet:delimiterSet intoString:NULL];
NSArray* kvPair = [pairString componentsSeparatedByString:#"="];
if ([kvPair count] == 2)
{
NSString* key = [[kvPair objectAtIndex:0] stringByRemovingPercentEncoding];
NSString* value = [[kvPair objectAtIndex:1] stringByRemovingPercentEncoding];
[pairs setObject:value forKey:key] ;
}
}
return [NSDictionary dictionaryWithDictionary:pairs];
}
I found the solution. The answer is deep linking. Here is my method in appDelegate:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlPath : String = url.path as String!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlPath == "/Action"){
let ActionView: ActionViewController = mainStoryboard.instantiateViewController(withIdentifier: "ActionViewController") as! ActionViewController
self.window?.rootViewController = ActionView
} else if(urlPath == "/VoiceCommandView") {
let VoiceCommandView: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.window?.rootViewController = VoiceCommandView
}
self.window?.makeKeyAndVisible()
return true
And in the TodayExtensionViewController I defined two URL schemes with same host but different URL paths. And made a simple:
self.extensionContext?.open(startAppURL! as URL, completionHandler:nil)
for first button but changed the urlPath for the second button.
Is it possible to make same action like on Android, when we press Phone icon in application?
On Android we will see Phone Call screen with entered phone number.
On iOS it will call to a phone number immediately.
Example:
Android: Some app call button click -> Native Android Phone Call screen with pre-entered number -> Manual click call button
iOS: Same app call button click -> Immediately call action to phone number
I need to know, is it possible to add 2nd step into iOS like on Android.
yes it is possible by using openUrl as follows, i wrote in swift u can try same in objc
// swift
func makePhoneCall(_ phoneNumber: String?) {
if phoneNumber != nil && phoneNumber?.length ?? 0 > 0 {
let charSet: CharacterSet = CharacterSet(charactersIn: "0123456789-+()").inverted
let cleanedString: String? = (phoneNumber!.components(separatedBy: charSet) as NSArray).componentsJoined(by:"")
let escapedPhoneNumber: NSString? = cleanedString?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) as NSString?
let phoneURLString: String = String(format:"telprompt:%#", escapedPhoneNumber!)
let phoneURL: URL? = URL(string: phoneURLString)
if phoneURL != nil {
if UIApplication.shared.canOpenURL(phoneURL!) == true {
UIApplication.shared.openURL(phoneURL!)
} else {
// show alert or something
}
}
}
}
// objc
-(void) makePhoneCall:(NSString *)phoneNumber {
if(phoneNumber.length > 0) {
NSCharacterSet *charSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789-+()"] invertedSet];
NSString *cleanedString = [[phoneNumber componentsSeparatedByCharactersInSet:charSet] componentsJoinedByString:#""];
NSString *escapedPhoneNumber = [cleanedString stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *phoneURLString = [NSString stringWithFormat:#"telprompt:%#", escapedPhoneNumber];
NSURL *url = [NSURL URLWithString:phoneURLString];
if([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
} else {
// show alert // or something
}
}
}
I've been trying to get Tweetbot to open a user account when a table row is tapped by the user. However, although Tweetbot opens, it doesn't show the user account. I've been using the Tweetbot URL Scheme page as a reference.
Below is my code:
if (indexPath.row == 1) {
// Removed the actual username
self.destViewURL = #"http://twitter.com/dummyusername";
self.destViewTitle = #"Twitter";
// URLs to try
NSURL *twitterURL = [NSURL URLWithString:#"twitter://user?screen_name= dummyusername"];
NSURL *tweetbotURL = [NSURL URLWithString:#"tweetbot://dummyusername/timeline"];
// Check if Tweetbot is available to open it
if ([[UIApplication sharedApplication] canOpenURL:tweetbotURL]) {
[[UIApplication sharedApplication] openURL:tweetbotURL];
}
else {
// Check if Twitter is available to open it
if ([[UIApplication sharedApplication] canOpenURL:twitterURL]) {
[[UIApplication sharedApplication] openURL:twitterURL];
}
// Otherwise open it in the web view
else {
[self performSegueWithIdentifier:#"showWebView" sender:nil];
}
The URL schemes page for Tweetbot 3 is here
All of the supported URLs begin with tweetbot://<screenname>, which suggests that you need to know the user's existing twitter screen name to link to a profile.
However, my testing has shown that you could link directly to a profile by using the same value for tweetbot://<screenname>/user_profile/<profile_screenname>
Swift e.g.
/* Tweetbot app precedence */
if let tweetbotURL = NSURL(string: "tweetbot://dummyusername/user_profile/dummyusername") {
if UIApplication.sharedApplication().canOpenURL(tweetbotURL) {
UIApplication.sharedApplication().openURL(tweetbotURL)
return
}
}
/* Twitter app fallback */
if let twitterURL = NSURL(string: "twitter:///user?screen_name= dummyusername") {
if UIApplication.sharedApplication().canOpenURL(twitterURL) {
UIApplication.sharedApplication().openURL(twitterURL)
return
}
}
/* Safari fallback */
if let webURL = NSURL(string: "http://www.twitter.com/dummyusername") {
if UIApplication.sharedApplication().canOpenURL(webURL) {
UIApplication.sharedApplication().openURL(webURL)
}
}
How can I determine if the user of an iOS device has a specific application installed? If I know the name of the application can I use canOpenURL somehow?
If the application supports a custom url scheme you can check 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.
If you control both apps you might also use a shared keychain or pasteboard to communicate between them in more detail.
You can check in this way as well:
BOOL temp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"yourAppURL://"]];
if(!temp)
{
NSLog(#"INVALID URL"); //Or alert or anything you want to do here
}
for swift users
let urlPath: String = "fb://www.facebook.com"
let url: NSURL = NSURL(string: urlPath)!
let isInstalled = UIApplication.sharedApplication().canOpenURL(url)
if isInstalled {
print("Installed")
}else{
print("Not installed")
}
Facebook uses this https://github.com/facebook/facebook-ios-sdk/blob/master/FBSDKCoreKit/FBSDKCoreKit/Internal/FBSDKInternalUtility.m internally, you can do the same
#define FBSDK_CANOPENURL_FACEBOOK #"fbauth2"
+ (BOOL)isFacebookAppInstalled
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[FBSDKInternalUtility checkRegisteredCanOpenURLScheme:FBSDK_CANOPENURL_FACEBOOK];
});
NSURLComponents *components = [[NSURLComponents alloc] init];
components.scheme = FBSDK_CANOPENURL_FACEBOOK;
components.path = #"/";
return [[UIApplication sharedApplication]
canOpenURL:components.URL];
}
Code in Swift 3
static func isFacebookAppInstalled() -> Bool {
let schemes = ["fbauth2", "fbapi", "fb"]
let schemeUrls = schemes.flatMap({ URL(string: "\($0)://") })
return !schemeUrls.filter({ UIApplication.shared.canOpenURL($0) }).isEmpty
}
How can I make a phone call programmatically on iPhone? I tried the following code but nothing happened:
NSString *phoneNumber = mymobileNO.titleLabel.text;
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
To go back to original app you can use telprompt:// instead of tel:// - The tell prompt will prompt the user first, but when the call is finished it will go back to your app:
NSString *phoneNumber = [#"telprompt://" stringByAppendingString:mymobileNO.titleLabel.text];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
Probably the mymobileNO.titleLabel.text value doesn't include the scheme //
Your code should look like this:
ObjectiveC
NSString *phoneNumber = [#"tel://" stringByAppendingString:mymobileNO.titleLabel.text];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber]];
Swift
if let url = URL(string: "tel://\(mymobileNO.titleLabel.text))") {
UIApplication.shared.open(url)
}
Merging the answers of #Cristian Radu and #Craig Mellon, and the comment from #joel.d, you should do:
NSURL *urlOption1 = [NSURL URLWithString:[#"telprompt://" stringByAppendingString:phone]];
NSURL *urlOption2 = [NSURL URLWithString:[#"tel://" stringByAppendingString:phone]];
NSURL *targetURL = nil;
if ([UIApplication.sharedApplication canOpenURL:urlOption1]) {
targetURL = urlOption1;
} else if ([UIApplication.sharedApplication canOpenURL:urlOption2]) {
targetURL = urlOption2;
}
if (targetURL) {
if (#available(iOS 10.0, *)) {
[UIApplication.sharedApplication openURL:targetURL options:#{} completionHandler:nil];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[UIApplication.sharedApplication openURL:targetURL];
#pragma clang diagnostic pop
}
}
This will first try to use the "telprompt://" URL, and if that fails, it will use the "tel://" URL. If both fails, you're trying to place a phone call on an iPad or iPod Touch.
Swift Version :
let phone = mymobileNO.titleLabel.text
let phoneUrl = URL(string: "telprompt://\(phone)"
let phoneFallbackUrl = URL(string: "tel://\(phone)"
if(phoneUrl != nil && UIApplication.shared.canOpenUrl(phoneUrl!)) {
UIApplication.shared.open(phoneUrl!, options:[String:Any]()) { (success) in
if(!success) {
// Show an error message: Failed opening the url
}
}
} else if(phoneFallbackUrl != nil && UIApplication.shared.canOpenUrl(phoneFallbackUrl!)) {
UIApplication.shared.open(phoneFallbackUrl!, options:[String:Any]()) { (success) in
if(!success) {
// Show an error message: Failed opening the url
}
}
} else {
// Show an error message: Your device can not do phone calls.
}
The answers here are perfectly working. I am just converting Craig Mellon answer to Swift. If someone comes looking for swift answer, this will help them.
var phoneNumber: String = "telprompt://".stringByAppendingString(titleLabel.text!) // titleLabel.text has the phone number.
UIApplication.sharedApplication().openURL(NSURL(string:phoneNumber)!)
If you are using Xamarin to develop an iOS application, here is the C# equivalent to make a phone call within your application:
string phoneNumber = "1231231234";
NSUrl url = new NSUrl(string.Format(#"telprompt://{0}", phoneNumber));
UIApplication.SharedApplication.OpenUrl(url);
Swift 3
let phoneNumber: String = "tel://3124235234"
UIApplication.shared.openURL(URL(string: phoneNumber)!)
In Swift 3.0,
static func callToNumber(number:String) {
let phoneFallback = "telprompt://\(number)"
let fallbackURl = URL(string:phoneFallback)!
let phone = "tel://\(number)"
let url = URL(string:phone)!
let shared = UIApplication.shared
if(shared.canOpenURL(fallbackURl)){
shared.openURL(fallbackURl)
}else if (shared.canOpenURL(url)){
shared.openURL(url)
}else{
print("unable to open url for call")
}
}
The Java RoboVM equivalent:
public void dial(String number)
{
NSURL url = new NSURL("tel://" + number);
UIApplication.getSharedApplication().openURL(url);
}
Tried the Swift 3 option above, but it didnt work. I think you need the following if you are to run against iOS 10+ on Swift 3:
Swift 3 (iOS 10+):
let phoneNumber = mymobileNO.titleLabel.text
UIApplication.shared.open(URL(string: phoneNumber)!, options: [:], completionHandler: nil)
let phone = "tel://\("1234567890")";
let url:NSURL = NSURL(string:phone)!;
UIApplication.sharedApplication().openURL(url);
'openURL:' is deprecated: first deprecated in iOS 10.0 - Please use openURL:options:completionHandler: instead
in Objective-c iOS 10+ use :
NSString *phoneNumber = [#"tel://" stringByAppendingString:mymobileNO.titleLabel.text];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNumber] options:#{} completionHandler:nil];
Swift
if let url = NSURL(string: "tel://\(number)"),
UIApplication.sharedApplication().canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
Use openurl.
For making a call in swift 5.1, just use the following code: (I have tested it in Xcode 11)
let phone = "1234567890"
if let callUrl = URL(string: "tel://\(phone)"), UIApplication.shared.canOpenURL(callUrl) {
UIApplication.shared.open(callUrl)
}
Edit: For Xcode 12.4, swift 5.3, just use the following:
UIApplication.shared.open(NSURL(string: "tel://555-123-1234")! as URL)
Make sure that you import UIKit, or it will say that it cannot find UIApplication in scope.