I've meet a strange issue when dropping the support for iOS 12. When handle the user activity from AppDelegate continue userActivity, Although we drop INStartAudioCallIntent and INStartVideoCallIntent for it is deprecated in iOS 13, we still receive the above 2 intents from native contact card. But actually we want to handle INStartCallIntent instead.
Anyone knows why this happens for my debug version is 14.6, thanks.
Adding Intent as an app extension to handle INStartCallIntentHandling made my AppDelegate receive INStartCallIntent!
It can be implemented like this:
File > New -> Target -> Intent
Add INStartCallIntent to Supported Intents
Implement the IntentHandler (In the intent extension) like this:
class IntentHandler: INExtension, INStartCallIntentHandling {
override func handler(for intent: INIntent) -> Any {
return self
}
func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
let response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
}
The INStartCallIntent will now be called in the AppDelegate:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let intent = userActivity.interaction?.intent {
if let intent = intent as? INStartCallIntent {
// The correct INStartCallIntent
}
}
}
Related
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.
After reading all the guides, and after checking hundred of articles on the internet, I'm quite sure that the method receivedBackgroundNotification is never called.
Everything works perfect, but when the app is in background, a notification is shown and this method never is called. Seems to be impossible to get it working.
Assuming all the normal operations and the basic configuration is well done and is working, what can I do to intercept and manage background push notifications with this library?
I will appreciate a lot any help.
Make sure you have the following configured:
Remote notifications background mode enabled in the target's capabilities
Background app refreshed is enabled on your test device
Assuming you are trying to use the UAPushNotificationDelegate, make sure you either have automatic setup enabled or you are forwarding all the proper methods to UA SDK.
Apple will only wake up your application if you send the push notification with content-available=1 in the payload. The option is exposed in the composer as "background processing" or you can set it in the iOS overrides when using the push api.
In Urban Airship iOS SDK v.13.4.0 if func receivedBackgroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping (UIBackgroundFetchResult) -> Void) is not called you can handle notifications in func receivedNotificationResponse(_ notificationResponse: UANotificationResponse, completionHandler: #escaping () -> Void) if you display notifications as alerts. Remember that you should only handle the notifications with actionIdentifier == UNNotificationDefaultActionIdentifier (the user opened the app from the notification interface).
This is an example of UAPushNotificationDelegate implementation:
extension MyPushNotificationDelegate: UAPushNotificationDelegate {
public func receivedForegroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping () -> Void) {
completionHandler()
}
public func receivedBackgroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
completionHandler(.newData)
}
public func receivedNotificationResponse(_ notificationResponse: UANotificationResponse, completionHandler: #escaping () -> Void) {
guard notificationResponse.actionIdentifier == UNNotificationDefaultActionIdentifier else {
completionHandler()
return
}
someFunc() {
completionHandler()
}
}
public func extend(_ options: UNNotificationPresentationOptions = [], notification: UNNotification) -> UNNotificationPresentationOptions {
[.alert, .badge, .sound]
}
}
I have iOS app with minimal deployment target set to iOS 8.0 and I want to enable spotlight search in it. I understand that spotlight search can be used only in iOS 9 and higher. That is why in my AppDelegate I am using available
#available(iOS 9.0, *)
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
if let uniqIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
print(uniqIdentifier)
}
}
return true
}
And Xcode gives me this error
Protocol 'UIApplicationDelegate' requires
'application(_:continue:restorationHandler:)' to be available on iOS
8.0 and newer
What can I do with it
I found solution so I decide to post it as it may be useful to other developers. After import of CoreSpotlight
import CoreSpotlight
You should add application(_:continue:restorationHandler:) method if you are targeting iOS 8.0 and newer. But as 'CSSearchableItemActionType' is only available on iOS 9.0 or newer you should add version check inside method.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if #available(iOS 9.0, *) {
if userActivity.activityType == CSSearchableItemActionType {
if let uniqIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
print(uniqIdentifier)
}
}
}
return true
}
I am using Sirikit to integrate with my payment domain app where I need to interact with the app. I read Apple documentation, they asked to use common frameworks.
Is it possible to use handoff? if yes then how?
How can I call the other viewController which is in parent app from sirikit?
I will really appreciate for any help. Thanks
Every user activity object needs a type and we need to define those types in the
info.plist of the application
For Eg.
let userActivity = NSUserActivity(activityType: "uaType")
userActivity.title = "uaTitle"
userActivity.userInfo = ["uaKey": "uaValue"]
You can now access this activity object in AppDelegate as follows
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
switch userActivity.activityType {
case "uaType":
//Write your logic to access uaKey & uaValue here
return true
default: return false
}
}
Check SiriKit Programming Guide,
To use handoff, You can create intent response object with NSUserActivity object. While creating NSUserActivity object assign userInfo property to appropriate object that you want,
let userActivity = NSUserActivity()
userActivity.userInfo = ["myKey": myAnyObject]
You will get this NSUserActivity object in parent application AppDelegate,
optional public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Swift.Void) -> Bool
Here you can get userInfo object which you can compare as per your requirement and call appropriate viewController.
I'm trying to make my app download images in background. But when I press [Home] button, the app stop download. Is there any way to make it continue download even when I use another app? I have seen some apps can do like that but I don't know how.
This is what I've tried so far.
//
// AppDelegate.swift
// Swift-TableView-Example
//
// Created by Bilal ARSLAN on 11/10/14.
// Copyright (c) 2014 Bilal ARSLAN. All rights reserved.
//
import UIKit
import WebKit
protocol DownloadInBackgroundDelegate {
func downloadInBackgroundDidFinish(chapterid:Int, chaptername:String, storyid:Int, progressPercent:Float)
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shareCache = NSURLCache()
var downloadDelegate:DownloadInBackgroundDelegate? = nil
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
var navigationBarAppearace = UINavigationBar.appearance()
application.setStatusBarOrientation(UIInterfaceOrientation.PortraitUpsideDown, animated: false)
self.startDownload()
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
FBAppEvents.activateApp()
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
func applicationDidReceiveMemoryWarning(application: UIApplication) {
NSURLCache.sharedURLCache().removeAllCachedResponses()
}
func application(application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
application.windows
}
func startDownload(){
var filesPath = [String]()
filesPath.append("https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/iphoneappprogrammingguide.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/MobileHIG.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/NetworkingOverview.pdf")
filesPath.append("https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/AVFoundationPG.pdf")
filesPath.append("http://manuals.info.apple.com/MANUALS/1000/MA1565/en_US/iphone_user_guide.pdf")
downloadFiles(0, filesPath: filesPath)
}
func downloadFiles(index: Int, filesPath: [String]) -> Void {
var imgURL: NSURL = NSURL(string: filesPath[index].stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
var fileCacheName = String(format: "%04d", index)
dispatch_async(dispatch_get_main_queue(), {
var fileExt = (data != nil && error == nil) ? Utility.checkImageType(data) : ""
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imagePath = paths.stringByAppendingPathComponent("\(fileCacheName).png")
if data.writeToFile(imagePath, atomically: false)
{
println("saved")
}
if index < filesPath.count - 1
{
var nextIndex:Int = index + 1
self.downloadFiles(nextIndex, filesPath: filesPath)
}
})
})
}
}
Answer
Based on the comment below, I found this thread : objective c - Proper use of beginBackgroundTaskWithExpirationHandler . I can solve my problem with it.
For downloading and storing of the images, instead of writing the logic yourself, I suggest you use some well known libraries, like:
https://github.com/Haneke/Haneke
https://github.com/rs/SDWebImage
Reason behind that is those libraries are well tested, quite robust and mainly very easy to use for basic tasks.
Now for the background download, there is beginBackgroundTaskWithExpirationHandler: that is specifically designed to do that. When you use it, you will get few more minutes to execute whatever you need (after that limit, your application will get terminated no matter what).
You can write following methods:
func beginBackgroundTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
When you want to use it, you just simple begin / end the task when starting / finishing the download call:
// Start task
let task = self.beginBackgroundTask()
// Do whatever you need
self.someBackgroundTask()
// End task
self.endBackgroundTask(task)
Hope it helps!
Use beginBackgroundTaskWithExpirationHandler: from the UIApplication to start a background task when the app enters the background
See Apple's document on multitasking background execution for details. See download in background in iphone its a similar question.