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 ?
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];
}
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.
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
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)
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];
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.
Related
I have aUITabBarController as my main base view controller. Under the first tab, I have a UINavigationController which of course has a rootViewController associated with it, call it vcA. vcA has a button which fires a child view controller, vcB using the code:
[self performSegueWithIdentifier:#"PlacesDetailsSegue" sender:senderDictionary];
This appears to work, and I see in instruments that new allocations for vcB are occurring.
When I pop the view controller back to vcA, everything looks to work, but it appears that vcB is never released (i.e., dealloc is never called). So every time a go from vcA->vcB->vcA->vcB, the memory usage increases and increases.
I have some instance variables inside vcB, all of which I set to nil in dealloc. But since dealloc isn't being fired, they are never actually set to nil.
I do have a [UIView animationWith...] block which references the frame properties of self.view, but I have managed that using the code:
__unsafe_unretained BBCategoryViewController *weakSelf = self;
[UIView animateWithDuration:duration
animations:^{
[_feedTableView setFrame:CGRectMake(0,
_navBar.frame.size.height,
weakSelf.view.frame.size.width,
weakSelf.view.frame.size.height - kMenuBarHeight)
];
}
completion:nil];
Does anyone have any idea how I can go about finding out what objects are still being retained on a vcB pop?
For reference, my interface extension is:
#interface BBCategoryViewController () <UITableViewDataSource, UITableViewDelegate> {
UITableView *_feedTableView;
UIRefreshControl *_refreshControl;
BBSearchBar *_searchBar;
MKMapView *_mapView;
BBNavigationBar *_navBar;
NSString *_title;
NSString *_categoryId;
NSArray *_feedArray;
}
#end
and my dealloc (which is never fired) is:
-(void)dealloc {
NSLog(#"Dealloc: BBCategoryViewController\n");
_feedTableView = nil;
_refreshControl = nil;
_searchBar = nil;
_mapView = nil;
_navBar = nil;
_feedArray = nil;
}
UPDATE: This was actually due to a retain cycle in a child view, and not directly related to the view controllers as I first thought. Dan F led me to the correct answer here, in case anyone runs across something similar.
Your vcB should absolutely be deallocated, and dealloc called when you pop back to vcA. You must either be keeping a strong reference to it somewhere, or you're doing your "pop" incorrectly. If you're doing that with a segue, that could be your problem -- segues should not be used for going backwards (except unwind segues). So either use an unwind segue or use popViewControllerAnimated to go back to vcA. Also, when using ARC, there's no need to set your ivars to nil in dealloc.
I'm trying to send an array from one viewController to another using protocols and delegates. I declared the delegate on viewcontroller B and used the following code in viewcontroller A to send the message from A to B. The protocol's method is didReceiveMessage. Unfortunately, the message never arrives.
Attached is the code from viewController A
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:gvc animated:YES];
[gvc release];
[delegate didReceiveMessage:brain.internalExpression];
}
and the code from viewcontrollerB
- (IBAction) ViewdidLoad {
CalculatorViewController *cvc =[[CalculatorViewController alloc] init];
cvc.delegate=self;
[cvc release];
}
- (void) didReceiveMessage:(NSMutableArray *)expression {
NSLog(#"message received from CalculatorAppDelegate");
}
Any suggestions would be greatly appreciated.
I'm not sure what are you doing in second sample? You created an object, assign its delegate property and then released it. Why?
If that is code from your application it could probably be from releasing cvc at the end of your ViewDidLoad method.
init would give it a retain count of 1, and the release would take it back to 0.
Also the code seems sort of backwards, if you wanted to set view A as the delegate for view B, you would do so in view A.
Unless there is a more complex reason to use a delegate that I'm not seeing from the code, I would just keep a pointer around to the child if you really need to send it messages.
Like others have posted, you are just getting rid of the Calculator VC after creating it. I would recommend adding an #property to the second VC and using it to store a pointer to the Calculator. It should be a retain property. Then set the delegate of that property to self.
Also, you make sure to use an assign property for the delegate value, and try to use the property syntax (self.delegate) whenever possible.
There could absolutely be more going on here, so if this doesn't actually solve the problem try and post up more of your code (header files, what connections are made in IB, etc.)
Edit: For the record, the method is -(void)viewDidLoad, not -(void)ViewDidLoad, so that could be contributing to the problem.
As you said you are trying to pass an array from one view controller to another.Well i use in this way.Here is the code
- (IBAction) graphPressed:(UIButton *)sender {
GraphingViewController *gvc=[[GraphingViewController alloc] initWithNibName:nil bundle:nil];
gvc.myArray=infoArray;
[self presentModalViewController:gvc animated:YES];
[gvc release];
}
Where myArray is array in your GraphingViewController,You just need to make property of this array with retain attribute,simply as
#property(nonatomic,retain)NSMutableArray *myArray;
And you need to Synthesize it as well.
The Instruments tells me that this piece of code has a leak. However, I am pretty sure I have released it later on. Can anyone tell me what's going on here?
- (void) addReminderEntry{
DataEntryController* item = [[DataEntryController alloc] initWithEntryType:REMINDER]; // it says that the leak was instantiated here
item.delegate = self;
[[self navigationController] pushViewController:item animated:YES];
[item setEditing:YES animated:YES];
[item release];// this is the place I release it
}
Thanks
More than likely it has to do with something not being released within the DataEntryController class. Make sure you are releasing all your properties/etc within that class.
Leaks tells you only where memory was allocated, what it cannot tell you is where to put the code that should have released it to start with!
So this is saying that you made a view controller, and it was still around in memory after you finished with it. Yes you release the VC in that code, but only after you present it - which means the navigation controller has retained it, and possibly other things. It only gets deallocated when the final release is called.
The main culprit for view controllers not being released is usually having the view controller set itself as a delegate for something it retains, and then not undoing that when the view controller goes offscreen. If your view controller is a delegate of something that retains it, it's never going to be deallocated.
It turns out that this is caused by this constructor:
- (DataEntryController*) initWithEntryType:(DataType) eType{
DataEntryController* item = [[DataEntryController alloc] init];//<- here
item.entryType = eType;
item.allowEdit = YES;
return item;
}
Apparently iOS adds retain 1 to each constructor with an initial 'init'.
It works fine after switching to:
DataEntryController* item = [super init];
- (void)launchSearch
{
EventsSearchViewController *searchController = [[EventsSearchViewController alloc] initWithNibName:#"EventsSearchView" bundle:nil];
[self.navigationController pushViewController:searchController animated:YES];
//[searchController release];
}
Notice the [searchController release] is commented out. I've understood that pushing searchController onto the navigation controller retains it, and I should release it from my code. I did just alloc/init it, after all, and if I don't free it, it'll leak.
With that line commented out, navigation works great. With it NOT commented out, I can navigate INTO this view okay, but coming back UP a level crashes with a *** -[CFArray release]: message sent to deallocated instance 0x443a9e0 error.
What's happening here? Is the NavigationController releasing it for me somehow when it goes out of view?
The boilerplate that comes on a UINavigationController template in XCode has the newly-pushed controller getting released. But when I do it, it fails.
---EDIT----
So this morning, I sit down, and it works. No real clue why. Sigh.
Taking what I thought I learned, then, and applying it to another piece of this same controller, I did the following. Yesterday I had this code WITHOUT the release statements, because it didn't work right with them. So this morning I added them to create:
- (IBAction)switchView:(id)sender
{
UISegmentedControl *seg = (UISegmentedControl *)sender;
NSInteger choice = [seg selectedSegmentIndex];
NSArray *array = [mainView subviews];
UIView *oldView = [array objectAtIndex:0];
[oldView removeFromSuperview];
if (choice == 0) {
tableController = [[EventsTableViewController alloc]
initWithNibName:#"EventsTableView" bundle:nil];
[mainView addSubview:tableController.view];
[tableController release];
}
if (choice == 1) {
calendarController = [[EventsCalendarViewController alloc]
initWithNibName:#"EventsCalendarView" bundle:nil];
[mainView addSubview:calendarController.view];
[calendarController release];
}
if (choice == 2) {
mapController = [[EventsMapViewController alloc]
initWithNibName:#"EventsMapView" bundle:nil];
[mainView addSubview:mapController.view];
[mapController release];
}
}
With it set up like this, when I come onto the view, the main portal of my view is filled with the EventsTableViewController's view, I can click to mapView and calendarView, but when I go BACK to tableView, I die because the table delegate methods are being called on a deallocated instance.
So I went and made all of these controllers into synthesized properties, so I can release them in [dealloc]. Which seems to work, but the real question is why adding these views as subviews doesn't retain them, passing ownership to the new view it's a member of, allowing me to release them right there?
Wow, guys. Thanks so much for all your responses--tragically I sent you all on a horrible goosechase.
My NavigationView navigates a NSArray of Event objects (local arts events). My table view drills down to a detail view.
My detail view has in it the following:
-(void)loadEvent:(Event *)event
{
thisEvent = event;
}
And I call that from my table view before pushing the detail view onto the nav stack. thisEvent is a synthesized property of type Event, and so since it's synthesized, I dutifully release'd it in [dealloc].
Many of you already see the problem. Backing up to the table view, when I scroll such that the one I just saw is displayed, it builds the custom table row, and so it goes to get the title property from the Event.... which I just released inside the detail controller. Boom.
I added a retain to that loadEvent: method above and the crashes, they are gone.
NONE of this was really about the views getting retained and released by the navcontroller. It was about accidentally over-releasing the data objects I'm navigating. Part of what had me discover this was, I NSLogged myself in the [dealloc] of each of these view controllers, and I can now see they're behaving exactly as they should.
Thanks! I SO love this site.
I'd guess the fault lies in EventsSearchViewController's init. Is it returning an autoreleased self by mistake ?
Looks like EventsSearchViewController is allocating an array and then over-releasing it, with one of the releases probably in its dealloc.
If you comment out the release, your EventsSearchViewController is never deallocated (it leaks). So, errors that occur as a result of its own dealloc will be masked since that method won't be called. Releasing the controller is the right thing, but you have another bug in the controller itself that only appears at dealloc time.
It could also be that dealloc is releasing an autoreleased array, so you may not have two explicit release calls in your code. But it looks very much like releasing something in dealloc that's causing the problem.
I have a button inside the content of a UIPopoverController. This button runs a method called myAction.
MyAction has the form
- (void) myAction:(id)sender
so, myAction receives the id of the caller button.
Now, inside this method I would like to dismiss the UIPopoverController, but the only thing I have is the ID of the caller button. Remember that the button is inside the UIPopoverController.
Is there a way to discover the ID of the UIPopoverController, given the button ID I already have?
thanks.
Unfortunately no. At least, not within the standard practices. You might be able to travel up the responder stack to find it, but it's a hack, it's buggy, and it's really, really messy.
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover. Usually that would be the owner of the popover (not the controller showed within the popover). When the button is pressed, it can send a message to the owner controller, which can then dismiss the popover.
You might be tempted to have the controller displayed inside of the popover be the owner of its own popover, but coding this way is brittle, can get messy (again), and may result in retain loops so that neither ever gets released.
You can access the presenting popoverController by accessing "popoverController" with KVC.
[[self valueForKey:#"popoverController"] dismissPopoverAnimated:YES]
I have this working, and I do not think it is a hack. I have a standard split view iPad app. I then added a method on my detail controller (the owner of the pop over) to handle the dismissal.
On the standard split view architechture, both the root and detail view controllers are available via the app delegate. So I bound a button click inside the pop over to call a method which gets the app delegate. From there I call the method on the detail controller to dismiss the pop over.
This is the code for the method on the View Controller that is displayed inside the popover:
- (void) exitView: (id)sender {
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.detailViewController exitDrill];
}
Then the simple method to dismiss on the Detail View Controller:
- (void) exitDrill {
if(dtController != nil){
[dtController dismissPopoverAnimated: YES];
[dtController release];
}
}
I like the ability to do this because it give me a way to show a user how they can exit a pop over. This may not be necessary in future versions of the app; for right now, while this paradigm is still new to the platform, I prefer to let the users gexit a display in a couple fo different ways to make sure I minimize frustration.
As Ed Marty already wrote
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover
This is very true; however, when showing a UIPopoverController, the class opening the popovercontroller keeps this resource already. So, what you could do is to use this class as the delegate class for your Popover Controller.
To do so, you could do the following, which I use in my code.
In the class opening the popover, this is my code:
- (void)showInformationForView:(Booking*)booking frame:(CGRect)rect
{
BookingDetailsViewController *bookingView = [[BookingDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped booking:booking];
[bookingView setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookingView];
self.popController = [[UIPopoverController alloc] initWithContentViewController:navController];
[self.popController setDelegate:self];
[self.popController setPopoverContentSize:CGSizeMake(320, 320)];
rect.size.width = 0;
[self.popController presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
- (void)dismissPopoverAnimated:(BOOL)animated
{
[self.popController dismissPopoverAnimated:animated];
}
So what I am doing here is creating a UINavigationController and setting a BookingDetailsViewController as its rootViewController. Then I am also adding the current class as delegate to this BookingDetailsViewController.
The second thing I added is a dismissal method called dismissPopoverAnimated:animated.
In my BookingDetailsViewController.h I added the following code:
[...]
#property (nonatomic, strong) id delegate;
[...]
And in my BookingDetailsViewController.m I added this code:
[...]
#synthesize delegate = _delegate;
- (void)viewDidLoad
{
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeView)];
[self.navigationItem setRightBarButtonItem:closeButton];
[super viewDidLoad];
}
- (void)closeView
{
if ([self.delegate respondsToSelector:#selector(dismissPopoverAnimated:)]) {
[self.delegate dismissPopoverAnimated:YES];
}
else {
NSLog(#"Cannot close the view, nu such dismiss method");
}
}
[...]
What happens is that when the "Close" button in the UINavigationController is pressed, the method closeView is called. This method check if the delegate responds to dismissPopoverAnimated:animated and if so, it calls it. If it does not respond to this method it will show a log message and do nothing more (so it wont crash).
I have written my code using ARC, hence there is no memory management.
I hope this helped you.