How share a screenshot to Facebook using Swift? - ios

I need to have a Facebook share button on one of my app's view controllers so that when the user pushes it, it will share a screenshot of the user's current screen to Facebook.
I have been watching a few tutorials such as this one on how to implement a Facebook share button: https://www.youtube.com/watch?v=774_-cTjnVM
But these only show how I can share a message on Facebook, and I'm still a little bit confused how to share the whole screen that user is currently interacting with.

Sharing directly to Facebook isn't hard to do. First, import the Social framework:
import Social
Now add this as the action for your button:
let screen = UIScreen.mainScreen()
if let window = UIApplication.sharedApplication().keyWindow {
UIGraphicsBeginImageContextWithOptions(screen.bounds.size, false, 0);
window.drawViewHierarchyInRect(window.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
let composeSheet = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
composeSheet.setInitialText("Hello, Facebook!")
composeSheet.addImage(image)
presentViewController(composeSheet, animated: true, completion: nil)
}
You might be interested to know that UIActivityViewController lets users share to Facebook but also other services. The code above is for your exact question: sharing to Facebook. This code renders the entire visible screen; you can also have individual views render themselves if you want.
Note: As Duncan C points out in a comment below, this rendering code won't include anything outside your app, such as other apps or system controls.

In iOS 8 and earlier there used to be a private framework that you could use to capture the entire screen. Using that framework would cause your app to be rejected for the app store, but at least it worked.
Starting in iOS 9 that API no longer works
The best you can do is to capture your app's views. That won't include the status bar or other things drawn by the system or other apps.
One way is to create an off-screen context, render the parent view you want to capture into the off-screen context (probably using drawViewHierarchyInRect:afterScreenUpdates:, load the data from the context into a UIImage, and then close the context.
Another way is a new API that will capture a snapshot of a view hierarchy. One of the new methods to capture a snapshot is snapshotViewAfterScreenUpdates. That creates specialized snapshot view.

swift 3
let screen = UIScreen.main
if let window = UIApplication.shared.keyWindow {
UIGraphicsBeginImageContextWithOptions(screen.bounds.size, false, 0);
window.drawHierarchy(in: window.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
let composeSheet = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
composeSheet?.setInitialText("Hello, Facebook!")
composeSheet?.add(image)
present(composeSheet!, animated: true, completion: nil)
}

Related

Take snapshot of PDF viewer on iOS12

Good afternoon,
I am trying to take a snapshot of a PDF but I am facing some difficulties to access view's content on iOS 12.
In order to display the PDF's content, I've already used two different approaches:
UIDocumentInteractorController
Webview
On iOS 11, I couldn't already take a snapshot of a UIDocumentInteractorController view and the best answer that I could find was this one https://stackoverflow.com/a/13332623/2568889. In short, it says that the document viewer runs in an external process and on its own windows that the main app process doesn't have access to.
The WebView was the solution at that time, until iOS 12 came. While testing on real devices running iOS 12, I had the same issue of not being able to access the viewer's content while taking the snapshot. While inspecting the view hierarchy, it looks like we have a childviewcontroller (PDFHostViewController) that is the one rendering the actual view.
Please take into account that this issue is just happening for PDFs, on regular webpages is working fine!
Code used to take snapshot:
private extension UIView {
func takeSnapshot() -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.opaque = self.isOpaque
let renderer = UIGraphicsImageRenderer(size: self.frame.size, format: format)
return renderer.image { context in
self.drawHierarchy(in: self.frame, afterScreenUpdates: true)
}
}
}
Note: I have also tried to use the native Webview.takeSnapshot(with:,completionHandler) of the web view but it just works for regular webpages, not PDFs
Maybe it works with Apple's PDFKit. As far as i know the PDFView is a subclass of UIView.
import PDFKit
#IBOutlet var pdfView: PDFView!
let pdfDocument = PDFDocument(url: url)
pdfView.document = pdfDocument
And then your extension as PDFView extension

iOS 11 Screenshot Markup

iOS 11 added a markup option after taking a screenshot, how can I programmatically apply this option after programmatically taking a screenshot? it directly gets saved to photos without providing the markup/share option.
I use the code below to take and save the screenshot
#IBAction func takeScreenshot(_ sender: Any) {
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
layer.render(in: UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(screenshot!, nil, nil, nil)
}
Instant Markup is not documented anywhere in Apple's Reference Docs, so I think its safe to assume this isn't made public through their SDK.
Instead you would have to create your own markup editor.
Note: You may not change the way actual device screenshots are handled (when the user presses Home and Lock together) as per Apple's guidelines.

Can't add title and description in facebook Share

I am trying to implement the Facebook Share in my quiz app. The content is app store link and quiz score.Which is working fine in simulator but in device shows in different and not showing my description. Here is my Code
func ShareFB() {
let fbVC = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
fbVC?.setInitialText("Hey! I scored \(String(describing: UserDefaults.standard.string(forKey: "TOTAL_SCORE")!)) in Test. This is really interesting! You can also try.")
fbVC?.add(URL(string: "https://itunes.apple.com/us/app/test-app/i?ls=1&mt=8"))
fbVC?.add(UIImage(named: "AppIcon"))
present(fbVC!, animated: true) { _ in
}
}
also attaching screen Shot of simulator and device.
now you cannot share image, text and url simultaneously in facebook. also Facebook does not allow pre filled text now
Why don't you use UIActivityController for such functionality?
Example:
let shareItems = [
"Hey! I scored \(String(describing: UserDefaults.standard.string(forKey: "TOTAL_SCORE")!)) in Test. This is really interesting! You can also try.",
URL(string: "https://itunes.apple.com/us/app/test-app/i?ls=1&mt=8"),
UIImage(named: "AppIcon")] //Add the items you want to share in this array
let activityViewController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
Edit:
Check this out:
https://stackoverflow.com/a/30020929/5716829
According to Facebook's 2.3 policy rules, you can't share pre-filled content because of policy violation. Facebook does not allow you to share the pre-filled user message parameters with any content the user didn't entered himself.
If you want to share content on Facebook then user has to write/enter himself in Facebook prompt message dialog.
For more info, please visit: https://developers.facebook.com/docs/apps/review/prefill

How to show a webview (with all interaction) in secondary display, like apple Tv

I have a viewcontroller which has a webview attached to it. I wan't to mirror it to external screen connected to ipad. I am able to create new window and show images and all, but here i want the exact mirroring of UIWebview (all taps, links, textfield input in web page, video) on secondary display.
func initiateTheExternalDisplay() {
guard UIScreen.screens.count > 1 else {
return
}
let externalScreen = UIScreen.screens[1]
let externalWindow = UIWindow(frame: externalScreen.bounds)
self.externalSecondaryWindow = externalWindow
let roortVc = ExternalPresentationViewController() // this contains view to be shown
self.externalPresentationViewController = rootVc
externalWindow.rootViewController = rootVc
externalWindow.screen = externalScreen
externalWindow.isHidden = false
externalWindow.makeKeyAndVisible()
}
This is how i am instantiating the secondary display and is working fine. Suppose same class is showing the webview, can anyone suggest what info should i pass from here (or alternate way) to achieve mirroring.
I couldn't find any way to do so, so I used a work around. I taking screenshots of the web view controller and updating the secondary view at around 20fps, so it lags a bit but at least able to see the same interaction in almost rela time.

iOS UIDocumentInteractionController LaunchServices: invalidationHandler called

I'm having an issue when I try to Launch Instagram from my app. Everything works and I'm able to launch IG and even see the photo I sent etc.
The problem is that the UIDocumentInteractionController is crashing my app. And YES I have done my research.
I've seen the posts LIKE THIS that indicate that this is an Apple bug as as long as you can fix the crash, you should be able fine and ignore the Launch Services message.
The problem is I am still having the crash and trying to figure out how to resolve it.
I found a post that talks about adding an IF-STATEMENT after presenting the ViewController HERE, this post was written in Objective-C, and the example was not for a UIDocumentInteractionController.
I tried to take a stab at it in Swift, but it is still not working out for me. Would appreciate if someone can help out.
dic = UIDocumentInteractionController(URL: imageURL!)
dic.delegate = self
var annotationDictionary: [String: String] = ["InstagramCaption": "Test"]
dic.annotation = annotationDictionary
dic.UTI = "com.instagram.exclusivegram"
dic.presentOpenInMenuFromRect(CGRectMake(1, 1, 1, 1), inView: self.view, animated: true)
if dic.respondsToSelector("popoverPresentationController") {
Scripts.log("InstagramVC Did Respond to popoverPresentationController")
var popoverController: UIPopoverPresentationController = self.popoverPresentationController!
popoverPresentationController?.sourceView = self.view
}
The fix in my case was to declare the UIDocumentInteractionController variable as part of the viewcontroller's class instead of creating it in the same function where I set up the annotation and UTI and called .presentOpenInMenuFromRect
So near the top of my class outside of any functions I declared the variable:
var docController = UIDocumentInteractionController()
And then when I was ready to use it, I configured everything about the already existing UIDocumentInteractionController instead of creating one:
docController = UIDocumentInteractionController(URL: imageURL!)
docController.UTI = "com.instagram.exclusivegram"
docController.delegate = self
docController.annotation = ["InstagramCaption":"Text"]
docController.presentOpenInMenuFromRect(rect, inView: self.view, animated: true)
The app stopped crashing and Instagram now loads with the image/text assigned. 😃👍
I found the suggestion that led me to this fix here: https://stackoverflow.com/a/16057399/428981 and then adapted for Swift
It sounds like your instance of UIDocumentInteractionController is going out of scope. Try making it a property on the class or some other way to retain it.
I had the same problem: sending the picture to Instagram worked but instantaneously crashed my app. I think it has something to do with how the UIDocumentInteractionController is handled by the system once another app opens. If you try to send the picture via the embedded Facebook or Twitter frameworks that open a popover on top of your app, then no crash happens...
In any case the way I finally made it work is by NOT declaring my viewController as delegate, so:
// dic.delegate = self
The downside being that you can't use any of the delegate methods. In my case I wasn't using them anyway.

Resources