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?

Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
  • How are you dismissing the UIPopoverViewController? From past experience you need to dismiss the popover itself explicitly by calling [myPopoverControler dismissPopoverAnimated]; – Mitchell Currie Oct 03 '12 at 00:52
  • 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. – matt Feb 02 '13 at 02:45
  • 1
    @matt 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`. – Stephen Darlington Feb 03 '13 at 14:37
  • 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. – matt Feb 03 '13 at 16:43

8 Answers8

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.

Alex Curylo
  • 4,744
  • 1
  • 27
  • 37
  • 1
    Yup, that did the trick. (It's still displaying as a modal form rather than a pop-over but that's a different question...) – Stephen Darlington Oct 03 '12 at 06:17
  • 1
    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? – ragfield Nov 16 '12 at 15:47
  • @ragfield You're right. And this behaviour shows in my test app, too. I raised a Radar about this. No response from Apple yet. – Stephen Darlington Nov 17 '12 at 19:53
  • 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. – matt Feb 02 '13 at 02:57
  • 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. – jrwagz Aug 31 '13 at 20:29
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. :)

eploko
  • 5,347
  • 2
  • 28
  • 23
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;
    }
aironik
  • 105
  • 1
  • 4
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.

AndyDev
  • 1,409
  • 10
  • 17
  • 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). – Stephen Darlington Sep 27 '12 at 12:55
  • Odd, just double checked in our project and it is working fine for both iPhone & iPad Simulators. – AndyDev Sep 27 '12 at 12:56
  • 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. – Stephen Darlington Sep 27 '12 at 19:17
  • 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. – AndyDev Sep 28 '12 at 09:36
  • I can't (easily) put my actual code on GitHub but I have just added a sample project that demonstrates the problem: https://github.com/sdarlington/ActivityViewContollerBug – Stephen Darlington Sep 30 '12 at 10:42
  • 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. – AndyDev Sep 30 '12 at 11:19
  • 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... https://github.com/bufferapp/buffer-uiactivity – AndyDev Sep 30 '12 at 11:34
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;
    }
}
Weel
  • 132
  • 2
  • 7
0

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

Hope it helps!

Thermometer
  • 2,567
  • 3
  • 20
  • 41
  • 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?). – Stephen Darlington Sep 22 '12 at 17:33
  • Ah okay, too bad :( And indeed, sorry, I meant `UIActivityIndicatorView` indeed. – Thermometer Sep 24 '12 at 07:26
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.

Nidal Fakhouri
  • 139
  • 1
  • 4
-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...

Noah
  • 36
  • 4
  • 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. – Stephen Darlington Oct 03 '12 at 05:53