UIActivity activityViewController being presented modally on iPad instead of in popover - ios

When using a customer UIActivity subclass in iOS 6, it's possible to specify a custom view controller that will be displayed when your action is chosen from the initial UIActionViewController's view. You do this by returning a reference to a custom view controller from your UIActivity subclass's activityViewController method.
According to the UIActivity class reference:
activityViewController
The default implementation of this method returns nil. Subclasses that provide additional UI using a view controller can override this method to return that view controller. If this method returns a valid object, the system presents the returned view controller for you, instead of calling the performActivity method. On iPad, your view controller is presented inside of a popover. On iPhone and iPod touch, your view controller is presented modally.
Your custom view controller should provide a view with your custom UI and should handle any user interactions inside those views. Upon completing the activity, do not dismiss the view controller yourself. Instead, call the activityDidFinish: method and let the system dismiss it for you.
Note that bit at the end of the first paragraph: On iPad, your view controller is presented inside of a popover. On iPhone and iPod touch, your view controller is presented modally.
However, on iPad the view controller returned by activityViewController always displays modally, no matter how I present the UIActivityViewController (either modally or via a popover). When presenting via a popover, it causes it to crash since it doesn't think it's been dismissed.
What am I doing wrong? Is this a bug in iOS 6?
Update: here's a simple Xcode project that illustrates the problem. Feel free to clone it and play around to see if you can see where we're going wrong: github.com/simonwhitaker/GSActivityDemo

As we are talking about the UIActivityViewController, which is the view showing the available activities to the user. Apple state the following...
Your app is responsible for configuring, presenting, and dismissing this view controller. Configuration for the view controller involves specifying the data objects on which the view controller should act. (You can also specify the list of custom services your app supports.) 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.
I took the last line as a sign that you have to handle how the view is presented, so I check whether the code is running on iPad and use a UIPopover accordingly. As you can sere here... https://github.com/bufferapp/buffer-uiactivity/blob/master/BufferUIActivity/Views/FirstViewController.m within the following method.
-(IBAction)openUIActivityView:(id)sender {
NSString *text = #"Hello world";
NSString *url = #"http://bufferapp.com";
NSArray *activityItems = #[text, url];
BufferUIActivity *bufferActivity = [[BufferUIActivity alloc] init];
UIActivityViewController *activityView = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:#[ bufferActivity ]];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[self presentViewController:activityView animated:YES completion:^{
}];
} else {
// Change Rect to position Popover
self.popup = [[UIPopoverController alloc] initWithContentViewController:activityView];
[self.popup presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.width/2, 100, 100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}

I think the issue with the activity view controller not showing in a popover is a bug and the docs reflect the correct intent. However, I don’t know of a way to workaround this atm.
The part about dismissing the view controller, however, is a different issue. You are not supposed to dismiss the view controller that you return from -[UIActivity activityViewController], but you are responsible for dismissing the popover that you have presented, which in turn will also remove your custom view controller from the hierarchy. (And because it works this way, I’m inclined to believe that the custom view controller would normally have to be shown in the popover.)
Here’s an example with code from your example app:
UIActivityViewController *vc = [[UIActivityViewController alloc] initWithActivityItems:activityItems
applicationActivities:applicationActivities];
vc.completionHandler = ^(NSString *activityType, BOOL completed){
[self.activityPopoverController dismissPopoverAnimated:YES];
};

I had the same problem in iOS 7. The solution to show the custom view in the popover is to create and show it in the -(void)performActivity method instead of returning it in -(UIViewController *)activityViewController.
You can see example code in my question/answer under this link:
iOS 7 custom UIActivity as popover

I just had the same problem but solved it by setting the ViewController to:
[yourViewController setModalPresentationStyle:UIModalPresentationPageSheet];
in
- (UIViewController *)activityViewController
hope this helps

Related

UINavigationController: presenting view controller while dismissing another controller is in progress on iPad

I have a view that requires user to be logged in. When the user attempts to open that view an he is not logged in I will call the login view for him to login and after he is done I will call the original view that he intended to see.
On iPhone this works fine as I push view controllers there.
But on iPad where I present view controller this does not work. It says that dismissal in progress, can't show new controller. Here is the code:
- (void) buttonPressed
{
if (!userLoggedIn) { // userLoggedIn getter calls new screens of login if needed
return; // this is executed if user declined to login
}
MyViewController *temp = [[MyViewController alloc] init];
[self.navigationController presentViewController:temp animated:YES]; // this returns warning that dismissal in progress and does not work
}
What can I do about that? On iPhone all of my logic works fine, but on iPad it fails. I use it in many places and completely rewriting code is not good.
EDIT: more code:
- (BOOL) userLoggedIn {
// code omitted
[centerController presentViewController:navController animated:YES completion:nil];
// code omitted
[centerController dismissViewController:navController animated:YES]; // setting to NO does not fix my problem
return YES;
}
EDIT2:
This is the code for iPad. I have removed iPhone-related code. What it does on iPhone - instead of presenting controller it uses pushing, and in that situation everything works fine.
You cannot present another view as long as the dismissing of your 1st view is not complete. The animation of dismissing view should be completed before presenting new view. So, either you can set its animation to NO while dismissing, or use
performSelector:withObject:afterDelay:
and present the next view after 2-3 seconds.
Hope this helps.
You've not posted enough code to really see what you're doing, but one approach to the problem of dismissing and pushing view controllers clashing in this way is to make a the pop+posh into a single atomic operation operation, rather then seqential operations.
You can do this by using the setViewControllers:animated: method on UINavigationController. This allows you to effectively remove one or more view controllers, and add one or more view controllers, all as one cohesive operation, with one seamless animation.
Here's a simple example:
[self.navigationController pushViewController:loginController];
// ... later on, when user login is validated:
NSMutableArray *viewControllers =
[self.navigationController.viewControllers copy];
[viewControllers removeLastObject];
[viewControllers addObject:[[MyNewViewController alloc] init]];
[self.navigationController setViewControllers:viewControllers animated:YES];
If you do things this way, your code will be more predictable, and will work across iPhone and iPad.
For more info, see the API docs.
Update
Since your problem involves a modal dialog on top, try using setViewControllers:animated:NO to change the nav controller stack underneath the modal login dialog before you dismiss the modal.

iOS7 SDK force Landscape for viewController

On iOS6 I had a method to make one view controller in my navigation-style app auto rotate to landscape when I pushed it. (Basically present a bogus view controller and dismiss it in viewWillAppear).
UIViewController *mVC = [[UIViewController alloc] init];
[self presentModalViewController:mVC animated:NO];
if (![mVC isBeingDismissed])
[self dismissModalViewControllerAnimated:NO];
With the latest SDK this no longer works. Does anyone have another way to auto rotate?
Turns out the solution is simple, just pass YES to dismissModalViewControllerAnimated
UIViewController *mVC = [[UIViewController alloc] init];
[self presentModalViewController:mVC animated:NO];
if (![mVC isBeingDismissed])
[self dismissModalViewControllerAnimated:YES]; //Fix here
From Developer site
"When a view controller is presented over the root view controller, the system behavior changes in two ways. First, the presented view controller is used instead of the root view controller when determining whether an orientation is supported. Second, the presented view controller can also provide a preferred orientation. If the view controller is presented full screen, the user interface is presented in the preferred orientation. The user is expected to see that the orientation is different from the device orientation and rotate the device. A preferred orientation is most often used when the content must be presented in the new orientation."
I think here you can use the preferred orientation method here.

How to know when a View Controller unwinds when it´s presented programmatically

I got this code that brings up the native "share" view where the user can post to facebook/twitter etc... There is a completion block, but this only get´s called when the VC shows itself, I need to know when it dismisses. Cause my app has different View Controllers for landscape / portrait mode, and I do not want to dismiss the view if the user rotates and the UIACtivityViewController is on screen.
I send a notification when the shared button is pressed to not dismiss the current view if user rotates the device. All i need now, is to know when it´s dismissed so I can reenable the function
- (IBAction)shareButtonPressed:(UIButton *)sender {
// Notify that another view is on screen to allow rotation without view disapearing.
[self sendNotificationWithName:#"landscapeViewHasPopupActive" andObject:#"empty string"];
NSString *message = #"Hello World!";
UIImage *imageToShare = [UIImage imageNamed:#"Icon.png"];
NSArray *postItems = #[message, imageToShare];
UIActivityViewController *activityVC = [[UIActivityViewController alloc]
initWithActivityItems:postItems
applicationActivities:nil];
[self presentViewController:activityVC animated:YES completion:^() {
}];
// Is showing landscape set to NO, and YES when this view disapears
}
In ios6 storyboards, there is a thing called an unwind segue. Add a method to the presenting controller to verify if unwind can/will happen. Check with google.
The same view controller that called the – presentViewController:animated:completion: method has it's counterpart: the – dismissViewControllerAnimated:completion: method.
When you want to dismiss the activityVC controller call the – dismissViewControllerAnimated:completion: method. Use the 'completion' block to execute the code you want when the view controller is dismissed.
Hope this helps!

When and how to dismiss UIActivityViewController

I create and present UIActivityViewController in my app with custom UIActivity items in it.
When I tap UIActivity icon, UIActivityViewController slides down and my modal view controller is presented. However, when I dismiss my VC, UIActivityViewController shows up.
How can I make it disappear and never shows up again when activity item is pressed?
You need to call the activityDidFinish: method on the chosen UIActivity.
From the docs for UIActivity activityDidFinish::
Discussion
This method dismisses the sharing interface provided by the UIActivityViewController object. If you provided a view controller using the activityViewController method, this method dismisses that view controller too.
You must call this method after completing the work associated with this object’s service. This is true regardless of whether you used the activityViewController or performActivity method to initiate the service. When calling the method, use the Boolean value to indicate whether the service completed successfully.
Let's say when Activity A is chosen from UIActivityVC, you want to present modal view controller M on your current view Controller C .
If you implement A's -(UIViewController*)activityViewController method, you need to call [A activityDidFinish] in your modal view controller M's dismiss method;
If you implement A's -(void) performActivity method, it's impossible to present modal view , because current view controller C is in the process of dismissing UIActivityVC .
I think the final solution is a bit tricky. My basic idea is to subclass UIActivityViewController and override -(void) viewDidDisappear method. Thus you can do whatever you like( i.e present your own modal view,or push a sequence of other view controllers) on your current view controller C.
i found this by Ethan Huang
[self presentViewController: activityController animated: YES completion:nil];
activityController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = activityController.popoverPresentationController;
popPC.barButtonItem = saveBtn;
popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
read all about it here :
http://getnotebox.com/developer/uiactivityviewcontroller-ios-8/

Present Modal View Controller from inside Popover View

So in my universal app I have a section where a person can look at an existing list of notes from our system (retrieved through a simple web service) and then also create a new note if they want. So for the iphone it's pretty simple layout, a TableViewController for displaying the list with a "Add" button on the NavigationBar that presents the modalview for adding the new item. On the iPad though, the same layout has a lot of wasted space so I opted to go with the popOver method to show the list in a popOver and then let them add from there. My problem is that when the user clicks on the Add button within the PopOver view, the modal view comes up full screen instead of just coming up within the popover view. Here's the code I have so far:
-(void) AddButtonPressed:(id)sender {
NewNoteVC *newNote = [[[NewNoteVC alloc] initWithNibName:#"NewNoteVC" bundle:nil] autorelease];
newNote.defaultClientID = defaultClientID;
UINavigationController *navCon = [[[UINavigationController alloc] initWithRootViewController:newNote] autorelease];
if ([isPopOver isEqualToString:#"YES"]) {
[navCon setModalInPopover:YES];
[self.navigationController setModalInPopover:YES];
[self.navigationController presentModalViewController:navCon animated:YES];
}
else {
[self.navigationController presentModalViewController:navCon animated:YES];
}
}
The "isPopOver" string is just a placeholder sent from the previous screen that called this TableView (I know I can switch this to a boolean for better performance I just put this together real quick to try it out). I know I messed up somewhere, I just don't know what setting I need to get this working correctly.
You need to define the view controller's modalPresentationStyle to be "current context".
navCon.modalPresentationStyle = UIModalPresentationCurrentContext;
This will result in modal view controller filling the popover like the popover's root controller.
Try using the presentViewController:animated:completion: instead of presentModalViewController:animated: and set self.navigationController.definesPresentationContext = YES

Resources