4

I am creating an iOS Application iMessage Extension.

According to Example by Apple, I creating a message according to provided logic

guard let url: URL = URL(string: "http://www.google.com") else { return }

let message = composeMessage(url: url)
activeConversation?.insert(message, completionHandler: { [weak self] (error: Error?) in
    guard let error = error else { return }
    self?.presentAlert(error: error)        
})

also

private func composeMessage(url: URL) -> MSMessage {
    let layout = MSMessageTemplateLayout()
    layout.caption = "caption"
    layout.subcaption = "subcaption"
    layout.trailingSubcaption = "trailing subcaption"

    let message = MSMessage()
    message.url = url
    message.layout = layout

    return message
}

and

private func presentAlert(error: Error) {
    let alertController: UIAlertController = UIAlertController(
        title: "Error",
        message: error.localizedDescription,
        preferredStyle: .alert
    )

    let cancelAction: UIAlertAction = UIAlertAction(
        title: "OK",
        style: .cancel,
        handler: nil
    )

    alertController.addAction(cancelAction)

    present(
        alertController,
        animated: true,
        completion: nil
    )
}

As far as I understand, after message is sent, on a click, Safari browser should be opened.

When I click on a sent message, MessageViewController screen takes place in whole screen, without opening safari or another app.

Where is the problem? How can I achieve desired functionality?

Daumantas Versockas
  • 797
  • 1
  • 10
  • 29

6 Answers6

3

Here is the code I use to open a URL from a iMessage extension. It is currently working to open the Music app in the WATUU iMessage application. For instance with the URL "https://itunes.apple.com/us/album/as%C3%AD/1154300311?i=1154300401&uo=4&app=music"

This functionality currently works in iOS 10, 11 and 12

func openInMessagingURL(urlString: String){
    if let url = NSURL(string:urlString){
        let context = NSExtensionContext()
        context.open(url, completionHandler: nil)
        var responder = self as UIResponder?

        while (responder != nil){
            if responder?.responds(to: Selector("openURL:")) == true{
                responder?.perform(Selector("openURL:"), with: url)
            }
            responder = responder!.next
        }
    }
}

UPDATE FOR SWIFT 4

func openInMessagingURL(urlString: String){
    if let url = URL(string:urlString){
        let context = NSExtensionContext()
        context.open(url, completionHandler: nil)
        var responder = self as UIResponder?

        while (responder != nil){
            if responder?.responds(to: #selector(UIApplication.open(_:options:completionHandler:))) == true{
                responder?.perform(#selector(UIApplication.open(_:options:completionHandler:)), with: url)
            }
            responder = responder!.next
        }
    }
}
Julio Bailon
  • 3,735
  • 2
  • 33
  • 34
  • 1
    Thank you but I think you will find that the context.open can be removed as it's not doing anything. The code which walks up the tree to find who responds to openURL is what works. – Andy Dent Mar 06 '19 at 09:40
  • Posted an answer below, thanks, updated for current iOS and Swift 4, using your technique – Andy Dent Mar 07 '19 at 23:29
2

I think safari Browser only opens for macOS. This worked for me:

override func didSelectMessage(message: MSMessage, conversation: MSConversation) {

        if let message = conversation.selectedMessage {
            // message selected

            // Eg. open your app:
            let url = // your apps url
            self.extensionContext?.openURL(url, completionHandler: { (success: Bool) in

            })
        }
    }
benaneesh
  • 426
  • 6
  • 10
2

Using the technique shown by Julio Bailon

Fixed for Swift 4 and that openURL has been deprecated.

Note that the extensionContext?.openURL technique does not work from an iMessage extension - it only opens your current app.

I have posted a full sample app showing the technique on GitHub with the relevant snippet here:

    let handler = { (success:Bool) -> () in
        if success {
            os_log("Finished opening URL")
        } else {
            os_log("Failed to open URL")
        }
    }

    let openSel = #selector(UIApplication.open(_:options:completionHandler:))
    while (responder != nil){
        if responder?.responds(to: openSel ) == true{
            // cannot package up multiple args to openSel so we explicitly call it on the iMessage application instance
            // found by iterating up the chain
            (responder as? UIApplication)?.open(url, completionHandler:handler)  // perform(openSel, with: url)
            return
        }
        responder = responder!.next
    }
Andy Dent
  • 17,578
  • 6
  • 88
  • 115
  • Look at the sample app I linked to and see where it is in that code. It's too hard to explain out of context. Those samples are carefully crafted to demonstrate just one main point per sample. https://github.com/AndyDentFree/im-plausibilities/tree/master/webFromIM – Andy Dent Jul 26 '19 at 07:12
  • This doesn't seem to work in Xcode 11. Getting the error "'open(_:options:completionHandler:)' is unavailable in application extensions for iOS" – NSExceptional Oct 20 '19 at 00:28
  • I noticed that too - see https://stackoverflow.com/questions/58153341/xcode11-error-open-optionscompletionhandler-is-unavailable-in-application – Andy Dent Oct 20 '19 at 18:52
1

It seems it is not possible to open an app from a Message Extension, except the companion app contained in the Workspace. We have tried to open Safari from our Message Extension, it did not work, this limitation seems by design.

You could try other scenari to solve your problem :

  1. Webview in Expanded Message Extension

    You could have a Webview in your Message Extension, and when you click on a message, you could open the Expanded mode and open you Url in the Webview.

The user won't be in Safari, but the page will be embedded in your Message Extension.

  1. Open the Url in the Companion App

    On a click on the message, you could open your Companion app (through the Url Scheme with MyApp://?myParam=myValue) with a special parameter ; the Companion app should react to this parameter and could redirect to Safari through OpenUrl.

In this case, you'll several redirects before the WebPage, but it should allow to go back to the conversation.

We have also found that we could instance a SKStoreProductViewController in a Message Extension, if you want to open the Apple Store right in Messages and let the user buy items.

Christian Navelot
  • 1,174
  • 9
  • 9
1

If you only need to insert a link, then you should use activeConversation.insertText and insert the link. Touching the message will open Safari.

radar
  • 500
  • 1
  • 6
  • 24
-1
  1. openURL in didSelectMessage:conversation: by using extensionContext

  2. handle the URL scheme in your host AppDelegate

handrenliang
  • 1,047
  • 1
  • 10
  • 21
  • inherited method `didSelectMessage:conversation:` is not called when the desired message is tapped. It is triggered, when message is selected before being sent. What is more, handling URL scheme in host app delegate is even worse, host app might haven't be even launched, despite the fact, that is in a background as a best case scenario... – Daumantas Versockas Oct 07 '16 at 12:47
  • 1
    @DaumantasVersockas When you tap sent message, you should handle it on didBecomeActiveWithConversation: method, you can get selectedMessage from Conversion. Using extensionContext openURL will launch your host app. – handrenliang Oct 07 '16 at 15:20