UIActivity activityViewController not dismissing on iPad
Asked Answered
H

8

9

I have a UIActivity subclass that creates its own activityViewController:

- (UIViewController *)activityViewController {
    WSLInProgressViewController* progressView = [[[WSLInProgressViewController alloc] init] autorelease];
    progressView.message = [NSString stringWithFormat:NSLocalizedString(@"Posting to %@...",@"Posting to..."),
                        self.activityType];

    return progressView;
}

I've add a full repro on GitHub.


According to the documentation, you aren't supposed to dismiss this manually. Instead, the OS does that when you call activityDidFinish:. This works fine when ran on an iPhone.

When I say "works," this is the sequence of events that I'm expecting (and see on the iPhone):

  1. Display the UIActivityViewController
  2. User presses my custom activity
  3. My view controller appears
  4. I call activityDidFinish:
  5. My custom view controller is dismissed
  6. The UIActivityViewController is also dismissed

However, when I run this same code on the iPad Simulator -- the only difference being that I put the UIActivityViewController in a popup, as the documentation says you should -- the activityViewController never dismisses.

As I say, this is code wo/the popUP works on the iPhone and I have stepped through the code so I know that activityDidFinish: is getting called.

I found this Radar talking about the same problem in iOS6 beta 3, but it seems such fundamental functionality that I suspect a bug in my code rather than OS (also note that it works correctly with the Twitter and Facebook functionality!).


Am I missing something? Do I need to do something special in the activityViewController when it's run in a UIPopoverViewController? Is the "flow" supposed to be different on the iPad?

Hummocky answered 22/9, 2012 at 12:4 Comment(4)
How are you dismissing the UIPopoverViewController? From past experience you need to dismiss the popover itself explicitly by calling [myPopoverControler dismissPopoverAnimated];Swashbuckling
Did you come up with a solution for this? It appears to be a serious bug. Basically it means that a custom UIActivity that implements activityViewController is useless on iPad.Microreader
@Microreader The accepted answer below is the best I've seen. I raised Radar 12545554 for the "not dismissing" bug and 12545600 for "is appearing modally." Please dupe if you get the chance. They're both still open. For my app I ended up not using activityViewController.Hummocky
Thanks @StephenDarlington - I did indeed submit a bug report on this. The answer you accepted below works once but not the second time, and if you examine the dealloc of the various classes involved you can see that serious memory management errors are happening under the hood.Microreader
L
8

The automatic dismissal only appears to happen when your 'activity' controller is directly presented, not wrapped in anything. So just before showing the popup it's wrapped in, add a completion handler

activity.completionHandler = ^(NSString *activityType, BOOL completed){
   [self.popup dismissPopoverAnimated:YES];
};

and you'll be good.

Lenzi answered 3/10, 2012 at 1:15 Comment(5)
Yup, that did the trick. (It's still displaying as a modal form rather than a pop-over but that's a different question...)Hummocky
This appears to only delay the problems. The second time you choose the custom activity the app crashes. As far as I can tell the first UIActivityViewController & UIViewController are leaked, and when you try to create a second one it crashes with some stale data. Does anyone have any ideas how to fix this?Thain
@Thain You're right. And this behaviour shows in my test app, too. I raised a Radar about this. No response from Apple yet.Hummocky
Also try logging dealloc in the activityViewController and the custom UIActivity. It is being called twice on the activityViewController and meanwhile the UIActivity object is leaking. That's very very wrong. Something is wrong with how this is being managed on iPad.Microreader
This is just a patch/workaround to the real issue. See eploko's answer below for the real root cause. Per Apple's documentation you SHOULD NOT dismiss the UIActivityViewController directly. When you dismiss it directly you will notice that the dismissal animations are not displayed correctly.Funnel
W
5

I see the question is quite old, but we've been debugging the same view-controller-not-dismissing issue here and I hope my answer will provide some additional details and a better solution than calling up -dismissPopoverAnimated: manually.

The documentation on the UIActivity is quite sparse and while it hints on the way an implementation should be structured, the question shows it's not so obvious as it could be.

The first thing you should notice is the documentation states you should not be dismissing the view controller manually in anyway. This actually holds true.

What the documentation doesn't say, and what comes as an observable thing when you come across debugging the non-dissmissing-view-controller issue, is iOS will call your -activityViewController method when it needs a reference to the subject view controller. As it turns out, probably only on iPad, iOS doesn't actually store the returned view controller instance anywhere in it's structures and then, when it wants to dismiss the view controller, it merely asks your -activityViewController for the object and then dismisses it. The view controller instantiated in the first call to the method (when it was shown) is thus never dismissed. Ouch. This is the cause of the issue.

How do we properly fix this?

Skimming the UIActivity docs further one may stumble accross the -prepareWithActivityItems: method. The particular hint lies along the following text:

If the implementation of your service requires displaying additional UI to the user, you can use this method to prepare your view controller object and make it available from the activityViewController method.

So, the idea is to instantiate your view controller in the -prepareWithActivityItems: method and tackle it into an instance variable. Then merely return the same instance from your -activityViewController method.

Given this, the view controller will be properly hidden after you call the -activityDidFinish: method w/o any further manual intervention.

Bingo.

NB! Digging this a bit further, the -prepareWithActivityItems: should not instantiate a new view controller each time it's called. If you have previously created one, you should merely re-use it. In our case it happily crashed if we didn't.

I hope this helps someone. :)

Wrought answered 6/6, 2013 at 12:19 Comment(1)
Perfect explanation! Thanks for taking the time to really find the true root cause here.Funnel
G
2

I had the same problem. It solved for me saving activityViewController as member and return stored controller. Activity return new object and dismiss invoked on new one.

    - (UIViewController *)activityViewController {
        if (!self.detaisController) {
            // create detailsController
        }
        return self.detailsController;
    }
Granulation answered 30/5, 2013 at 14:55 Comment(0)
B
1

I pass through the UIActivity to another view then call the following...

[myActivity activityDidFinish:YES];

This works on my device as well as in the simulator. Make sure you're not overriding the activityDidFinish method in your UIActivity .m file as I was doing previously. You can see the code i'm using here.

Bauxite answered 27/9, 2012 at 12:39 Comment(7)
Thanks for your answer but, no, I'm not overriding activityDidFinish:. As I say in the question, the same code works fine on the iPhone (Simulator and device) but not on the iPad (Simulator and device).Hummocky
Odd, just double checked in our project and it is working fine for both iPhone & iPad Simulators.Bauxite
I'm not convinced that the sample code is correct -- it dismisses the view controller even though Apple's docs say not to -- and it doesn't seem to work as I'd expect either... the menu doesn't disappear when after the activity is dismissed. Having said that, I am going to expand the question to explain what I think should be happening; maybe I have it wrong.Hummocky
Removing that line also works, my project also supports iOS5 so I need to find a way of ensuring it is only called when needed. Re-reading your question that is exactly how it should work and does so with Facebook & Twitter. Do you have your project on GitHub? I'm happy to take a look for you as we're trying to build up a collection of activities at uiactivities.com.Bauxite
I can't (easily) put my actual code on GitHub but I have just added a sample project that demonstrates the problem: github.com/sdarlington/ActivityViewContollerBugHummocky
Just looking at this now. As you note it should be displayed in a Popover on iPad which I am not currently doing. I'm going to try and implement this now and see if I hit the same stumbling block and will let you know how I get on.Bauxite
I think it might be an issue with Storyboards. I have implemented the popover for iPad (thanks for the spot btw) and have no issues again. You can see the changes I made here... github.com/bufferapp/buffer-uiactivityBauxite
W
1

a workaround is to ask the calling ViewController to perform segue to your destination ViewController via - (void)performActivity although Apple does not recommend to do so.

For example:

- (void)performActivity
{

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
    {
        [self.delegate performSomething]; // (delegate is the calling VC)
        [self activityDidFinish: YES];
    }
}

- (UIViewController *)activityViewController
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
    {
        UIViewController* vc=XXX;

        return vc;
    }
    else
    {
        return nil;
    }
}
Walhalla answered 17/4, 2013 at 8:54 Comment(0)
E
0

Do you use storyboards? Maybe in your iPad storyboard, the UIActivityIndicatorView doesn't have a check on "Hides When Stopped"?

Hope it helps!

Eleonoreleonora answered 22/9, 2012 at 12:42 Comment(2)
Good thought, but no. There are no Storyboards or XIBs. WSLInProgressViewController is derived from UIViewController rather than a UIActivityIndicatorView (which I assume is what you mean by UIActivityView?).Hummocky
Ah okay, too bad :( And indeed, sorry, I meant UIActivityIndicatorView indeed.Eleonoreleonora
S
0

So I had the same problem, I had a custom UIActivity with a custom activityViewController and when it was presented modally it would not dismiss not matter what I tried. The work around I choose to go with so that the experience remained the same to the user was to still use a custom UIActivity but give that activity a delegate. So in my UIActiviy subclass I have the following:

- (void)performActivity
{
    if ([self.delegate respondsToSelector:@selector(showViewController)]) {
        [self.delegate showViewController];    
    }

    [self activityDidFinish:YES];
}

- (UIViewController *)activityViewController
{
    return nil;
}

Then I make the view controller that shows the UIActivityViewController the delegate and it shows the view controller that you would otherwise show in activityViewController in the delegate method.

Sidewheel answered 29/1, 2014 at 19:14 Comment(0)
H
-1

what about releasing at the end? Using non-arc project!

[progressView release];

Many Users have the same problem as u do! Another solution is:

UIActivityIndicatorView *progress= [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 50, 30, 30)];
progress.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
[alert addSubview:progress];
[progress startAnimating];

If you are using storyboard be sure that when u click on the activityind. "Hides When Stopped" is clicked!

Hope that helped...

Himyarite answered 2/10, 2012 at 22:33 Comment(1)
Thanks for your answer, however... progressView is autoreleased, so adding a new release would cause it to be over-released. WSLInProgressViewController is not just a fancy UIActivityIndicatorView so it doesn't have the startAnimating method.Hummocky

© 2022 - 2024 — McMap. All rights reserved.