Seeing this error message in the logs, though not consistently, around the time that I use SLComposeViewController to open a Twitter or Facebook share sheet. I am not using any new iOS 8 API, just testing existing code on iOS 8. I see others have had this problem and even seen crashes when using other modal view controllers from the Cocoa Touch SDK.
LaunchServices: invalidationHandler called
Are there new precautions to take with SLComposeViewController and UIActivityViewController in iOS 8? Something else to consider?
Add this code after you present your activity view controller:
if ([activityVC respondsToSelector:#selector(popoverPresentationController)])
{
// iOS 8+
UIPopoverPresentationController *presentationController = [activityVC popoverPresentationController];
presentationController.sourceView = sender; // if button or change to self.view.
}
Looking at the developer forums: "That log message does not indicate any error on your part."
I had a similar problem with a UIDocumentInteractionController, where when I tapped outside it to dismiss it, or selected another app to open the document in, it would crash with the "LaunchServices: invalideationHandler called" console message displayed twice (only using iOS 8).
A workaround is to add the call to presentOpenInMenuFromRect:inView:animated to the main queue, i.e.
dispatch_async(dispatch_get_main_queue(), ^() {
[self.documentInteraction presentOpenInMenuFromRect:theRect inView:self.view animated:YES];
});
You may also need to define the sourceRect. I used the following code to display a SLComposeViewController from a tableView.
if ([controller respondsToSelector:#selector(popoverPresentationController)]) {
//get rect for this row in table
CGRect frame = [self.tableView rectForRowAtIndexPath:indexPath];
//convert table row frame to view reference
CGRect frameInView = [self.tableView convertRect:frame toView:self.view];
[controller popoverPresentationController].sourceRect = frameInView;
[controller popoverPresentationController].sourceView = self.view;
}
Regarding the auto-closing (not the crash):
I think it's probably related to the link you are trying to share. I'm seeing the same thing when trying to post music links (Spotify, SoundCloud,...). The same tweet works if I replace the link by a link to some non-media-content. I'll file radar on this to see whether it's intentional...
This gets rid of the Error message for me and works as expected. You have to get rid of the if statement that calls "isAvailableForServiceType:"
It should look like this. Happy coding.
SLComposeViewController *tweetSheet = [SLComposeViewController
composeViewControllerForServiceType:SLServiceTypeTwitter];
[tweetSheet setInitialText:#"Great fun to learn iOS programming at appcoda.com!"];
[self presentViewController:tweetSheet animated:YES completion:nil];
if ([tweetSheet respondsToSelector:#selector(popoverPresentationController)])
{
// iOS 8+
UIPopoverPresentationController *presentationController = [tweetSheet popoverPresentationController];
presentationController.sourceView = sender; // if button or change to self.view.
}
Related
I have a very strange (& serious) problem.
My app uses a UIDocumentInteractionController to share a PDF document.
When the user selects the "Mail" option in the controller's pop-up the MailCompose window is opened.
But, neither the Send nor Cancel button in this window causes the MailCompose window to be dismissed, meaning the user gets stuck and has to kill the app. The mail does go out though.
Here's the catch:
This happens only in iOS8 (both versions released so far) and only on apps installed via the AppStore. That EXACT same version of the app, when running on my device via USB debugging works fine.
Here's some code:
-(void)sharePDF:(id)sender
{
#try
{
NSURL *fileURL = [NSURL fileURLWithPath:currentFileObject.LocalPath];
if(fileURL)
{
//UIDocumentInteractionController
NSString *newPath;
#try
{
//Create a copy of the file for sharing with a friendly name
if (currentFileObject.isSpecialReport)
{
newPath = [svc saveReport:[NSData dataWithContentsOfURL:fileURL] ToFile:[NSString stringWithFormat:#"%#.pdf", currentFileObject.ReportName]];
}
else
{
newPath = [svc saveReport:[NSData dataWithContentsOfURL:fileURL] ToFile:[NSString stringWithFormat:#"%#.pdf", currentFileObject.PatientFullName]];
}
}
#catch (NSException *exception) {
return;
}
NSURL *newURL = [NSURL fileURLWithPath:newPath];
self.docController = [UIDocumentInteractionController interactionControllerWithURL:newURL];
self.docController.delegate = self;
if (currentFileObject.isSpecialReport)
{
self.docController.name = [NSString stringWithFormat:#"Pathology - %#", currentFileObject.ReportName];
}
else
{
self.docController.name = [NSString stringWithFormat:#"Pathology - %#", currentFileObject.PatientFullName];
}
[self.docController presentOptionsMenuFromBarButtonItem:btnShare animated:YES];
}
}
#catch (NSException *exception) {
return;
}
}
I do not implement any of the delegate methods since non of them are required, I also do not make use of the preview functionality.
What's most puzzling to me is that the app from the AppStore behaves differently than my local one although the code is identical. My next step is to use the new beta developer tools (Test Flight) to re-publish the app, hoping that I can replicate the problem.
EDIT: I found a similar question on SO here: Cannot dismiss email sheet invoked from UIDocumentInteractionController in iOS 8
After reading that post I think it worth mentioning that I submitted the app to the AppStore via XCode 5 (the last version before XCode 6). Can that really be a factor here? Does Apple not use the same version on their side as the version in which the app was originally built?
I think this is a bug in iOS 8, and if it's still not working for you, I don't think Apple are likely to fix it. I'd upgrade to Xcode 6 and see if that fixes it for you. (It did for us, as you've discovered).
My standard implementation for this delegate method is the following. I just initialize the navigation button and save locally the button and the popover.
- (void) splitViewController: (UISplitViewController *) splitController
willHideViewController: (UIViewController *) viewController
withBarButtonItem: (UIBarButtonItem *) barButtonItem
forPopoverController: (UIPopoverController *) popoverController
{
// Set the button to open the PopOver
barButtonItem.title = viewController.title;
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
// Save the ref to the default left navigation button
_masterButton = barButtonItem;
// Save the ref to the PopOver
_masterPopOver = popoverController;
}
From iOS 8 this method is deprecated and the Apple documentation says:
Implement the splitViewController:willChangeToDisplayMode: method instead.
But the arguments of the new method has nothing to do with the deprecated method!
I guess I have to create a button and a popover myself?
Does somebody already made this re-coding to implement the current popup behaviour?
Thank you for your help
Take a look at displayModeButtonItem. It is very similar to barButtonItem from the deprecated method.
You can refactor your example into using the new splitViewController:willChangeToDisplayMode: method in the following way:
- (void)splitViewController:(UISplitViewController *)svc
willChangeToDisplayMode:(UISplitViewControllerDisplayMode)displayMode {
if (displayMode == UISplitViewControllerDisplayModePrimaryHidden) {
self.navigationItem.leftBarButtonItem = svc.displayModeButtonItem;
}
}
This is an extension to Alexander's answer. To cover Cihad's comment: the last line of code creates the leftBarButtonItem and makes it the blue "<" button that will open the master viewController.
I just commented out willHideViewController and willShowViewController from my detail viewController and cut and pasted Alexander's code. Worked first time.
Then I discovered that if I started the app in portrait it did not work until I went landscape and back to portrait. Obviously the method is not called until a change of orientation.
So I added this code in my viewDidLoad method of my detail viewController and it worked fine:
//Set up the splitview controller
if (self.splitViewController.displayMode == UISplitViewControllerDisplayModePrimaryHidden) {
self.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;}
splitViewController is a property of your detail viewController that should be there for you to use.
I just have one question related to presenting a from sheet view controller in iOS 8. In iOS 7 I was able to change the height of the view controller using the last line of code in the function below:
SendRemainingEvaluationsViewController *sendRemainingEvaluationViewController = [[[SendRemainingEvaluationsViewController alloc] initWithNibName:#"SendRemainingEvaluationsViewController" bundle:nil]autorelease];
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:sendRemainingEvaluationViewController] autorelease];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navigationController animated:YES completion:nil];
//To Change the default size of the viewController which is presented in UIModalPresentationFormSheet presentation style
navigationController.view.superview.frame = CGRectMake(navigationController.view.superview.frame.origin.x, navigationController.view.superview.frame.origin.y, navigationController.view.superview.frame.size.width, 230);
But this does not work on iOS 8, not sure why? Does anyone knows what is the problem? any idea will be appreciated.
Thanks in advance.
After checking it, I could figure out the problem. After initializing the view navigation controller, its superview is nil and you can't access it right after presenting it. As a workaround, you can change the frame of its superview in the viewwillAppear of the view controller you're trying to present as you can see below:
- (void) viewWillAppear:(BOOL)animated
{
self.parentViewController.view.superview.frame = CGRectMake(self.parentViewController.view.superview.frame.origin.x, self.parentViewController.view.superview.frame.origin.y, self.parentViewController.view.superview.frame.size.width, 230);
}
This should work.
I Currently have a nearly landscape only iPad application on the app store and have been having some issues with the new iOS 6 way on handling rotation locking.
It is a UINavigationController based application and since iOS handles most the responsibility to the rootViewController of the UIWindow I have to manually ask each UIViewController what rotation it wants.
As I have a very large amount of UIViewControllers manually adding code to each Controller to do this would have taken me ages, So I made an extension of both the UINavigationController and UIViewController to override these calls and there I could manually set what views I want to block Portrait to and what ones to allow it to.
UINavigationController-Extension.m:
//
// UINavigationController-Extension.m
// DrivingInstructor
//
// Created by Liam Nichols on 06/12/2012.
// Copyright (c) 2012 Liam Nichols. All rights reserved.
//
#import "UINavigationController-Extension.h"
#implementation UINavigationController (Extension)
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return [self.topViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
-(NSUInteger)supportedInterfaceOrientations
{
return self.topViewController.supportedInterfaceOrientations;
}
-(BOOL)shouldAutorotate
{
return YES;
}
#end
#implementation UIViewController (Extension)
-(BOOL)shouldAutorotate
{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations
{
if ([[self portraitClasses] containsObject:NSStringFromClass([self class])])
{
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskLandscape;
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
if ([[self portraitClasses] containsObject:NSStringFromClass([self class])])
{
return YES;
}
return (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
-(NSArray*)portraitClasses
{
static NSArray *classes;
if (classes == nil)
{
classes = #[ #"MockTestController", #"PLUILibraryViewController", #"PhotoViewController" ];
}
return classes;
}
#end
At fist I thought this had fixed the issue (I also kept the app locked to landscape in the info.plist so that it would launch to lanscape and then in the app delegate I called application:supportedInterfaceOrientationsForWindow: and returned all orientations so that my select views could access portrait if needed.
So this seemed to have worked and all view controllers where locked to lanscape accept the 3 I specified in my extension class.. I was monitoring the extension and whenever I pushed to the new controller it checked for the orientations and locked the app to the specified orientation.
The one issue I found however and can't seem to fix is that when i for example am in portrait on my allowed view controller and try to pop back to the previous view controller what is locked to landscape, supportedInterfaceOrientations is never called again and the view that should be locked to landscape isn't (this causes issues).
As per apples documents, this is how it should work as the responsibility of handling rotation is passed to the rootViewController and as the user isn't rotating their device and the rootViewController isn't changing there is no need to request supportedInterfaceOrientations..
My question is, is there a way to get the application to force call supportedInterfaceOrientations or should I be doing this differently?
Thanks for reading, and If I can find a solution to this last bug then this code might be a good reference to people who are also in the same situation.
-----Edit-----
Was doing some further investigation and found that just before the viewWillAppear: function, supportedInterfaceOrientations is actually in fact called on the controller I am attempting to pop back to and does return the correct mask UIInterfaceOrientationMaskLandscape to try and make it automatically rotate back from portrait however it doesn't seem to listen to this response and still leaves the UIViewController in portrait...
So this means that I do not need to call the supportedInterfaceOrientations again but instead make the device rotate back round to landscape!
According to the documentation you can call:
+ (void)attemptRotationToDeviceOrientation
which if I understand the documentation correctly then would query rotation to the different view controllers again.
Everything in my app is working a treat - but there is one niggling problem.
The UIImagePickerController seems to return the status bar when it is called. Obviously the app has the statusbar hidden throughout.
Now I have worked around this by rehiding it upon completion or canelation of the picker. This resulted in a black bar at the top of the app. So after the rehide I have had to reposition the titlebar and other table contents for it to fit.
All in all this works perfectly fine. However, the UIImagePickerController is called in detail view of a table. Therefore when the user has used the picker (and ive resized after use) and clicks the back button to return to the main table there is a small graphical glitch.
The detail view has been shifted up to hide the statusbar void, yet when I return to the main table and the app slides horizontally back to the main view, for a split second a 20px black box can be seen above the items on the detail view?
To recap. UIImagePickerController returns the staus bar (seemingly no matter what) and after coding to get rid and reformat the view I get a time (messy) graphical issue when returning to the main view.
Surely there is a way to stop the statusbar returning so I dont have to bodge bar back out using code? I have it set 'off' in the plist.
It very odd! Cheers
This helps to me.
1) You must delegate the UIImagePickerController
2) Add this to ViewController:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{ // Esconder el StatusBar. Provocado por el iOS7 y el UIImagePickerController
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
}
implementing UIimagepicker controller use this.and hide status bar in to plist(statusbar initialy hidden=true) and set Uiview size 320x480 & implementing this
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
{
UIImagePickerController *picker= [[UIImagePickerController alloc]init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:picker animated:YES];
[picker release];
}