UIActivityViewController Copy copies item twice on iOS 11 - ios

I am using UIActivityViewController to share a webview url on iOS 11 and when using Copy then go to Messages app then paste, it is pasted twice.
Out of curiosity I tried to do the same with Safari, Chrome, Firefox, and also for other apps than messages. The result was interesting:
Coping from Safari always works in any app
Coping from Chrome or FireFox works in Notes app, but it duplicates the copied text in any app that has TextField (Messages, WhatsApp, Slack, Signal,...)
Here is my simple code
func shareURL(title: String, url: URL) {
var activityItems = [AnyObject]()
activityItems.append(TitleActivityItemProvider(title: title))
activityItems.append(url as AnyObject)
let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
and here is the TitleActivityItemProvider class
class TitleActivityItemProvider: UIActivityItemProvider {
static let activityTypesToIgnore = [UIActivityType.copyToPasteboard]
init(title: String) {
super.init(placeholderItem: title)
}
override var item : Any {
if let activityType = activityType {
if TitleActivityItemProvider.activityTypesToIgnore.contains(activityType) {
return NSNull()
}
}
return placeholderItem! as AnyObject
}
override func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
return placeholderItem as! String
}
}
is it a bug on iOS 11 or should I consider doing specific change when working with UIActivityViewController
==UPDATE==
I noticed when I comment adding TitleActivityItemProvider it works fine and when I add it, it duplicates the url however I ignore UIActivityType.copyToPasteboard in the title provider and return NSNull()

So I fixed it by using url.absoluteString when adding item to activityItems
activityItems.append(url.absoluteString as AnyObject)

Are you testing on the Simulator? On the simulator I had this issue, but on device it only pastes once.

Related

Firebase Dynamic Links with Social Metadata not showing image on iMessage (iOS Messages)

I'm having issues with dynamic links and social metadata. Seems to work fine when sharing on Facebook... image is attached properly. But when I share using Messages on iOS... no image appears. Just the Firebase folder logo.
I had the exact same issue and I found a way to show preview image in iMessage by creating LPLinkMetadata of LinkPresentation. It seems to be working even though it's not solving the core issue.
Basically, I downloaded the image to show in the preview first and create LPLinkMetadata based on that image.
let image = UIImage(data: data)! //Image to show in preview
let metadata = LPLinkMetadata()
metadata.imageProvider = NSItemProvider(object: image)
metadata.originalURL = url //dynamic links
metadata.title = "Holland Bloorview Kids Rehabilitation Hospital on Flixxaid"
let metadataItemSource = LinkPresentationItemSource(metaData: metadata)
let activity = UIActivityViewController(activityItems: [metadataItemSource], applicationActivities: [])
self.present(activity, animated: true)
And LinkPresentaionItemSource is from this blog.
class LinkPresentationItemSource: NSObject, UIActivityItemSource {
var linkMetaData = LPLinkMetadata()
//Prepare data to share
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
return linkMetaData
}
//Placeholder for real data, we don't care in this example so just return a simple string
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return "Placeholder"
}
/// Return the data will be shared
/// - Parameters:
/// - activityType: Ex: mail, message, airdrop, etc..
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return linkMetaData.originalURL
}
init(metaData: LPLinkMetadata) {
self.linkMetaData = metaData
}
}

Swift - How to share app link as in the App Store

I'm trying to share my app using UIActivityViewController but I can't reproduce the same effect as when I share an app from the App Store, meaning :
When I clicked the share button in the App Store I have something that looks like this :
But when I try to share my app I have this :
The code that I used was :
if let logo = UIImage(named: "myLogo"), let websiteURL = URL(string: "https://itunes.apple.com/app/idxxxxxxxxxx") {
let objectsToShare = ["My App Name", websiteURL, logo] as [Any]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: [])
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
present(activityVC, animated: true)
}
The if let popoverController = ... loop is for preventing a crash when using iPads.
What do I have to change in order to have the effect as the App Store? (to have an image with a title and a subtitle)
Moreover, once I share the app with Messages for instance, this is the difference :
How can I have the same effect? (A single image with the title and subtitle, an as a bonus, a video). I'm not sure if this is an iOS 13 problem, since all similar questions don't have the same app sharing popover.
You have to use the new LinkPresentation framework.
Which essentially involves UIActivityItemSource conformance then retrieving the metadata that will encompass the Activity View and the data you are sharing. Data can be retrieved locally or downloaded.
ExampleController: UIViewController {
var metadata: LPLinkMetadata?
func share() {
let activityView = UIActivityViewController(activityItems: [self], applicationActivities: nil)
present(activityView, animated: true)
}
...
}
extension ExampleController: UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return metadata
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return metadata
}
func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
metadata = LPLinkMetadata()
metadata.title = "Title"
metadata.originalURL = URL(string: "Description")
metadata.url = metadata.originalURL
// Using a locally stored item
metadata.iconProvider = NSItemProvider(object: UIImage(named: "image")!)
metadata.imageProvider = NSItemProvider.init(contentsOf:
Bundle.main.url(forResource: "image", withExtension: "JPG"))
return metadata
}
}
Docs:
https://developer.apple.com/documentation/uikit/uiactivityitemsource/3144571-activityviewcontrollerlinkmetada
WWDC Presentation:
https://developer.apple.com/videos/play/wwdc2019/262/
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
You can use this.

How to set different parameters in WhatsApp sharing with UIActivityViewController in iOS Swift?

I am using UIActivityViewController for sharing contents from app.
I want to share different content with different sharing apps.
Like in Message output will be like this.image + text + URL
in Whatsapp i would like to share like below image text + URL
How can i do this? See below screen shots for this.
It took me a good time figuring this out, but that's how it worked for me:
Think of this problem in two steps: first, we need to tell to the UIActivityViewController which content we want to share. Second, we need to return the content based on each social media, either a link, an image or a text. Its up to the social media app to tell which content it can handle, and it will only show up if we share the right kind of content.
In the first step, we will try to fool the social media apps, saying that we want to share an UIImage and a NSObject. This will open most of the social media apps to share.
In the second step, we will identify which social media app the user has clicked, and return the appropriated content for it.
Implementation:
create two UIActivityItemSource, one which will return an UIImage and other which will return the NSObject.
class SocialActivityItem: NSObject, UIActivityItemSource {
var img: UIImage?
var url: URL?
convenience init(img: UIImage, url: URL) {
self.init()
self.img = img
self.url = url
}
// This will be called BEFORE showing the user the apps to share (first step)
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return img!
}
// This will be called AFTER the user has selected an app to share (second step)
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType?) -> Any? {
//Instagram
if activityType?.rawValue == "com.burbn.instagram.shareextension" {
return img!
} else {
return url
}
}
}
and
class TextActivityItem: NSObject, UIActivityItemSource {
var textToShare: String?
convenience init(textToShare: String) {
self.init()
self.textToShare = textToShare
}
// This will be called BEFORE showing the user the apps to share (first step)
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return NSObject()
}
// This will be called AFTER the user has selected an app to share (second step)
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType?) -> Any? {
var text = ""
if activityType?.rawValue == "net.whatsapp.WhatsApp.ShareExtension" {
text = "Sharing on Whatsapp"
}
if activityType == UIActivityType.postToFacebook {
text = "Sharing on Facebook"
}
return text
}
}
Then, you just need to set everything up:
let url = URL(string: "www.google.com")!
let socialProvider = SocialActivityItem(img: img, url: url)
let textProvider = TextActivityItem(textToShare: "Sharing on social media!")
let activityViewController = UIActivityViewController(activityItems: [socialProvider, textProvider], applicationActivities: nil)

How to make iMessage to open my app with a custom document type

EDIT: As requested added info.pslist and code.
I have a custom Document Type and I have registered the UTIs and a new MIME type. I basically followed the steps by this tutorial. I am using a Codable object that it is no more than a JSON file with a custom extension and a custom icon. Honestly it looks pretty cool to me. The app I am doing is is a Grocery list app that makes a lot of sense being able to share it by a note or iMessage.
Like the finalised app in the tutorial I followed it opens in mail and even in notes!!! but iMessage does not recognise the extension and shows a folder icon and does not open it.
My question is how can I tell iMessage that this file is meant to be opened by my App. Do I need an iMessage extension? I am pretty new to iOS. info.pslist:
And now code:
func exportToUrl() -> URL? {
let contents = try? JSONEncoder().encode(shoppingList)
guard let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
let saveUrl = path.appendingPathComponent("/list.grabgrocerieslist")
try? contents?.write(to: saveUrl, options: .atomic)
return saveUrl
}
#IBAction func sharedTapped(_ sender: UIBarButtonItem) {
guard let url = exportToUrl() else {
return
}
let activityController = UIActivityViewController(activityItems: ["Shopping List", url], applicationActivities: nil)
activityController.excludedActivityTypes = [.assignToContact, .saveToCameraRoll, .postToFacebook ]
activityController.popoverPresentationController?.barButtonItem = sender
self.present(activityController, animated: true, completion: nil)
}
Many Thanks,

Swift 3 iMessage Extension doesn't open URL

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?
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
}
}
}
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
})
}
}
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
}
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 :
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.
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.
If you only need to insert a link, then you should use activeConversation.insertText and insert the link. Touching the message will open Safari.
openURL in didSelectMessage:conversation: by using extensionContext
handle the URL scheme in your host AppDelegate

Resources