Deep-linking on xcode 11 beta5 and swiftui - deep-linking

I wanted to play a bit with SwiftUI and deep-linking, with passing data between two apps. But it seems not to work.
I build two small apps. First app with a textfield and a button, second app with a label and a button. When I press the button in the first app, it calls the second app via the given url scheme and vice versa.
First App looks like that...
struct ContentView: View {
#State var message: String = ""
var body: some View {
Group {
TextField("Enter message...", text: $message).textFieldStyle(.roundedBorder).padding()
Button(action: {
// go to second app
let application = UIApplication.shared
let secondAppPath = "second://\(self.message)"
let appUrl = URL(string: secondAppPath)!
application.open(appUrl, options: [ : ], completionHandler: nil)
}) { Text("Go to second app")}
.padding()
.border(Color.black, width: 2)
}
}
}
and the info.plist...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>second</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>first</string>
</array>
</dict>
</array>
Second app info.plist
<key>LSApplicationQueriesSchemes</key>
<array>
<string>first</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>second</string>
</array>
</dict>
</array>
And when I press the button in the first app, this function in the second app should print the url like: "second://somethingfromthetextfield"
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print(url)
return true
}
But its not doing it. The second app starts but didn't print anything. Now i wondering, am I doing something wrong? Is it working anyways on Xcode beta and SwiftUI or does it work differently?

Try to use the following method in the SceneDelegate:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
print("\(URLContexts.first!.url)")
}

Related

Xcode 12 project with iOS 13 SwiftUI support: SceneDelegate functions not invoked

I had created a project in Xcode 12 with iOS 14 as deployment target (bigger project). After some time I realized that I had to also be able to support iOS 13 devices. My project uses SwiftUI.
It is apparently not very straight-forward to simply change the deployment target to iOS 13 as some of the code generated by Xcode 12 is not backwards compatible with iOS 13.
So I essentially need to convert my Xcode 12-based project (currently with deployment target iOS 14.0) to also support iOS 13.
Here's what I did to prepare for my project's downgrade:
Install Xcode 11.7, create new project with SwiftUI support and ensure the deployment target is iOS 13.0. Thereby I know what Xcode believes the "iOS 13"-way should be, i.e., using AppDelegate and SceneDelegate.
Run that app - works fine on my iOS 14 device.
Open Xcode 12, create new project with SwiftUI support, lower the deployment target to iOS 13.0 (because it's default set to 14.x), build it, and be presented with a bunch of compilation issues (as expected).
Changes then made to the Xcode 12 project:
Comment out everything in the <project>App.swift file.
Add AppDelegate.swift file with the same contents from the Xcode 11.7-generated project mentioned above.
Add SceneDelegate.swift file with the same contents from the Xcode 11.7-generated project mentioned above.
Add a LaunchScreen.storyboard file (because Xcode warned about its non-existence).
Run the app.
The app runs, but the contents of ContentView is not shown. Just a black screen (black is probably because my device is in dark mode). Also, as you'll notice in the code below, I've inserted debug logs but only "didFinishLaunchingWithOptions" is printed in the console - that is, the scene delegate methods are not invoked, it seems.
I've (of course) Googled a lot, tried various things with Info.plist, but nothing seems to help.
Code:
AppDelegate.swift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("didFinishLaunchingWithOptions")
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
print("configurationForConnecting")
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
print("didDiscardSceneSessions")
}
}
SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
print("scene willConnectTo")
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {
print("sceneDidDisconnect")
}
func sceneDidBecomeActive(_ scene: UIScene) {
print("sceneDidBecomeActive")
}
func sceneWillResignActive(_ scene: UIScene) {
print("sceneWillResignActive")
}
func sceneWillEnterForeground(_ scene: UIScene) {
print("sceneWillEnterForeground")
}
func sceneDidEnterBackground(_ scene: UIScene) {
print("sceneDidEnterBackground")
}
}
ContentView.swift
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.foregroundColor(.blue)
.padding()
.onAppear {
print("ContentView")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string></string>
<key>LSApplicationCategoryType</key>
<string></string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
If you've already built app using SwiftApp before implementation SceneDelegate, just uninstall and rebuild it. And then, you can resolve this problem.
persistentIdentifier will be generated by the system for creating new Scene when an app is launched at the first time. After that, the identifier will be same one every app open. So the app doesn't need to create new Scene.
That's why configurationForConnecting wasn't invoke but set up no rootview so it happens that your app shows black window.
The following WWDC19 session video(around 31:40) says about the identifier.
https://developer.apple.com/videos/play/wwdc2019/212/?time=1900.
Hope it will help your problem.
Thank you.
I'm going to guess that you forgot to add the scene delegate file to the app target. When you build and run, look in the console and see if there's a complaint about not finding the scene delegate. That would explain the phenomenon.

userDidAcceptCloudKitShareWith not being called

func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {...}
does not get called after clicking "OK" on the link to a shared cloudkit record. The app will open, but this function (which is supposed to be called) is not.
CKSharingSupported:YES is listed in my info.plist
I've put the record into a zone called "sharedZone"
SharedZone is showing up in my zone list, on my Private database.
The shared record shows me as being "invited"
Tried deleting app and cloud data and reinstalling, no change
func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
print("you accepted something")
let acceptShareOperation: CKAcceptSharesOperation = CKAcceptSharesOperation(shareMetadatas:[cloudKitShareMetadata])
//acceptShareOperation.qualityOfService = .userInteractive
acceptShareOperation.perShareCompletionBlock = {meta, share,
error in
print("share was accepted")
}
acceptShareOperation.acceptSharesCompletionBlock = {
error in
/// Send your user to where they need to go in your app
print("share accepted completion block!")
}
CKContainer(identifier: cloudKitShareMetadata.containerIdentifier).add(acceptShareOperation)
}
Expected result: AT LEAST seeing the line "you accepted something" printed to the console.
UPDATE: Reverting back to IOS11.3 doesn't make it work.
UPDATE: I downloaded a tutorial someone had built that uses cloudkit and (after some finagling, got theirs to work. I then gutted their code and put mine in instead, and NOW the function works properly, so I think the issue may have something to do with either:
XCODE 11 beta, or
The new coredata + cloudkit syncing feature apple added.
If you use fresh Xcode 11 project, there is SceneDelegate.swift.
You need to implement there:
internal func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) { }
If you want to get userDidAcceptCloudKitShareWith called inside AppDelegate.swift you have to remove from Info.plist file next lines:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
UPDATED
If you are trying to accept share on cold start using Scene Manifest, CKShare.Metadata will be passed to func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) in connectionOptions.
To make this work you have to use SceneDelegate.
There are 3 steps to get scenedelegate working.
1 - Add Appdelegate
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("abc")
return true
}
}
Don't forget to use a delegate adaptor if you use SwiftUI way of entering the program.
struct ExampleApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
MainView()
}
}
Also don't forget to add this to your appdelegate class:
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if options.userActivities.first?.activityType == "newWindow" {
let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
} else {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
2 - Configure info.plist to handle scenes through a SceneDelegate
add these to your info.plist
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
3 - Now add SceneDelegate and get the delegate callbacks that you need
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
print("abcd")
}
}
}
note: in info.plist and in your appdelegate code there is a reference to "default configuration". You can change this to whatever you want (it's meant to enable management of more than one scene), but they should be the same off course.
After 2 days of trial and error I got it working.
The issue seems to be related to Apples new Core Data + Cloudkit syncing abilities. They seem to gloss over the appdelegate function I need to call.
I fixed the issue by rebuilding the app in Xcode 10, then opening it in XCode 11 without changing anything. I'll let Apple know too.
Thank me. I'm welcome.

How to check & open installed parent app in your device from child app?

I'have parent & child app in my device.I need to check and open parent app. In my app delegate(when app is launched), I am checking if the app is installed then open parent app otherwise go to app store.
I have tried below code to check if app is installed or not in your device.
I was created URL Types, as given-
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>Parent</string>
</array>
<key>CFBundleURLName</key>
<string>parentAppBundleid</string>
</dict>
</array>
I have written following code in didFinishLaunchingWithOptions-
func openGoYoApp() {
let parentapp = "Parent://"
let parentAppUrl = URL(string: parentapp)
if UIApplication.shared.canOpenURL(parentAppUrl! as URL)
{
UIApplication.shared.open(parentAppUrl!)
}
else {
//redirect to safari because the user doesn't have Instagram
print("App not installed")
UIApplication.shared.open(URL(string: "ituneLink")!)
}
}
In the given condition, if is returning true and UIApplication.shared.open(parentAppUrl!) is executed but its not opening my parent app.
The parent app's info plist should have something like this:
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLIconFile</key>
<string>temp</string>
<key>CFBundleURLName</key>
<string>com.parent-app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>parent</string>
</array>
</dict>
</array>
and in your parent app AppDelegate, you should handle it from here:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print("open url: \(options)")
if let scheme = url.scheme, scheme == "parent" {
// handle url scheme
return true
}
return false
}
then in your child app:
// add this to button action or where ever you want it to trigger the 'open parent app scheme'
let urlString = "parent://info-to-pass-to-parent"
if let schemeURL = URL(string: urlString) {
if UIApplication.shared.canOpenURL(schemeURL) {
UIApplication.shared.open(schemeURL, options: [:], completionHandler: { (success) in
print("open success: \(success)")
})
}else{
//redirect to safari because the user doesn't have Instagram
print("App not installed")
UIApplication.shared.open(URL(string: "ituneLink")!)
}
}

How to open app from email URL in iOS 9?

I've tried something like:
In the AppDelegate:
func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool {
var returnValue = false
let urlString = url.absoluteString
print(urlString)
if(urlString.hasPrefix("")){
returnValue = true
}
return returnValue
}
In the info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>co.example.ExampleApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>example</string>
</array>
</dict>
</array>
But doesn't work. I want the app opens from a link that I send via email, anyone has an idea to make it work?
For future users, as #iSashok mentioned in the comments, the handleOpenURL function is deprecated as of iOS 9.
For newer versions of iOS you need to use the following function (Apple Docs):
optional func application(_ app: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
You'll be able to keep the body of the function the same as before, and now it will work.

App crash on using Fabric - Cannonball in Swift?

I've got this error when ran CannonBall: app here
'[Fabric] Value of Info.plist key "Fabric" must be a NSDictionary.'
Appdelegate.swift:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
let welcome = "Welcome to Cannonball! Please onboard with the Fabric Mac app. Check the instructions in the README file."
//assert(NSBundle.mainBundle().objectForInfoDictionaryKey("Fabric") != nil, welcome)
Fabric.with([Crashlytics(), Twitter(), Digits(), MoPub()])//error here
if Twitter.sharedInstance().session() == nil && Digits.sharedInstance().session() == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let signInViewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier("SignInViewController")
window?.rootViewController = signInViewController as? UIViewController
}
return true
}
I get a suggest is "missing Fabric APIKey from your info.plist".
But i can't find Fabric key in my info.plist.
Info.plist:
Add this code to your .plist file:
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>Your-key</string>
<key>Kits</key>
<array>
<dict>
<key>KitInfo</key>
<dict/>
<key>KitName</key>
<string>Crashlytics</string>
</dict>
<dict>
<key>KitInfo</key>
<dict>
<key>consumerKey</key>
<string>Your-consumerKey</string>
<key>consumerSecret</key>
<string>Your-consumerSecret</string>
</dict>
<key>KitName</key>
<string>Twitter</string>
</dict>
<dict>
<key>KitInfo</key>
<dict>
<key>consumerKey</key>
<string>Your-consumerKey</string>
<key>consumerSecret</key>
<string>Your-consumerSecret</string>
</dict>
<key>KitName</key>
<string>Digits</string>
</dict>
</array>
</dict>

Resources