Expensify / react-native-share-menu

A module for React Native that adds your app to the share menu of the device
MIT License
655 stars 239 forks source link

iOS 18 No Longer Opens App #318

Open jaaywags opened 1 month ago

jaaywags commented 1 month ago

tldr: solution at bottom

I had set this extension up originally for my app running iOS 17.

I did it a tiny bit different than what the docs suggested just because all I wanted to was be able to open Safari, click share, select my app, and it receive the URL from Safari. For this I basically used the same ShareViewController file but modified it slightly. More on this here.

Anyway, I noticed after I upgraded my device to iOS 18, nothing would happen after clicking my app in the share sheet. It still shows up but nothing happened.

Turns out the issue was that an underlining function, openUrl, called in ShareViewController has been deprecated and I guess fully removed in iOS 18. I was getting this error when I debugged my extension in XCode:

BUG IN CLIENT OF UIKIT: The caller of UIApplication.openURL(_:) needs to migrate to the non-deprecated

Solution

To fix this, I did a few things.

 // ...
 import UIKit
 import Social
 import RNShareMenu

+@available(iOSApplicationExtension, unavailable)
+
 class ShareViewController: UIViewController {
   var hostAppId: String?
   var hostAppUrlScheme: String?

   // ..

   internal func openHostApp() {
     guard let urlScheme = self.hostAppUrlScheme else {
       exit(withError: NO_INFO_PLIST_URL_SCHEME_ERROR)
       return
     }
+
+    guard let url = URL(string: urlScheme) else {
+      exit(withError: NO_INFO_PLIST_URL_SCHEME_ERROR)
+      return //be safe
+    }
+ 
+    UIApplication.shared.open(url, options: [:], completionHandler: completeRequest)
-    
-    let url = URL(string: urlScheme)
-    let selectorOpenURL = sel_registerName("openURL:")
-    var responder: UIResponder? = self
-    
-    while responder != nil {
-      if responder?.responds(to: selectorOpenURL) == true {
-        responder?.perform(selectorOpenURL, with: url)
-      }
-      responder = responder!.next

-    completeRequest()
   }

-  func completeRequest() {
+  func completeRequest(success: Bool) {
     // 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.
     extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
   }

   func cancelRequest() {
     extensionContext!.cancelRequest(withError: NSError())
   }
}

This works for me. Opening the app is now powered by this function. Apparently this function has been supported since iOS 10 so I see no reason to add any version specific code.

I am in no way a swift developer. I just poked around until it worked. If anyone is, please provide feedback.

Hope this helps someone!

jaaywags commented 1 month ago

Potentially worth noting in the completeRequest(success: Bool) function, we should probably check the success is true or not. If it is false, maybe call cancelRequest()

kueda commented 1 week ago

@jaaywags, thanks for this solution, I think it will work for us. I'm not particularly knowledgeable about Swift or iOS, so can you explain what @available(iOSApplicationExtension, unavailable) is doing? According to the docs, that's saying this class is not available in an iOS application extension... except it is, right? Without it I get an error that says 'shared' is unavailable in application extensions for iOS, except your code works, so clearly shared is available. Is this code somehow running in the application context and not in the extension context?

jaaywags commented 1 week ago

can you explain what @available(iOSApplicationExtension, unavailable) is doing?

@kueda - Glad this works for you! I am not a Swift developer either so I couldn't tell you with 100% certainty. I played around with the code a lot and looked at other examples on GitHub until I got something working.

My understanding about that line is the same, and I had the same error when I tried to remove it. What I think it is doing is just changing how the extensions gets compiled. See this comment.

attribute to declarations using app extension unavailable APIs in order to get them to compile in a way that works for both apps and app extensions.

Sometimes I do things that work without knowing why and I wish I had an expert in that area sitting next to me that I could ask lol

ndv23 commented 1 week ago

Hi ! @jaaywags Thank you very much for your fix! However, I am not an iOS dev and when I try to apply your fix, I get an error the during building phase:

'ShareViewController' is unavailable in application extensions for iOS

It seems @available(iOSApplicationExtension, unavailable) makes UIApplication.shared available but causes unavailability of ShareViewController

Just to test I tried replacing UIApplication.shared.open with extensionContext?.open, this allows me to successfully build the app but doesn't open the link, so it doesn't help me much haha

I have to admit I don't understand much. Does anyone have the same problems? Does anyone know why this error is occurring please ? Thanks all !

visoft commented 5 days ago

@ndv23, what version of Xcode do you have? I don't see the problem you are having. I'm running Xcode Version 16.0 (16A242d)

ndv23 commented 4 days ago

Hello @visoft thanks for your message but unfortunately, i have the exact same version 16.0 (16A242d) :/