iOS: crash if sharing with "Message" option - ios

Our app only supports portrait mode. Presenting a UIActivityViewController works.
However, sharing with the "Message" option crashes the app:
*** Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported
orientations has no common orientation with the application, and
[MFMessageComposeViewController shouldAutorotate] is returning YES'
Sharing with another option, such as Facebook Messenger, works.
Solutions from similar SO questions like this one do not work since they suggest supporting all orientations. We only want to support portrait.
1) How can we support the "Message" share option while only supporting portrait orientation, that is while only supporting portrait orientation in Info.plist?
2) Why are we able to support the "Message" share option in other apps with only portrait orientation in Info.plist but not this one? Where should we look for debugging purposes?
// Define share objects
let objectsToShare = ["test message"] as [Any]
// Configure UIActivityViewController
let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityViewController.excludedActivityTypes =
[UIActivityType.addToReadingList,
UIActivityType.assignToContact,
UIActivityType.print,
UIActivityType.copyToPasteboard]
// Define completion handler
activityViewController.completionWithItemsHandler = doneSharingHandler
// Show UIActivityViewController
present(activityViewController, animated: true, completion: nil)

I tried for a while to reproduce this bug and could not get it to crash. Finally I was able to get this exact crash when I returned UIInterfaceOrientationPortrait when I should have been returning UIInterfaceOrientationMaskPortrait for one of the orientation functions. Check your view controller's implementation of supportedInterfaceOrientations and your implementation of application:supportedInterfaceOrientationsForWindow:

Here is a very hackish solution that should work for an app that only presents in portrait.
Create a category over MFMessageComposeViewController and override supportedInterfaceOrientations and shouldAutorotate to only support portrait.
You may need to create this category in Objective C to actually get it to compile, but it will work.
#interface MFMessageComposeViewController (NoRotation) #end
#implementation MFMessageComposeViewController (NoRotation)
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate {
return NO;
}

All of your individual view controllers can support only portrait, by implementing supportedInterfaceOrientations to return UIInterfaceOrientationPortrait.
But your app, meaning the Info.plist or the app delegate's application(_:supportedInterfaceOrientationsFor:), should support all orientations.
This will allow the runtime to present this MFMessageComposeViewController the way it wants to, but all of your view controller will still be in portrait only, which is what you want.

Stray code (bad developer, bad developer, bad developer!) elsewhere in the app caused the bug.
An extension of UINavigationController elsewhere in the code overrode supportedInterfaceOrientations and caused everyone here to waste time over a stupid bug. Sorry! Hopefully our sloppiness benefits some future user, though.
Removing the extension fixed the bug.
If SO ever decides to recognize and award the worst developers around, we're happy to stand for nomination. :)

Related

How to create a landscape-view only ios application

I want to create an iOS app that can only work on landscape-view mode.
I Googled how to do that, and end up with supportedInterfaceOrientations method
Here's my attempt:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}
I tried to put the above code on viewController.m and AppDelegate.m, but it didn't seem to work
Thanks, any opinion will be much appreciated
You don't need to write any code to run app which supports landscape mode only. Just select app target and uncheck portrait and portrait upside down orientation and make sure landscape orientation is checked.
It seems as though you want to programmatically change the orientation of your application. This can be done by any ViewController by implementing one method.
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationLandscapeLeft;
}
That should force the view to auto-rotate in the direction you want it.
When you use -supportedInterfaceOrientationForPresentation it simply states that the returned interfaces can be used, not necessarily that they should be used.
When you use -preferredInterfaceOrientationForPresentation it says that this is orientation that I want, and that the ViewController should switch to it.

UIActivityViewController: Choosing an activity for which permissions are denied leads to a dead end

I am using a UIActivityViewController to implement image sharing.
UIActivityViewController *shareVC = [[UIActivityViewController alloc] initWithActivityItems:#[image] applicationActivities:nil];
[shareVC setCompletionHandler:^(NSString *activityType, BOOL completed){
NSLog(#"completed image export activity: %# - %d", activityType, completed);
}];
[self presentViewController:shareVC animated:true completion:nil];
In testing, I have noticed that if the user selects, for example, "Assign to Contact", but then denies the permission in the ensuing dialog, then they are taken to a screen that says "This app does not have access to your contacts. You can enable access in Privacy Settings.", from which there is no way to back out. The only way for them then to get back to the actual app is to manually restart it.
I'm fairly happy to leave this behaviour as is for now since I don't anticipate any users will be particularly bothered by it, but I'd like to know if there is a sensible way to work around it, and if it is indeed the expected behaviour.
Hmm, maybe you could check if a segue is called when the new "This app does not have access to your contacts. You can..."-Viewcontroller is shown. Then you could manipulate the destinationViewController property of the segue and add for example a button, with an action enabling the user to go back.
All highly theroretical.
I am working on the iPad platform, and in that context presenting a UIActivityViewController directly is wrong according to the documentation, which states that
When presenting the view controller, you must do so using the appropriate means for the current device. On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally.
In most ways, the behaviour of the modally presented view controller on iPad seemed perfectly fine, but this case illustrates why it is not appropriate. I don't know how the equivalent case is handled on iPhone / iPod touch as I haven't really looked at those platforms just yet.
Now that I am presenting it in a UIPopoverController, the view explaining that the app doesn't have access to contacts appears within the popup, and is dismissed as the popup is dismissed. Unfortunately it appears that the completion handler is not called in this case; I'm not sure if that's an iOS bug or if I lack understanding of something. It is a slight problem for me at the moment, so I'll have to work around it.
Unfortunately, it seems relatively unlikely that anyone experiencing a related problem will be helped by my question / answer; maybe I'll try to edit the question.

GameCenter authentication in landscape-only app throws UIApplicationInvalidInterfaceOrientation

Problem:
If user is not logged into GameCenter account - GameCenter authentication view is launched in portrait mode (in ios 5 there were a modal dialog) asking to log in. But if I disable Portrait mode in xcode (Project Summary) or in supportedInterfaceOrientationsForWindow: (as my app supposed to run in landscape mode ONLY) I get:
Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and shouldAutorotate is returning YES'
If I enable Portrait for ipad/iphone (and/or comment out supportedInterfaceOrientationsForWindow:) it works without crash, but I don't want portrait mode to be enabled.
While writing this question and experimenting with code, it seems that I've found a solution:
enable all orientations in project summary and remove application:supportedInterfaceOrientationsForWindow.
Add this code to ViewController:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
Now it works seamlessly.
Add to app delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)w {
return (NSUInteger)[application supportedInterfaceOrientationsForWindow:w] | (1<<UIInterfaceOrientationPortrait);
}
I have found that the problem is coming from the Game Center in my case. When in the simulator I do not have the Game Center initialized yet, it would like to pop up the login view, but in portrait mode. Once it is reaching this point it crashes if I disallowed portrait orientation. Strange bug in the OS as Game Center should take the allowed orientations only to be inline with our intention of landscape user interface.
I do not have the solution yet, but I will post if I find it.
I had the same issue as you and I fixed it with a kinda, ugly work around, basically I have a global variable in my app that I use to choose what the valid interface orientations are. In the
- (NSInteger)application : (UIApplication *)supportedInterfaceOrientationsForWindow:(UIWindow *)window{
if(orientationIndicator == 1){
return UIInterfaceOrientationMaskAllButUpsideDown;
}
else if(orientationIndicator == 2){
return UIInterfaceOrientationMaskLandscape;
}
}
To declare the global variable put this in your appDelegate.m file :
int orientationIndicator = 1;
To import the global variable use :
extern int orientationIndicator;
Then you can change the value of orientation indicator and it will allow you to run in different interface types. So what I did was I start by making the orientationIndicator = 1. When you authenticate a player and initiate the login view controller set the orientation indicator to 2. When you dismiss the view (authenticate the player) then you can change it back to 1.
This is a slimy work around but it has worked for me.
Hope this helps!
Catching the exception appears to work just fine for me:
#try {
[rootNavigationController pushViewController:viewController animated:YES];
}
#catch (NSException *exception) {
//somehow, catching this exception just allows the view controller to be shown?
}
In iOS 6.0, the exception is thrown, but if you catch it then the viewController will still be shown and GameCenter will behave as expected in landscape orientation.
An alternate solution is just to target iOS 6.1 and above, as Apple fixed the bug by that release.

iPad - Dismiss keyboard for modal view controller in UIModalPresentationFormSheet mode

In my iPad app I want to present some view controllers in UIModalPresentationFormSheet modal mode without keyboard.
I use it to display help as an example.
At the moment I use the code found on the one of stackoverflow answers to dismiss it:
// trick to dismiss keyboard in iPad:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
// iPad specific behaviour:
#try
{
Class UIKeyboardImpl = NSClassFromString(#"UIKeyboardImpl");
id activeInstance = [UIKeyboardImpl performSelector:#selector(activeInstance)];
[activeInstance performSelector:#selector(dismissKeyboard)];
}
#catch (NSException *exception)
{
//NSLog(#"%#", exception);
}
}
But I am afraid Apple can reject it in during approval process as it uses private API
I can see Apple developers achieved that in the GarageBand help screens so it must be the 'proper' way to do this.
Would appreciate help as our client do not want to change design concept because of such a slight limitation.
UPDATE:
Just today I was rejected from AppStore:
We found that your app uses one or
more non-public APIs, which is not in
compliance with the App Store Review
Guidelines. The use of non-public APIs
is not permissible because it can lead
to a poor user experience should these
APIs change.
We found the following non-public APIs
in your app:
activeInstance dismissKeyboard
So please do NOT follow this advice:
How to HIDE the iPad keyboard from a MODAL view controller?
As I said in a comment in the mentioned question: you can construct the selector dynamically with NSSelectorFromString(). This will be accepted for the AppStore, your bug will be fixed and your code will not crash.

IOS4 UISplitViewController in Portrait Orientation with RootViewController showing like Landscape

In IOS 3.2 I was able to display my UISplitViewController side by side like in landscape mode.
In IOS 4.2 the RootViewController (MasterView) is not showing up in portrait mode. Does anyone know if we need to display the rootviewcontroll in a popover? Can we display it side by side like how it is in landscape mode?
I want to avoid having to click on a button to show the masterview (when in portrait mode)
In that case, you can skip the splitviewcontroller and create only view base application where you could manually control the UI.
on viewDidAppear you can do
[splitViewController setHidesMasterViewInPortrait:NO];
It works even though you get a warning. I think you can create category with a custom splitviewcontroller to get rid of the warning.
2.Otherwise you can do something like
on the viewWillAppear, you can do something like
if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
UIViewController *master = [[splitViewController.viewControllers objectAtIndex:0];
UIViewController *detail = [[splitViewController.viewControllers objectAtIndex:1];
[splitViewController setupPortraitMode:master detail:detail];
}
(setupPortraitMode ) http://intensedebate.com/profiles/fgrios.
I used setHidesMasterViewInPortrait:NO and it did work on the pre-5.0 releases, and even got into the apple store once. But the next time I updated the app, they rejected it because I used a hidden API. I am still searching for a way to make this work.

Resources