iOS Today Widget Universal Links From Lock Screen and Notification Center - swift

We have a today widget to deep link into the app. The deep links work just fine when the user accesses the widget from the home screen. However, when a user accesses the widget when the device is locked, or when the user slides down from the top of the screen, the links open in Safari.
I was wondering if anyone else has come across this issue, and if so, how they solved it.

Here was the solution we came upon (Swift 4.1). We needed to support a custom URL scheme to tell iOS that we can open links from the today widget. This uses a different UIApplication delegate function. Along with implementing func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool, we also need to implement func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
First, in Info.plist, we have our supported schemes under CFBUndleURLTypes.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>todayWidgetScheme</string>
</array>
</dict>
</array>
Then, also in Info.plist, we also listed the scheme under LSApplicationQueriesSchemes.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>todayWidgetScheme</string>
</array>
Next, when opening the link from the today widget, set the url scheme to the iOS recognized todayWidgetScheme.
func openAppFromTodayWidget() {
if let url = URL(string: "https://url.com") {
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)
components?.scheme = "todayWidgetScheme"
if let todayWidgetUrl = components?.url {
extensionContext?.open(todayWidgetUrl)
}
}
}
Finally, in AppDelegate.swift, when iOS asks the application to handle the universal link, set the original url scheme
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme == "todayWidgetScheme" {
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)
components?.scheme = "https"
if let todayWidgetUrl = components?.url {
// do your thing
return true
}
}
return false
}

Related

Flutter IOS Universal Link Opens App, But Does Not Navigate To Correct Page

I have set up Universal Links on my flutter project for IOS.
Like the title suggests, my app does open when I click on a link relating to my site but it does not navigate to the correct page. It just opens the app.
I'm not using the uni_links package, rather I used a combination of guides (including official documentation):
https://developer.apple.com/videos/play/wwdc2019/717/
https://nishbhasin.medium.com/apple-universal-link-setup-in-ios-131a508b45d1
https://www.kodeco.com/6080-universal-links-make-the-connection
I have setup my apple-app-site-association file to look like:
{
"applinks": {
"details": [
{
"appIDs": [
"XXXXXXX.com.my.appBundle"
],
"componenents": [
{
"/": "/*"
}
]
}
]
}
}
and I have added this to my info.plist file:
<key>FlutterDeepLinkingEnabled</key>
<true/>
and my AppDelegate.swift file looks like:
import UIKit
import Flutter
import Firebase
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// This will allow us to check if we are coming from a universal link
// and get the url with its components
// The activity type (NSUserActivityTypeBrowsingWeb) is used
// when continuing from a web browsing session to either
// a web browser or a native app. Only activities of this
// type can be continued from a web browser to a native app.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
// Now that we have the url and its components,
// we can use this information to present
// appropriate content in the app
return true
}
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
My Runner-entitlements are also setup correctly like:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.example.com</string>
<string>applinks:*.example.com</string>
</array>
The issue is, if I click a hyperlink for www.example.com/mypath , it does not got to the page/route handled by /mypath, but instead just opens the app.
My routing is done using go_router: ^5.2.4
Please does anyone know why this is happening? I'm blocked by this. I have seen similar questions, but none with answers that have worked for me. Any help is appreciated.
Ok so figured it out. The official apple documentation requests the addition of a variation of this function in the AppDelegate.swift file:
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// This will allow us to check if we are coming from a universal link
// and get the url with its components
// The activity type (NSUserActivityTypeBrowsingWeb) is used
// when continuing from a web browsing session to either
// a web browser or a native app. Only activities of this
// type can be continued from a web browser to a native app.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
// Now that we have the url and its components,
// we can use this information to present
// appropriate content in the app
return true
}
Seems that it conflicts with the flutter framework for handling universal links. Taking that function out and just having this in my info.plist worked (everything else stayed the same):
<key>FlutterDeepLinkingEnabled</key>
<true/>
Flutter documentation is not out for this (as at the time of posting this answer) so if people are interested, I could do a small article on the necessary steps.
When you handle the dynamic link you get the universal link and other data in the userActivity parameter of the following function.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
debugPrint("incoming url is", incomingURL)
let link = DynamicLinks.dynamicLinks().shouldHandleDynamicLink(fromCustomSchemeURL: incomingURL)
print(link)
let linkHandle = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { link, error in
guard error == nil else {
print("Error found.")
return
}
if let dynamicLink = link {
self.handleDynamicLinks(dynamicLink)
}
}
if linkHandle {
return true
} else {
return false
}
}
return false
}
Parse the data from another function or you can parse in above code also. In my case I parsed the code in below function.
func handleDynamicLinks(_ dynamicLink: DynamicLink) {
guard let link = dynamicLink.url else {
return
}
if let landingVC = self.window?.rootViewController as? LandingViewController {
// Do you your handling here with any controller you want to send or anything.
}
// example you are getting ID, you can parse it here
if let idString = link.valueOf("id"), let id = Int.init(idString) {
print(id)
}
}
When you get the details from the link you can simply fetch the navigation controller or the VisibleController, and then can push to the desired flow.

In iOS, how can I get the contact shared from "Recent" calls list to my app in Flutter?

Please excuse me if I sound stupid, I'm new to flutter.
I have started learning flutter recently and wanted to create an app where anyone can share a contact from the "Recent" calls list to my app. I'm following this blog post which allows text share from any other app to my app.
What I have done so far:
This is my plist file, added the public.vcard to allow my app to appear on the tap of "Share Contact".
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>
SUBQUERY (
extensionItems, $extensionItem,
SUBQUERY (
$extensionItem.attachments, $attachment,
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard"
).#count >= 1
).#count > 0
</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>
Here's my ShareViewController.swift
import Social
import MobileCoreServices
class ShareViewController: SLComposeServiceViewController {
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
print("Something is not right")
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
let sharedSuiteName: String = "group.com.thelogicalbeing.whatsappshare"
let sharedDataKey: String = "SharedData"
let extensionItem = extensionContext?.inputItems[0] as! NSExtensionItem
let contentTypeText = kUTTypeText as String // Note, you need to import 'MobileCoreServices' for this
for attachment in extensionItem.attachments! {
print(attachment)
if attachment.hasItemConformingToTypeIdentifier(contentTypeText) {
attachment.loadItem(forTypeIdentifier: contentTypeText, options: nil, completionHandler: {(results, error) in
if let sharedText = results as! String? {
if let userDefaults = UserDefaults(suiteName: sharedSuiteName) {
userDefaults.set(sharedText, forKey: sharedDataKey)
}
}
})
}
}
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
Here's my AppDelegate.swift
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
let sharedSuiteName: String = "group.com.thelogicalbeing.whatsappshare"
let sharedDataKey: String = "SharedData"
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel(name: "com.thelogicalbeing.whatsappshare", binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
if call.method == "getSharedData" {
if let prefs = UserDefaults(suiteName: sharedSuiteName) {
if let sharedText = prefs.string(forKey: sharedDataKey) {
result(sharedText);
}
// clear out the cached data
prefs.set("", forKey: sharedDataKey);
}
}
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
What I'm trying to achieve is that I need to receive the phone number and display it in my app.
Don't know how to proceed. Any help will be appreciated.
1- Apple does not allow fetching call logs on iOS!
You can fetch all contacts with all their information. But not the calls log.
2- On Android you can use the pub.dev dependency plugin call_log to do that.
Have a look at this package called receive_sharing_intent, it allows you to to receive sharing photos, videos, text, urls or any other file types from another app. And it also supports iOS Share extension and launching the host app automatically.

URL not being passed to appdelegate

I can't get dynamic links or universal links working. My app won't read in a URL. I've started with a fresh project, added a URL Scheme of com.company.AppLoginView and the below code into AppDelegate. I added a url in notes of: com.company.AppLoginView://Register?User=Name. Clicking the link requests to open the app which then opens. However, nothing seems to be passed to the app and the below isn't being called. What am I doing wrong? This is a completely empty app and should work.
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let scheme = url.scheme,
scheme.localizedCaseInsensitiveCompare("com.myApp") == .orderedSame,
let view = url.host {
var parameters: [String: String] = [:]
URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.forEach {
parameters[$0.name] = $0.value
}
print("View:\(view) Params:\(parameters) Scheme:\(scheme)")
// redirect(to: view, with: parameters)
}
return true
}
This is because target is iOS 13 and SceneDelegate takes over some functions from AppDelegate. In particular, open:url is replaced by SceneDelegate scene:openURLContexts.
More here: Apple openURLContexts
3 days of my life I won't get back!
You should add ULR scheme and URL identifier to plist. You can use this:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.company.AppLoginView</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.company.AppLoginView</string>
</array>
</dict>
</array>
and check it with print in method in AppDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print(url)
return true
}
open Safari and type your address
com.company.AppLoginView://Register?User=Name

Is Uber's API still stable? I can't get basic authentication working IOS 13 swift 5.0

Was trying to do basic integration of Uber's IOS Ride Request SDK, I followed the instructions here https://developer.uber.com/docs/riders/ride-requests/tutorials/api/ios
When trying to authenticate a user it opens the Uber app then immediately returns to my app. The console prints "code=25 'User cancelled the login process.'" I'm using a real device with IOS 13 and Swift 5.0. It looks like the Uber Developers social media hasn't been updated since 2017, and it doesn't appear very active.
Info.plist
<key>UberClientID</key>
<string>907YWpr4cTwH9-TWVP0Fq8DX-_HuCxN3</string>
<key>UberDisplayName</key>
<string>Wandr</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>uber</string>
<string>uberauth</string>
</array>
<key>UberCallbackURIs</key>
<array>
<dict>
<key>UberCallbackURIType</key>
<string>General</string>
<key>URIString</key>
<string>com.wandrinc.Wandr://oauth/consumer</string>
</dict>
</array>
login function
func loginToUber() {
let loginManager = LoginManager()
loginManager.login(requestedScopes:[.request], presentingViewController: UIApplication.shared.keyWindow?.rootViewController!, completion: { accessToken, error in
if let error = error {
print(error)
return
}
self.requestRide()
})
}
App Delegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
let handledUberURL = UberAppDelegate.shared.application(app, open: url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplication.OpenURLOptionsKey.annotation] as Any)
return handledUberURL
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
let handledUberURL = UberAppDelegate.shared.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
return handledUberURL
}
Output
Error Domain=com.uber.rides-ios-sdk.ridesAuthenticationError Code=25 "User cancelled the login process." UserInfo={NSLocalizedDescription=User cancelled the login process.}

application openURL in Swift

I am having an issue with the Appdelegate method OpenURL.
I have setup my Imported UTI's and Document Type. But when opening my app from a mail attachment, the app crashes immediately when I have the method implemented.
The depreciated handleOpenURL works, but not OpenURL?
At the moment I have no code in the implementation, and am just returning true.
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String, annotation: AnyObject?) -> Bool {
return true
}
The crash says Thread 1: EXC_BAD_ACCESS (code-1, address-0x0)
I don't really want to have to use the deprecated method.
I have my head blow for a week with this issue.
My app keep crashing after Login Using Social Media Such as Wechat / LinkedIn.But Facebook and Google Sign in Works Fine.
I have notice my app will keep crash after confirm sign in on Wechat Apps and will enter foreground.and Getting BAD EXCESS error. I have try to remove my application open url method on AppDelegate and the app wont crash but the action for Social Media Login are not functioning. so I detect that my issue was on the specific method. after search the web I found that im using an deprecated method of ApplicationOpenUrl as reference from https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return true
} // this method is deprecated in iOS 9 https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application
notice that the deprecated version are using annotation:Any which will cause issue if you had bridging to an Obj-c framework such as wechat.
So what I do was, I swap my code into a the new format
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String
let annotation = options[UIApplicationOpenURLOptionsKey.annotation]
let application = app
return true
}
Hope this help. it will became my reference in feature also. thanks StackOverflow
This is fairly typical of a signature mismatch between the method signatures automatically generated by the Swift compiler and the actual signature. It happens when you try to pass nil from Objective-C into a Swift explicitly unwrapped optional. Change the annotation parameter to be implicitly unwrapped and you should be gtg.
Swift 5 version of Muhammad Asyraf's answer:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let sourceApplication = options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String
let annotation = options[UIApplication.OpenURLOptionsKey.annotation]
return true
}