Is it possible to share story to instagram and copy text simultaneously (iOS, swift) - swift

I tried to do like this, but it does not work, the text is not copied
if let urlScheme = URL(string: "instagram-stories://share") {
if UIApplication.shared.canOpenURL(urlScheme) {
let imageData: Data = UIImage(systemName:"pencil.circle.fill")!.pngData()!
let items:[String: Any] = ["public.utf8-plain-text": "text","com.instagram.sharedSticker.backgroundImage": imageData]
UIPasteboard.general.setItems([items])
UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
}}
I would really appreciate any advice

2 things I can think of:
First, I am not sure the below data in your array can be properly handled by pastebin
let items:[String: Any] = ["public.utf8-plain-text": "text","com.instagram.sharedSticker.backgroundImage": imageData]
Next it seems that the activity of sharing causes data in the PasteBoard to be lost so I can offer the solution to put valid data into the PasteBoard (I am using string for example, you can use something else" from the completion handler of your sharing action, something like this might solve it:
UIApplication.shared.open(urlScheme, options: [:]) { (_) in
UIPasteboard.general.string =
"click on the screen until the paste button appears: https://google.com"
}
EDIT
It seems your set up was right and on reading the docs, IG stories should handle the Paste automatically as it seems to check the pasteboard when you execute this url scheme: instagram-stories://share - so it seems IG checks the pasteboard and performs a paste programmatically and that is why the pasteboard gets cleared.
Maybe because the image you choose is black on the black instagram background, it seems nothing is shared but with some proper image the result seems fine.
The other thing I noticed after reading their docs, they do not allow you to set captions anymore, I cannot find this key anymore public.utf8-plain-text
Another idea I can offer to share text is to convert text into an image and add it as a sticker as the sticker layer comes on top of the background image layer.
You can find multiple ways to convert text to an image and it is not relevant to your solution, here is one way I used
So bringing the code together, I have this:
// Just an example to convert text to UIImage
// from https://stackoverflow.com/a/54991797/1619193
extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: size).image { _ in
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
}
}
}
// Then inside some function of yours
func someFunction() {
if let urlScheme = URL(string: "instagram-stories://share") {
if UIApplication.shared.canOpenURL(urlScheme) {
let imageData: Data = UIImage(named: "bg")!.pngData()!
let textImage: Data = "Shawn Test".image(withAttributes: [.foregroundColor: UIColor.red,
.font: UIFont.systemFont(ofSize: 30.0)],
size: CGSize(width: 300.0, height: 80.0))!.pngData()!
let items = ["com.instagram.sharedSticker.stickerImage": textImage,
"com.instagram.sharedSticker.backgroundImage": imageData]
UIPasteboard.general.setItems([items])
UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
}
}
}
I then see this in IG stories with correct background and text as sticker which can be moved.
Only downside of using the sticker is you cannot edit the text in Instagram.

Regarding the research looks like the only one workaround to have a text/link copied in the Pasteboard when IG Story is opened is to use:
UIPasteboard.general.string = "your link here"
but you need to do it with a delay - like:
UIApplication.shared.open(instagramStoryURL, options: [:]) { success in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIPasteboard.general.string = "your link here"
}
}
to try to be sure the it won't override:
UIPasteboard.general.items
that contains, for example, "com.instagram.sharedSticker.stickerImage"
Also, please be careful with a delay - as iOS has some privacy restrictions to allow copy data to UIPasteboard when the App is in background (based on the tests we have less than 1 second to do that.
It means that you could try to copy the link this way:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
}
#objc func appMovedToBackground() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
UIPasteboard.general.string = "your link here"
}
}
Anyway, there is one obvious inconvenience.
Each time you try to call "instagram-stories://share" API the first time - you face a system popup that asks for the permisson to allow to open Instagram and also to allow paste the data.
In this case we'll lose, for example, "com.instagram.sharedSticker.stickerImage" data as by the delay it will be overrided by UIPasteboard.general.string.
But we could to make it expected for users by any UI/UX solution with instructions/guide.

Related

WatchOS complication getPlaceholderTemplate is being ignored

I have a complication working perfectly well on my Apple Watch (simulator and hardware). However, I cannot seem to get the representation to display correctly when you choose which app to assign to a complication area. In my case, graphic corner. Its showing the display name of the app with "----" under it. In my getPlaceholderTemplate protocol method, I am using CLKComplicationTemplateGraphicCornerTextImage - which is being ignored.
Here is my protocol method.
func getPlaceholderTemplate(for complication: CLKComplication,
withHandler handler: #escaping
(CLKComplicationTemplate?) -> Void)
{
let ft = CLKSimpleTextProvider(text: "Aware")
let img = UIImage(systemName: "headphones")
let tintedImageProvider = CLKImageProvider(onePieceImage: img!)
let finalImage = CLKFullColorImageProvider(fullColorImage: img!, tintedImageProvider: tintedImageProvider)
if complication.family == .graphicCorner {
let thisTemplate = CLKComplicationTemplateGraphicCornerTextImage(textProvider: ft, imageProvider: finalImage)
handler(thisTemplate)
return
} else {
print("Complication not supported.")
}
handler(nil)
}
So this seemingly isn't being used. For the Simulator I have done the "Device > Erase all content and settings" just to make sure nothing old is cached. Any idea why it's defaulting to a UI I would prefer not have? Again, this is in the complication picker only, everywhere else it's working and looking great.
Example screenshot of how it's being represented.screenshot

Can't hide share button in USDZ + QLPreviewController

I got a project that involves a few USDZ files for the augmented reality features embedded in the app. While this works great, and we're really happy with how it performs, the built-in share button of the QLPreviewController is something that we'd like to remove. Subclassing the object doesn't have any effect, and trying to hide the rightBarButtonItem with the controller returned in delegate method still shows the button when a file is selected. The implementation of USDZ + QLPreviewController we're using is pretty basic. Is there a way around this issue?
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let url = Bundle.main.url(forResource: models[selectedObject], withExtension: "usdz")! controller.navigationItem.rirButtonItems = nil.
// <- no effect return url as QLPreviewItem
}
#IBAction func userDidSelectARExperience(_ sender: Any) {
let previewController = QLPreviewController()
previewController.dataSource = self
previewController.delegate = self
present(previewController, animated: true)
}
This is the official answer from Apple.
Use ARQuickLookPreviewItem instead of QLPreviewItem. And set its canonicalWebPageURL to a URL (can be any URL).
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
guard let path = Bundle.main.path(forResource: "Experience", ofType: "usdz") else { fatalError("Couldn't find the supported input file.") }
let url = URL(fileURLWithPath: path)
if #available(iOS 13.0, *) {
let item = ARQuickLookPreviewItem(fileAt: url)
item.canonicalWebPageURL = URL(string: "http://www.google.com")
return item
} else { }
return url as QLPreviewItem
}
The version check is optional.
My approach is to add the QLPreviewController as an subview.
container is an UIView in storyboard.
let preview = QLPreviewController()
preview.dataSource = self
preview.view.frame = CGRect(origin: CGPoint(x: 0, y: -45), size: CGSize(width: container.frame.size.width, height: container.frame.size.height+45) )
container.addSubview(preview.view)
preview.didMove(toParent: self)
The y offset of the frame's origin and size may vary. This will ensure the AR QuickLook view to be the same size as the UIView, and hide the buttons (unfortunately, all of them) at the same time.
Instead of returning QLPreviewItem, use ARQuickLookPreviewItem which conforms to this protocol.
https://developer.apple.com/documentation/arkit/arquicklookpreviewitem
Then, assign a url that you would want to share (that will appear in share sheet) in canonicalWebPageURL property. By default, this property shares the file url (in this case, the USDZ file url). Doing so would not expose your file URL(s).
TLDR: I don't think you can.
I haven't seen any of the WWDC session even mention this and I can't seem to find any supporting developer documentation. I'm pretty sure the point of the ARKit QLPreviewController is so you don't have to do any actual coding on the AR side. I can see the appeal for this and for customisation in general, however, I'd suggest instead looking at some of the other ARKit projects that Apple has released and attempting to re-create those from the ground up as opposed to stripping this apart.
Please advise if this changes as I'd like to do something similar, especially within Safari.
I couldn't get to the share button at all to hide or disable it. Spent days to overcome this. I did rather unprofessional way of overcoming it. Subview QLPreviewController to a ViewController and subview a button or view on top of image view on top of share button and setting my company logo as image. It will be there all the time, even the top bar hides on full screen in AR mode. Not a clean solution. But works.

Access to Photos on iOS(Swift), have to try twice to get picture library to show up. It doesn't show up the first time, but show's the second time

I call the function. Alright
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let index = viewControllers?.index(of: viewController)
if index == 2 {
let layout = UICollectionViewFlowLayout()
let photoSelectorController = PhotoSelectorController(collectionViewLayout: layout)
let navController = UINavigationController(rootViewController: photoSelectorController)
present(navController, animated: true, completion: nil)
return false }
return true
}
Photos not showing on first time
I have all of the right things asking for permission and everything..
I then call for the images with these functions. It works, but the second time I hit the button after canceling posting a post..
I'm not sure how to get the images from the library for the first call.
After that it works like a charm, but most users have been telling me this isn't a good experience , if they have to try twice.
I'm trying to reduce friction in the app usage.
It should show the pictures right after the user "Allows" the app access to the pictures so they can post, but I'm not sure what I'm doing wrong for it to show the pictures soon as someone grants access.
var selectedImage: UIImage?
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func assetsFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 100
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: assetsFetchOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects { (asset, count, stop) in
print(asset)
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
if self.selectedImage == nil {
self.selectedImage = image
}
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
})
}
}
}
If you fetchAssets before the user grants privacy access to your app, you'll get a PHFetchResult that's empty.
However, if before making that fetch you register as a photo library observer, you'll get a photoLibraryDidChange callback as soon as the user approves privacy access for the app... from that callback you can access an updated version of your original fetch result (see changeDetails(for:)) that has all of the assets your fetch should have found. Then you can tell your UI to update and display those assets. (This is how Apple's canonical PhotoKit example code works.)
Also, once you have a populated fetch result, please don't request thumbnails for the whole thing the way you're doing.
Users commonly have photo libraries with tens of thousands of assets, many of which are in iCloud and not on the local device. If you synchronously get all thumbnails, you'll take forever, use tons of memory and CPU resources, and generate all kinds of network traffic (slowing things down even more) for resources your user may never see.
PhotoKit is designed to allow easy use in conjunction with UI elements like UICollectionView. A collection view only loads cells that are currently (or soon to be) on screen, even if you've told it you have zillions of items in your collection — similarly, you can request thumbnails only for assets that are visible in your collection view. Wherever you have your per-cell UI setup logic is where you should have your PHImageManager request. (Again, this is what the canonical PhotoKit example code does.)
You can optimize even further by "preheating" the thumbnail fetch/generation process for assets that are soon to be onscreen. And then by managing your "preheating" to cancel such work in progress when further UI updates (e.g. fast scrolling of large collection) make it unnecessary. PHCachingImageManager does this. (And yet again, it's what the canonical Apple sample does. Actually, that sample's a bit out of date, and as such does more work than it needs to on this front — it does its own calculation of what cells are just outside the scroll rect, but since iOS 10 the UICollectionViewDataSourcePrefetching protocol manages that for you.)

Display selected photos in Photos Project extension

I have been unable to find enough documentation to get started on a Photos Project extension (available in High Sierra).
How do I retrieve what a user has selected (like how Apple does it with their Prints extension), and display that in my extension's view?
I think you just want to look at PHProjectInfo which is passed to you in beginProject. That's where you get the real context of what was selected. For example:
let sections = projectInfo.sections
guard let firstContentSection = sections.first(where: { section in section.sectionType == .content }),
let firstContents = firstContentSection.sectionContents.first
You then need to convert the cloud identifiers to local ones for fetching:
localIdentifiers = context.photoLibrary.localIdentifiers(for: cloudIdentifiers)
From there, you can fetch the actual PHAssets:
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: localIdentifiers, options: nil)
For any PHAsset, when you want the image you want to use PHImageManager:
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: nil)

Media Image Loading Indefinitely

I'm currently using JSQMessagesViewController for showing the image bubble and SDImageWeb to download the images and display it. The way I'm doing this is that someone would send a person a text message containing a url and the sender and receiver would both check for that and if it matches display and download an image from the url. It would show up as an image bubble rather than text message.
Expected behavior
When sending media images or receiving I should have the images eventually load (there's a loading animation on the images). After it loads, I should easily see it next time I go on without loading since it's cached.
Actual behavior
When receiving media images, sometimes it loads right away but sometimes if it tries to load it will load indefinitely. I know it is done downloading because if I trigger a reload of collection view either through messaging something or going back to main view and coming back to the same viewcontroller it will show up. If I close my app and restart it, the pictures I have finished downloading should show up instead it will show up as loading again for indefinitely. It only happens to the ones most recent, the old ones are all okay.
Steps to reproduce
Use SD image to download images for media bubbles
Send images possibly on a slower internet where it takes more than a few milliseconds to load
Fixes I tried
The way I did this was I would receive a message containing a url and I download it and return back a JSQMessage that contains a media bubble. I tried reloading the collectionview at that index or reloading entire collectionview with a completion block in SD web but it ends up with a endless loop.
I'm not sure if this is currently a fault of SDweb or JSQMessageController. I have tried using Kingfisher as an image caching and downloading with relatively the same results.
Code
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
let message = self.messages[indexPath.item]
if (message.body!.rangeOfString ("An image https://x.com/uploads/") != nil) {
let types: NSTextCheckingType = .Link
let detector = try? NSDataDetector(types: types.rawValue)
guard let detect = detector else {
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
let matches = detect.matchesInString(message.body!, options: .ReportCompletion, range: NSMakeRange(0, message.body!.characters.count))
var stringUrl:NSURL?
stringUrl = matches[0].URL!
let tempImageView = UIImageView(image: nil)
tempImageView.sd_setImageWithURL(stringUrl, completed: nil)
let photoImage = JSQPhotoMediaItem(image: tempImageView.image)
// This makes it so the bubble can be incoming rather than just all outgoing.
if !(message.from == self.senderId) {
photoImage.appliesMediaViewMaskAsOutgoing = false
}
let message = JSQMessage(senderId: message.from, displayName: self.senderDisplayName, media: photoImage)
return message
}
let JSQTypeMessage = JSQMessage(senderId: message.from, senderDisplayName: message.from, date: message.date, text: message.body)
return JSQTypeMessage
}
I think what you what to do is use the completion handler that is built into sdWebImage
tempImageView.sd_setImageWithURL(stringUrl, completed: {
//Set your image in here because you know that you have an image
// Then reload the cell
Self.tableView.reloadCellAtIndexPath(indexPath)
})