Search code examples
iosswiftswiftui

How to receive an AirDrop text file in my app?


I want to receive an AirDrop text file, originating from my Mac (macOS 13.4), in my iOS app on my iPhone. Using Swift 5.8, Xcode 14.3 for iOS 16.

I have put this in my Info.plist

<key>UIFileSharingEnabled</key><true/>
<key>CFBundleDocumentTypes</key><array><dict>
<key>CFBundleTypeName</key><string>AirDrop Text File Type</string>
<key>LSHandlerRank</key>
  <string>Default</string>
<key>LSItemContentTypes</key><array> 
  <string>public.text</string>
</array>
</dict>
</array>

On my iPhone I can see my TestApp in the AirDrop menu, but I don't know how to receive the data.

I have this code, but I cannot get the url, or the data, or whatever I am supposed to receive. How can I get the content of the file, or even just its url, so I can read it?

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
    
struct ContentView: View {
    var body: some View {
        Text("testing")
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("\n didFinishLaunchingWithOptions \n")  // <-- can see this
        return true
    }
     
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        print("\n url: \(url) \n")  // <-- cannot see this
        return true
    }
    
    // what other func do I need here
}

Solution

  • SwiftUI uses new UIScene based UI LifeCycle, so you have to stick to UIScene events to handle it.

    You have two Options.

    1. use UISceneDelegate proxy UISceneDelegate.scene(_:openURLContexts:)

    In order to make UISceneDelegate proxy, implement UIApplicationDelegate.application(_:configurationForConnecting:options:) like below (where SceneDelegate is NSObject & UISceneDelegate but I recommand you to make it also conforms to UIWindowSceneDelegate and ObservableObject.)

        func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
            // Called when a new scene session is being created.
            // Use this method to select a configuration to create the new scene with.
            let configuration = connectingSceneSession.configuration.copy() as! UISceneConfiguration
            switch connectingSceneSession.role {
            case .windowApplication:
                
         
                // use this code to provide your AppDelegate instance to SceneDelegate
    //            var info = connectingSceneSession.userInfo ?? [:]
    //            info["proxyAppDelegate"] = self
    //            connectingSceneSession.userInfo = info
                
                configuration.delegateClass = SceneDelegate.self
            case .windowExternalDisplay:
                fallthrough
            default:
                break            
            }
    
            return configuration
        }
    

    In your SceneDelegate

        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
            // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
            // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
            // use this code to retrive the AppDelegate
            let appDelegate = session.userInfo?["proxyAppDelegate"] as? AppDelegate
            // use this codes to provide your SceneDelegate for later use in UIKit way
            var sessionInfo = session.userInfo ?? [:]
            sessionInfo["proxySceneDelegate"] = self
            session.userInfo = sessionInfo
            // use this to handle url actions like universal link
            connectionOptions.urlContexts
    
    
        }
    
    
        func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
            // use this method to handle url actions performed by this scene
        }
        
    

    If your SceneDelegate conforms to NSObject and ObservableObject then SwiftUI inject your SceneDelegate as EnvironmentObject in your View. If you provide your SceneDelgate to UISceneSession.userInfo. you can get that instance later by using UIApplication.shared.connectedScenes or UIApplication.shared.openSessions

    1. use SwiftUI URL event handler.

    View.onOpenURL(perform:)