My app features a share button which uses activityViewController on iPhones (iOS8+) and iPads (iOS9+). As iPads only use UIPopoverController on iOS8 and activityViewController on iOS9, I've implemented some conditions which direct each device to the appropriate controller. The problem is, iTunes Connect has shown crashes on iPads using UIPopoverController:
-[UIPopoverPresentationController presentationTransitionWillBegin]
Is the following code sound?
//if iOS9
if #available(iOS 9, *) {
self.presentViewController(activityViewController, animated: true, completion: nil)
}
//if iOS8
else {
//if iPad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Pad) {
let popoverCntlr = UIPopoverController(contentViewController: activityViewController)
popoverCntlr.presentPopoverFromRect(CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0), inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
//if other
else {
self.presentViewController(activityViewController, animated: true, completion: nil)
}
}
Related
For SwiftUI, when you want to present the ActivityViewController you can do with 3 steps like the code below
Deprecated Code
guard let urlShare = URL(string: "https://apple.com") else { return }
let activityVC = UIActivityViewController(activityItems: [urlShare], applicationActivities: nil)
// 'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead
UIApplication.shared.windows.first?.rootViewController?.present(activityVC, animated: true, completion: nil)
There are similar questions
How to use UIWindowScene.windows on iOS 15?
But in the case of presenting the UIActivityViewController, the view doesn't not present at all, and it presents the error log
UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow})
.first?.rootViewController?.present(activityVC, animated: true, completion: nil)
[Presentation] Attempt to present
<UIActivityViewController: 0x10f015e00> on
<_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10290e570>
(from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10290e570>)
which is already presenting
<_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x10a923870>.
What is the equivalent of this code in iOS 15?
UIApplication.shared.windows.first?.rootViewController?.present(activityVC, animated: true, completion: nil)
What is the right code for the PopoverController in iOS 9 have anywhere the right code for this code?
#IBAction func share(sender: UIBarButtonItem) {
let activityViewController = UIActivityViewController (
activityItems: [(webview.request?.URL!.absoluteString)! as NSString],
applicationActivities: nil)
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
//iPad
let activityPopover = UIPopoverController(contentViewController: activityViewController)
activityPopover.presentPopoverFromBarButtonItem(self.shareButton, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
else
{
//iPhone
presentViewController(activityViewController, animated: true, completion: nil)
}
}
}
Your code is ancient. You should throw all of that away. In iOS 8 and iOS 9, presented controllers adapt. UIActivityViewController is already a popover on iPad, automatically. Just present it and the right thing will happen.
Of course, you will have to supply it with a sourceView and sourceRect or a bar button item source. Otherwise, you'll crash on the iPad. But that's the case for any popover, so you can't be surprised about it.
I'm trying to add a button in order to share some sentences in Twitter, Facebook... etc. It all works on all iPhone models but simulator crash with an iPad.
This is my code:
#IBAction func shareButton(sender: AnyObject) {
frase = labelFrases.text!
autor = labelAutores.text!
var myShare = "\(frase) - \(autor)"
let activityVC: UIActivityViewController = UIActivityViewController(activityItems: [myShare], applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
And this is the error:
Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController (<_UIAlertControllerActionSheetRegularPresentationController: 0x7c0f9190>) should have a non-nil sourceView or barButtonItem set before the presentation occurs
How should I solve it?
For ipad (iOS > 8.0) you need to set popoverPresentationController:
//check ipad
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad)
{
//ios > 8.0
if ( activityVC.respondsToSelector(Selector("popoverPresentationController"))){
activityVC.popoverPresentationController?.sourceView = super.view
}
}
self.presentViewController(activityVC, animated: true, completion: nil)
More information here:
UIActivityViewController crashing on iOS 8 iPads
Do this instead for Swift 5 to get share button working on both iPad and iPhone:
#IBAction func shareButton(sender: UIButton) { {
let itemToShare = ["Some Text goes here"]
let avc = UIActivityViewController(activityItems: itemToShare, applicationActivities: nil)
//Apps to be excluded sharing to
avc.excludedActivityTypes = [
UIActivityType.print,
UIActivityType.addToReadingList
]
// Check if user is on iPad and present popover
if UIDevice.current.userInterfaceIdiom == .pad {
if avc.responds(to: #selector(getter: UIViewController.popoverPresentationController)) {
avc.popoverPresentationController?.barButtonItem = sender
}
}
// Present share activityView on regular iPhone
self.present(avc, animated: true, completion: nil)
}
Hope this helps!
Slightly adapted version to make it work on any button, iPad and iPhone.
Xcode 13.4.1 (Swift 5.6)
let itemToShare = ["Some Text goes here"]
let avc = UIActivityViewController(activityItems: itemToShare, applicationActivities: nil)
//Apps to be excluded sharing to
avc.excludedActivityTypes = [
UIActivity.ActivityType.print,
UIActivity.ActivityType.addToReadingList
]
// Check if user is on iPad and present popover
if UIDevice.current.userInterfaceIdiom == .pad {
if avc.responds(to: #selector(getter: UIViewController.popoverPresentationController)) {
avc.popoverPresentationController?.sourceView = sender as? UIView
}
}
// Present share activityView on regular iPhone
self.present(avc, animated: true, completion: nil)
When a UIActivityViewController is called on the iPhone in this app, it works perfectly, but when called on a iPad, the app crashes. Below is the code I used:
func shareButtonPress() {
//when the share button is pressed, default share phrase is added, cropped image of highscore is added
var sharingItems = [AnyObject]()
var shareButtonHighscore = NSUserDefaults.standardUserDefaults().objectForKey("highscore") as Int!
sharingItems.append("Just hit \(shareButtonHighscore)! Beat it! #Swath")
UIGraphicsBeginImageContextWithOptions(UIScreen.mainScreen().bounds.size, false, 0);
self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
var image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
sharingItems.append(image)
let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil)
var barButtonItem: UIBarButtonItem! = UIBarButtonItem()
activityViewController.excludedActivityTypes = [UIActivityTypeCopyToPasteboard,UIActivityTypeAirDrop,UIActivityTypeAddToReadingList,UIActivityTypeAssignToContact,UIActivityTypePostToTencentWeibo,UIActivityTypePostToVimeo,UIActivityTypePrint,UIActivityTypeSaveToCameraRoll,UIActivityTypePostToWeibo]
self.presentViewController(activityViewController, animated: true, completion: nil)
}
As you can see, I'm programming in Swift, in the SpriteKit Framework, and I don't understand why the app is crashing.
I'm receiving this error:
Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController (<_UIAlertControllerActionSheetRegularPresentationController: 0x7fc7a874bd90>) should have a non-nil sourceView or barButtonItem set before the presentation occurs.'
What can I do to fix this problem?
Before presenting the UIActivityViewController, add in this line of code:
activityViewController.popoverPresentationController?.sourceView = self.view
This way, the view controller knows in which frame of the GameViewController to appear in.
If you read the error it says how to fix it, you need to set the barButtonItem or sourceView from which to present the popover from, in your case:
func shareButtonPress(pressedButton: UIBarButtonItem) {
...
activityViewController.popoverPresentationController.barButtonItem = pressedButton
self.presentViewController(activityViewController, animated: true, completion: nil)
}
Swift 5:
Check if the device is iPhone or iPad and based on that add a sourceView and present the activityController
let activity = UIActivityViewController(activityItems: [self], applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .phone {
UIApplication.topViewController?.present(activity, animated: true, completion: nil)
} else {
activity.popoverPresentationController?.sourceView = UIApplication.topViewController!.view
UIApplication.topViewController?.present(activity, animated: true, completion: nil)
}
There are two option, the action came from a UIBarButtonitem or UIButton that is a UIView.
func shareButtonPress() {
...
if let actv = activityViewController.popoverPresentationController {
actv.barButtonItem = someBarButton // if it is a UIBarButtonItem
// Or if it is a view you can get the view rect
actv.sourceView = someView
// actv.sourceRect = someView.frame // you can also specify the CGRect
}
self.presentViewController(activityViewController, animated: true, completion: nil)
}
You may have to add a sender to your function like func shareButtonPress(sender: UIBarButtonItem) or func shareButtonPress(sender: UIButton)
I added for Swift 3:
activityViewController.popoverPresentationController?.sourceView = self.view
fixed my issue.
i use a view only in landscape for iphone 5/6/6 Plus / iPad Mini/iPad and now I would like to integrate the UIUserInterfaceIdiom in my application for use these devices in landscape.
func openGallary()
{
picker!.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
if UIDevice.currentDevice().userInterfaceIdiom == .Phone
{
self.presentViewController(picker!, animated: true, completion: nil)
}
else
{
popover=UIPopoverController(contentViewController: picker!)
popover!.presentPopoverFromRect(btnClickMe.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
}
I'm trying with this code but I get an error UIUserInterfaceIdiomPad
You have to do it like this:
if UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Phone {
// .....
}
you can also create a read-only computed property to return check it as follow:
var iPhone: Bool {
return UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Phone
}
//
var iPad: Bool {
return UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad
}
//
if iPhone {
presentViewController(picker!, animated: true, completion: nil)
} else if iPad {
popover=UIPopoverController(contentViewController: picker!)
popover!.presentPopoverFromRect(btnClickMe.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
} else {
println("Unspecified")
}
Check your orientation settings by clicking on the project file, selecting your target and inspecting the settings in the "General" tab under the heading "Devices". Make sure iPad is enabled. The error also point to the user interface orientation (just below, labeled "Device Orientation"). Check that as well.
Swift 3 version:
//--- for iPhone: ---------
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.phone {
// YOUR CODE
}
//--- for iPad: ---------
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
// YOUR CODE
}
//--- for Apple TV: ---------
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.tv {
// YOUR CODE
}