Tung Do
Tung Do

Reputation: 1533

Strange issue with UIDocumentInteractionController

I don't know what wrong with this code but everytime when I run the app, after the Menu is shown, the app crash.

NSString * path = [[NSBundle mainBundle] pathForResource:@"tung" ofType:@"doc"];

UIDocumentInteractionController *docController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];

docController.delegate = self;

//[docController presentPreviewAnimated:YES];

CGRect rect = CGRectMake(0, 0, 300, 300);
[docController presentOptionsMenuFromRect:rect inView:self.view animated:YES];

Error I got:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '-[UIPopoverController dealloc] reached while popover is still visible.'

What should I do now ?

Upvotes: 16

Views: 18564

Answers (6)

Mikel Sanchez
Mikel Sanchez

Reputation: 2890

SWIFT 3

Controller variable:

var documentIteratorController : UIDocumentInteractionController?

Call method:

documentIteratorController = UIDocumentInteractionController(url: reportURL)
documentIteratorController?.delegate = self
documentIteratorController?.presentOptionsMenu(from: self.printButton.frame, in: self.view, animated: true)

Upvotes: 1

Anthony Mattox
Anthony Mattox

Reputation: 7118

This error is caused (as others have mentioned) by the UIDocumentInteractionController being released while the presented view controller is still depending upon it. Thats a simple error and creating a strong reference to that view controller, in an reference counted environment, will solve the problem. The object can be released when it's no longer necessary by responding to delegate methods.

The reason this is confusing is that some other tools in Cocoa similar in appearance do not need to be retained the same way. For example UIImagePickerController or UIActivityViewController could be created and presented within a method without problem.

The difference between these other examples and UIDocumentInteractionController is that the other components are all subclasses of UIViewController. When they are pushed onto a navigation stack or presented they are retained by the navigation stack or the presenting view controller. When they are dismissed, that reference is removed andy they are released. UIDocumentInteractionController is not a UIViewController. Instead it provides view controllers which can display the relevant interface, but importantly do not (for good reason as it would cause a retain cycle) retain the document interaction controller. Because of that, whoever is creating the document controller must also maintain strong reference to it as long as it's needed by the presented interface.


This example is essentially the same as the accepted answer, but using ARC friendly style of retaining an object.

@interface MYViewController : UIViewController <UIDocumentInteractionControllerDelegate>

@property (nonatomic, strong) UIDocumentInteractionController *documentInteractionController;

@end

@implementation MYViewController

- (void)presentDocumentWithURL:(NSURL*)url {
    self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:url];
    self.documentInteractionController.delegate = self;
    [self.documentInteractionController presentPreviewAnimated:YES];
}

- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller {
    self.documentInteractionController = nil;
}

@end

Upvotes: 2

EquiAvia Tech
EquiAvia Tech

Reputation: 31

I solved this problem by creating a property and then using this code.

    [_DocController dismissMenuAnimated:NO];
    _DocController = [UIDocumentInteractionController interactionControllerWithURL:url];
    //docController.delegate = self;

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
    {
        [_DocController presentOptionsMenuFromRect:((UIView*)control).bounds inView:control animated:YES];
    }
    else
    {
        [_DocController presentOptionsMenuFromBarButtonItem:control animated:YES];
    }

The dismissMenuAnimated is important to prevent the UIPopover Dealloc error. The most common occurance of the error was when the popOver was still showing, and you pressed the button again to display the popover.

Upvotes: 0

JulianB
JulianB

Reputation: 1686

With Christian's technique...

Should you decide to launch different PDFs from different buttons in the view rather than from the navigation bar, don't use:

[controller autorelease];

Because it will remove the controller, so further instances won't work after the first use.

But if you are using it you may want to say

[self.controller autorelease];

Upvotes: 0

Christian Fries
Christian Fries

Reputation: 16932

To preview a document via a "throwaway" UIDocumentInteractionController you should retain it after interactionControllerWithURL and autorelease it in the UIDocumentInteractionControllerDelegate method documentInteractionControllerDidDismissOptionsMenu. As remarked by David Liu, releasing it will crash. But autoreleasing works. I have checked that dealloc is actually called.

The following code should work:


- (void)previewDocumentWithURL:(NSURL*)url
{
    UIDocumentInteractionController* preview = [UIDocumentInteractionController interactionControllerWithURL:url];
    preview.delegate = self;
    [preview presentPreviewAnimated:YES];
    [preview retain];
}
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
    [controller autorelease];
}

Upvotes: 24

David Liu
David Liu

Reputation: 9601

It's basically the old memory management problem.

[UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]] returns an autoreleased object, so it'll get autoreleased soon after your code block finishes. I'm guessing this is unlike presentModalViewController which will retain a copy for you, but that's a side point.

Basically, you need to retain it before your code block ends. The more annoying part is keeping track of what the docController is doing so you don't leak memory. You'll have to check the result from
[docController presentOptionsMenuFromRect:rect inView:self.view animated:YES];

If it returns NO, that means the menu never showed up, and so you should do a release on it right away (if you already did the retain).

However, if it returns YES, then you'll need to implement the delegate methods for docController, and release it when the menu is dismissed (in this case, it would be when:
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
gets called.

EDIT: I want to make a correction here:

The previous answer will crash if the popup menu is dismissed. Essentially there's really not any good way to create a throwaway DocController. Instead, I think it's best to just create one for every file you need in the viewcontroller, and deallocate when you're completely done. Otherwise you'll run into a myriad of possible cases where the DocController will get released too early and crash.

Upvotes: 8

Related Questions