UIActivityViewController in Swift Crashes on iPad - ios

I am making a share function in my game and I have the code and it works fine on iPhone but when I test it on a iPad, when I tap the share button the app crashes. I am using the following code for the share button
let textToShare = "Check out this website!"
if let myWebsite = NSURL(string: "http://www.apple.com/") {
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
self.view?.window?.rootViewController?.presentViewController(activityVC, animated: true, completion: nil)
}

The UIActivityViewController's has non-null popoverPresentationController property when running on iPad. So, try below.
if let wPPC = activityVC.popoverPresentationController {
wPPC.sourceView = some view
// or
wPPC.barButtonItem = some bar button item
}
presentViewController( activityVC, animated: true, completion: nil )

Building on #Satachito's answer: As the sourceView you can create an (invisible) CGRect at the place the popup should point to, and set the arrow in that direction:
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad {
activityVC.popoverPresentationController?.sourceView = UIApplication.shared.windows.first
activityVC.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: 300, height: 350)
activityVC.popoverPresentationController?.permittedArrowDirections = [.left]
}
UIApplication.shared.windows.first?.rootViewController?.present(activityVC, animated: true, completion: nil)

The popoverPresentationController sourceView needs to be set to current view.
let activityVC = UIActivityViewController(activityItems: [quoteController.attributedString, view.screenShot()], applicationActivities: [])
present(activityVC, animated: true)
activityVC.popoverPresentationController?.sourceView = view

Related

Share button work in Iphone and not work in iPad - IOS SWIFT

Share button works in iPhone but causes a crash in iPad.
Code :
let finaltext = "Text to share"
let objectsToShare = [finaltext]
let activityController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityController.popoverPresentationController?.sourceView = self.view
present(activityController, animated: true, completion: nil)
Thank for Help
Try to add sourceRect like this:
let finaltext = "Text to share"
let objectsToShare = [finaltext]
let activityController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityController.popoverPresentationController?.sourceView = self.view
activityController.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.maxY, width: 0, height: 0) //center bottom
present(activityController, animated: true, completion: nil)

UIActivityController Not Showing full on screen

i am Presenting UIActivityController But it Showing Half on screen.
let textToShare = "This is awesome! Check out this website about it!"
if let myWebsite = NSURL(string:urlShare) {
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
//New Excluded Activities Code
activityVC.excludedActivityTypes = [UIActivityTypeAirDrop, UIActivityTypeAddToReadingList]
self.navigationController!.presentViewController(activityVC, animated: true, completion: nil)
Have you try to self.presentViewController on the current ViewController instead of on the self.navigationController.
I use to have this issue as my rootViewController is larger and therefore when presenting using the self.navigationController it get cut off.
Try adding inside a POP UPViewController
var popup = UIPopoverController(contentViewController: controller)
popup.presentPopoverFromRect(CGRectMake(self.view!.frame.size.width / 2, self.view!.frame.size.height / 4, 0, 0), inView: self.view!, permittedArrowDirections: .Any, animated: true)
here, controller is your activityVC

Basic example for sharing text or image with UIActivityViewController in Swift

I started my search by wanting to know how I could share to other apps in iOS. I discovered that two important ways are
UIActivityViewController
UIDocumentInteractionController
These and other methods are compared in this SO answer.
Often when I am learning a new concept I like to see a basic example to get me started. Once I get something basic set up I can modify it how I like later.
There are many SO questions related to UIActivityViewController, but I couldn't find any that were just asking for a simple example. Since I just learned how to do this, I will provide my own answer below. Feel free to add a better one (or an Objective-C version).
UIActivityViewController Example Project
Set up your storyboard with two buttons and hook them up to your view controller (see code below).
Add an image to your Assets.xcassets. I called mine "lion".
Code
import UIKit
class ViewController: UIViewController {
// share text
#IBAction func shareTextButton(_ sender: UIButton) {
// text to share
let text = "This is some text that I want to share."
// set up activity view controller
let textToShare = [ text ]
let activityViewController = UIActivityViewController(activityItems: textToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
// exclude some activity types from the list (optional)
activityViewController.excludedActivityTypes = [ UIActivity.ActivityType.airDrop, UIActivity.ActivityType.postToFacebook ]
// present the view controller
self.present(activityViewController, animated: true, completion: nil)
}
// share image
#IBAction func shareImageButton(_ sender: UIButton) {
// image to share
let image = UIImage(named: "Image")
// set up activity view controller
let imageToShare = [ image! ]
let activityViewController = UIActivityViewController(activityItems: imageToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
// exclude some activity types from the list (optional)
activityViewController.excludedActivityTypes = [ UIActivity.ActivityType.airDrop, UIActivity.ActivityType.postToFacebook ]
// present the view controller
self.present(activityViewController, animated: true, completion: nil)
}
}
Result
Clicking "Share some text" gives result on the left and clicking "Share an image" gives the result on the right.
Notes
I retested this with iOS 11 and Swift 4. I had to run it a couple times in the simulator before it worked because it was timing out. This may be because my computer is slow.
If you wish to hide some of these choices, you can do that with excludedActivityTypes as shown in the code above.
Not including the popoverPresentationController?.sourceView line will cause your app to crash when run on an iPad.
This does not allow you to share text or images to other apps. You probably want UIDocumentInteractionController for that.
See also
Add sharing to your Swift app via UIActivityViewController
UIActivity​View​Controller from NSHipster
UIActivityViewController documentation
Share extension documentation
comparison with UIDocumentInteractionController
Share : Text
#IBAction func shareOnlyText(_ sender: UIButton) {
let text = "This is the text....."
let textShare = [ text ]
let activityViewController = UIActivityViewController(activityItems: textShare , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
Share : Image
#IBAction func shareOnlyImage(_ sender: UIButton) {
let image = UIImage(named: "Product")
let imageShare = [ image! ]
let activityViewController = UIActivityViewController(activityItems: imageShare , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
Share : Text - Image - URL
#IBAction func shareAll(_ sender: UIButton) {
let text = "This is the text...."
let image = UIImage(named: "Product")
let myWebsite = NSURL(string:"https://stackoverflow.com/users/4600136/mr-javed-multani?tab=profile")
let shareAll= [text , image! , myWebsite]
let activityViewController = UIActivityViewController(activityItems: shareAll, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
Just as a note you can also use this for iPads:
activityViewController.popoverPresentationController?.sourceView = sender
So the popover pops from the sender (the button in that case).
I found this to work flawlessly if you want to share whole screen.
#IBAction func shareButton(_ sender: Any) {
let bounds = UIScreen.main.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, true, 0.0)
self.view.drawHierarchy(in: bounds, afterScreenUpdates: false)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let activityViewController = UIActivityViewController(activityItems: [img!], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
I've used the implementation above and just now I came to know that it doesn't work on iPad running iOS 13.
I had to add these lines before present() call in order to make it work
//avoiding to crash on iPad
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
That's how it works for me
func shareData(_ dataToShare: [Any]){
let activityViewController = UIActivityViewController(activityItems: dataToShare, applicationActivities: nil)
//exclude some activity types from the list (optional)
//activityViewController.excludedActivityTypes = [
//UIActivity.ActivityType.postToFacebook
//]
//avoiding to crash on iPad
if let popoverController = activityViewController.popoverPresentationController {
popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
self.present(activityViewController, animated: true, completion: nil)
}
You may use the following functions which I wrote in one of my helper class in a project.
just call
showShareActivity(msg:"message", image: nil, url: nil, sourceRect: nil)
and it will work for both iPhone and iPad. If you pass any view's CGRect value by sourceRect it will also shows a little arrow in iPad.
func topViewController()-> UIViewController{
var topViewController:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while ((topViewController.presentedViewController) != nil) {
topViewController = topViewController.presentedViewController!;
}
return topViewController
}
func showShareActivity(msg:String?, image:UIImage?, url:String?, sourceRect:CGRect?){
var objectsToShare = [AnyObject]()
if let url = url {
objectsToShare = [url as AnyObject]
}
if let image = image {
objectsToShare = [image as AnyObject]
}
if let msg = msg {
objectsToShare = [msg as AnyObject]
}
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.modalPresentationStyle = .popover
activityVC.popoverPresentationController?.sourceView = topViewController().view
if let sourceRect = sourceRect {
activityVC.popoverPresentationController?.sourceRect = sourceRect
}
topViewController().present(activityVC, animated: true, completion: nil)
}
iOS share text or image
present UIActivityViewController
let controller = UIActivityViewController(activityItems: [someObject], applicationActivities: nil) //someObject can be UIImage, NSURL, String... iOS decide how to handle it properly
controller.popoverPresentationController?.sourceView = self.view
controller.completionWithItemsHandler = {
(
activityType: UIActivity.ActivityType?,
completed: Bool,
arrayReturnedItems: [Any]?,
error: Error?
) in
if let error = error {
//error occured
return
}
if completed {
if let activityType = activityType {
switch activityType {
case .saveToCameraRoll:
break
case .copyToPasteboard:
break
case .addToReadingList:
break
case .airDrop:
break
default:
//all others
break
}
}
} else {
//Cancel
}
}
self.present(controller, animated: true)
If you are going to save image into library add NSPhotoLibraryAddUsageDescription into app's .plist file or you get runtime error
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value explaining to the user how the app uses this data.
or just exclude this opportunity:
controller.excludedActivityTypes = [.saveToCameraRoll]
variant with completionWithItemsHandler which can helps to add post logic or handle errors.
For example I run into next error when saving UIImage into Photo library in a corresponding handler:
Error Domain=ALAssetsLibraryErrorDomain Code=-1 "Unknown error" UserInfo={NSLocalizedDescription=Unknown error, NSUnderlyingError=0x600003f85110 {Error Domain=PHPhotosErrorDomain Code=3303 "(null)"}}
As figured out I tried to save CIImage. As a variant you can convert it to CGImage
let context = CIContext()
guard let cgImage = context.createCGImage(output, from: output.extent) else { return nil }
return UIImage(cgImage: cgImage)

UIActivityViewController on iPad

I have been using the code below to show a UIActivityViewController which worked fine when I was using Xcode 6, Swift 1.2 and iOS 8. However when I updated it shows the UIActivityViewController but it is completely blank without any of the sharing options. Do you have any suggestions?
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
let textToShare = textViewOne.text
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
let nav = UINavigationController(rootViewController: activityVC)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
let popover = nav.popoverPresentationController as UIPopoverPresentationController!
popover.sourceView = self.view
popover.sourceRect = sender.frame
self.presentViewController(nav, animated: true, completion: nil)
} else {
let textToShare = textViewOne.text
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
}
This has fixed it.
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.title = "Share One"
activityVC.excludedActivityTypes = []
activityVC.popoverPresentationController?.sourceView = self.view
activityVC.popoverPresentationController?.sourceRect = sender.frame
self.presentViewController(activityVC, animated: true, completion: nil)
in swift 3.0:
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.title = "Share One"
activityVC.excludedActivityTypes = []
activityVC.popoverPresentationController?.sourceView = self.view
activityVC.popoverPresentationController?.sourceRect = sender.frame
self.present(activityVC, animated: true, completion: nil)
I was struggling with the above suggestion with SWIFT 5 since:
activity.popoverPresentationController?.sourceRect = sender.frame
does NOT WORK in some cases, since sender is not available in scope.
Try instead to set with a CGRECT, something like:
activityController.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY,width: 0,height: 0)
I hope this helps some people.
Swift 4.0
let shareText = "Hi"
let activity = UIActivityViewController(activityItems: shareText, applicationActivities: nil)
activity.excludedActivityTypes = []
if UIDevice.current.userInterfaceIdiom == .pad {
activity.popoverPresentationController?.sourceView = self.view
activity.popoverPresentationController?.sourceRect = sender.frame
}
self.present(activity, animated: true, completion: nil)
I had sort of a different issue. I wanted the UIActivityViewController to stay in the center of the screen but when rotating the iPad it was off centered when in Landscape.
Landscape wrong:
The 2 fixes was to make the UIActivityViewController a class property and most importantly set its sourceRect in viewDidLayoutSubviews. Follow the 5 steps-
// 1. make the activityVC a class property
var activityVC: UIActivityViewController?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if UIDevice.current.userInterfaceIdiom == .pad {
// 2. set its sourceRect here. It's the same as in step 4
activityVC?.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
}
}
// 3. present the UIActivityViewController
func presentActivityVC() {
let objectsToShare = [textToShare]
activityVC = nil
activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC?.excludedActivityTypes = [.addToReadingList, .openInIBooks, .print]
activityVC?.popoverPresentationController?.sourceView = self.view
if UIDevice.current.userInterfaceIdiom == .phone {
activityVC?.modalPresentationStyle = .overFullScreen
}
if UIDevice.current.userInterfaceIdiom == .pad {
// 4. set its sourceRect here. It's the same as in step 2
activityVC?.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
activityVC?.popoverPresentationController?.permittedArrowDirections = []
}
present(activityVC!, animated: true, completion: nil)
activityVC?.completionWithItemsHandler = { [weak self](activityType, completed:Bool, returnedItems:[Any]?, error: Error?) in
if let error = error {
print(error.localizedDescription)
return
}
// 5. set the activityVC to nil after the user is done
DispatchQueue.main.async { [weak self] in
self?.activityVC = nil
}
}
}
Now when rotating it's centered both in landscape and portrait.
Landscape correct:
Portrait:
if you want to attach the popover to the sender view's bounds, then the following code will work:
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad {
vc.popoverPresentationController?.sourceView = sender
vc.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint.zero, size: sender.frame.size)
}
sourceRect is defined in the coordinate space of the sourceView, thus we need to specify the origin of the sourceRect as CGPoint.zero rather than sender.frame.origin.
We would need to set the sourceView and sourceRect both specially for iPad.
We may try below snippet
activityViewController.popoverPresentationController?.sourceView = sender.self
activityViewController.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.minX + sender.frame.width/2, y: self.view.bounds.minY, width: 0, height: 0)
activityViewController.popoverPresentationController?.permittedArrowDirections = []
It would set the sourceView as sender's and sourceRect at center to the UI.
We are adding sender.frame.width/2 to x coordinate and removing the anchor arrow as well to get the pop-up exactly at center.
Updated for swift 4 ~ 5 with action event example, it'll show arrow also.Tested on iPadOS 15.7.2 & iOS 16.1, 13.6
#objc
private final func handleShareButtonPress(_ sender: UIButton) {
let urlString = Manager.getShareEventBaseURL
guard let url = URL(string: urlString) else { return }
let items: [Any] = [url]
let activityController = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityController.title = "\(user.name) sharing with you."
if let popoverController = activityController.popoverPresentationController {
popoverController.sourceView = self.view // to set the source of your alert
popoverController.sourceRect = sender.convert(sender.frame, to: self.view) /// Get correct rect of sender view
popoverController.permittedArrowDirections = [.up] // declare the direction of sender to show arrow
}
present(activityController, animated: true)
}

UIActivityViewController in a PopOverViewController Crashing in Swift

I have a UIActivityViewController for a share button. For iPhone I have it as a regular UIActivityViewController and for iPad its in a PopOverViewController. This is the code I have for it
let textToShare = "Check out this website!"
if let myWebsite = NSURL(string: "http://www.apple.com/") {
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
if let popUpVC = activityVC.popoverPresentationController {
popUpVC.permittedArrowDirections = .Any
popUpVC.sourceRect = share.frame
}
self.view?.window?.rootViewController?.presentViewController(activityVC, animated: true, completion: nil)
}
When I press the share button on a iPad it just crashes with a (lldb). But when I have it present from a view it works but isn't in the right position. This is the code I am using for the present from a view.
popUpVC.sourceView = self.view
Try this, you have to check if the device you are currently running on responds to popoverPresentationController because popoverPresentationController is new to iOS 8 and will crash on iOS 7. It'll also be nil on iPhone because it's only in a UIPopover on iPad.
let activityViewController = UIActivityViewController(activityItems: [myText, myUrl], applicationActivities: nil)
if activityViewController.respondsToSelector("popoverPresentationController") {
// iOS8+
view.presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.popoverPresentationController?.sourceView = view
} else {
view.presentViewController(activityViewController, animated: true, completion: nil)
}

Resources