Popover deallocating content view controller directly after initialization - objective-c

I have a weird problem where a UIPopovercontroller is immediately deallocating its content view controller after loading the popover, and then reinitializing it.
My goal is to read a textField when the popover is being dismissed.
My impression was that I create a UIViewController and set it as the content view controller for the popover. The PopoverViewController will then retain the content view controller and I can (auto)release it.
Later, when the popover is being dismissed, it will release the popover (and with it the content view controller). But that's not working. This is my relevant code:
- (IBAction)popoverButton:(id)sender {
// Create & Initialize content view controller
ContentViewController* cvc = [[[ContentViewController alloc] initWithNibName:#"ContentViewController" bundle:nil] autorelease];
// Create, initialize and load popover
popoverController = [[UIPopoverController alloc] initWithContentViewController:cvc];
[popoverController setDelegate:self];
[popoverController presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
NSLog(#"popoverButton: %#, retainCount: %d", cvc, [cvc retainCount]);
}
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)senderPopoverController
{
NSLog(#"popover should dismiss");
ContentViewController *dvc = (ContentViewController *)([popoverController contentViewController]);
NSLog(#"%# %# %#", dvc, [dvc testTextfield], [[[dvc testTextfield] text] description]);
return YES;
}
ContentViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[self testTextfield] setText:#"Bla"];
NSLog(#"viewDidLoad: %#", testTextfield);
}
- (void)dealloc {
NSLog(#"dealloc: %#", testTextfield);
[testTextfield release];
[super dealloc];
}
When I open the popover, the Log would be (I think the order of the output does not represent the order when it is actually called):
Popover Test[2363:707] viewDidLoad: <UITextField: 0x185750; ...>
Popover Test[2363:707] viewDidLoad: (null)
Popover Test[2363:707] popoverButton: <ContentViewController: 0x1844e0>, retainCount: 4
Popover Test[2363:707] dealloc: <UITextField: 0x185750; ...>
And when I dismiss it:
Popover Test[2363:707] popover should dismiss
Popover Test[2363:707] <ContentViewController: 0x1844e0> (null) (null)
Popover Test[2363:707] popover did dismiss
Popover Test[2363:707] <UIPopoverController: 0x184860>
Popover Test[2363:707] dealloc: (null)
So my questions would be:
Why is the ContentViewController deallocated and initialized a second time?
Why do the outlets (textField) not work anymore when its loaded the second time?
If I could solve this, I would be able to read from the textField in popoverControllershouldDismissPopover

Since ContentViewController is your class, implement the appropriate init* method (if you haven't already), set a break point and the debugger will stop on it at each allocation, answering your question as to why it is being recreated.
Note that retainCount is useless; don't call it.
Ah -- OK -- so, you are creating one instance when you are loading the nib file and a second instance directly in your code. Instead, you want an outlet somewhere that is connected to the instance in the nib file.
As for retainCount; Calling -retainCount Considered Harmful and When to use -retainCount?

Related

Cannot dismiss the Search view

I have a parent class with tableview and searchbar over it which is a subclass of tableview controller. Delegates for the searchBar and searchdisplaycontroller are set in a seperate class inherited from UISearchdisplaycontroller. The datasource and delegates for tableview and searchbar are handled in this class seperately. The classes are under ARC.
Hence, When a user taps on search, the control transfers from FilesListController (parent)class to this class. Now, When a user taps on cancel button, the searchbar delegate set in this class i.e.
- (void)searchBarCancelButtonClicked:(UISearchBar *) searchBar
is CALLED but DOESN'T serve the purpose of dismissing the full screen searchtableview and return to the parentviewcontroller. However, if I don't write this delegate in the search class, it works properly. I have set the searchbar delegates in xib and on calling:
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
like this:
self.searchResultsTableView.delegate = self;
self.searchResultsTableView.dataSource = self;
[parentFileViewController.searchDisplayController setDelegate:self];
Where am I going wrong? Thanks in advance.
If you want to dismiss a UISearchBar with a SearchBarController, just use this Code:
[self.searchDisplayController setActive:NO animated:YES];
you should implement resign the responder in the delegate function i.e
- (void)searchBarCancelButtonClicked:(UISearchBar *) searchBar {
[searchBar resignFirstResponder];
}
Memory warnings can appear at any time during the application run time, you must assume a memory warning will happen and the view and disposable objects will have to be recreated.
We are handling such situation by setting to nil our arrays:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if([self isViewLoaded] && self.view.window == nil)
{
self.view = nil;
keys = nil;
names = nil;
errorDuringNetworkCall = nil;
}
}
And by dismissing the search bar tableview before performing the segue operation:
[self performSegueWithIdentifier:#"navigateToNextScreen" sender:self];
self.searchBar.text = #"";
[self.searchDisplayController setActive:NO animated:YES];
After a Memory warning is received the viewDidLoad method is called again and the arrays are populated, the search bar will continue to be useful.work without issues

Heap memory is growing

Today I made some tests and I am curious of the results. I made an app (ARC) which have UINavigationController and two UIViewControllers. In the first view there is a button and when that button is pressed the second view is loaded. In the second view when shake gesture is detected the first view is loaded and so on. What I notice in instruments is that the heap grows every time when a view is loaded. Here is some code
AppDelegate.m
self.navigationController = [[UINavigationController alloc]init];
self.window setRootViewController:self.navigationController];
FirstViewController *firstview = [FirstViewController alloc]init];
[self.navigationController pushViewController:FirstViewController animated:YES];
FirstViewController.m
-(IBAction)loadSecondView
{
SecondViewController *secondview = [SecondViewController alloc]init];
[self.navigationController pushViewController:secondview animated:YES];
}
SecondViewController.m
-(IBAction)loadFirstView
{
FirstViewController *firstview = [FirstViewController alloc]init];
[self.navigationController pushViewController:first view animated:YES];
}
I can't figure out why that happens. How to avoid heap of growing in that case ?
Actually every time you are creating a new view controller object.. That should not be done.
So every time you allocate a new object and pushed that view, it will be added to the navigation stack and so, the memory grows.
Instead, when you are in first view and tapped the button, you can pop the current view controller and inform the AppDelegate class to show the second view.
Similarly while in second view, when you want to show the first view, pop the current view and inform the AppDelegate class to push the first view controller.
SecondViewController *secondview = [[[SecondViewController alloc]init] autorelease];
FirstViewController *firstview = [[[FirstViewController alloc]init] autorelease];
you should autorelease viewcontrollers (for not ARC)
if second controller opens first, you should do popViewController. If you wont return back, heap will grow

iOS UISplitViewController's Popover controller button disappear after pushing new view controller in portrait mode

In my UISplitViewController application, I have
RootViewController - view controller in the left pane.
DetailViewController - view controller in the right pane.
When one item (which is in a UITableView) in RootViewController is tapped, new view controller will be set as the following shows:
[detailViewController setViewControllers:[NSArray arrayWithObjects:newViewController, nil] animated:animated];
//detailPane is my DetailViewController
All works pretty well in landscape mode. However, I can't make the UISplitViewController work as what I want in portrait mode, that is, the RootViewController's popover button does not appear appropriately in my DetailViewController when I launch and use the application in portait mode.
When I launch the app in portrait mode, the popover button appears appropriately. But after tapping one item in the popover and a new view controller has been set on detailViewController, the button disappeared. I have to rotate the device to landscape and then back to portrait again to make the button appear again.
I set my UISplitViewController's delegate in my application's AppDelegate as follows:
self.splitViewController.delegate = self.detailViewController
And here is my UISplitViewControllerDelegate implementation
- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc {
NSLog(#"Will hide view controller");
barButtonItem.title = #"Menu";
[self.navigationItem setLeftBarButtonItem:barButtonItem];
self.popoverController = pc;
}
- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
NSLog(#"Will show view controller")
NSMutableArray *items = [self.navigationItem.leftBarButtonItems mutableCopy];
[items removeAllObjects];
[self.navigationItem setLeftBarButtonItems:items animated:YES];
[items release];
self.popoverController = nil;
}
Any hint or help is greatly appreciated.
Thanks.
Just came up with a new solution.
Subclass UINavigationController and implement UISplitViewControllerDelegate. Set an instance of this class as the right ViewController of the splitViewController. Everytime you want to change the detail view controller from the master
NewDetailViewController *newDetailVC = ....// Obtain the new detail VC
newDetailVC.navigationItem.leftBarButtonItem = [[[[self.splitViewController.viewControllers objectAtIndex:1]topViewController]navigationItem ]leftBarButtonItem]; //With this you tet a pointer to the button from the first detail VC but from the new detail VC
[[self.navigationController.splitViewController.viewControllers objectAtIndex:1]setViewControllers:[NSArray arrayWithObject:newDetailVC]]; //Now you set the new detail VC as the only VC in the array of VCs of the subclassed navigation controller which is the right VC of the split view Controller
This works for me and I can avoid defining a hole protocol and setting the master as the delegate, which is a big trade off. Hope it helps.
If you still need it:
http://developer.apple.com/library/ios/#samplecode/MultipleDetailViews/Introduction/Intro.html
What I did to my source (I had similar setup to you) to fix it:
I have the master viewcontroller (UITableViewController in my case) be the delegate of the UISplitViewController. In the two delegate methods for UISplitViewControllers (so this would be in your master viewcontroller implementation) you would save the popupviewcontroller and the barbuttonitem in your class. Now, if you change your details viewcontroller, you do:
self.viewControllers = [NSArray arrayWithObjects:[self.viewControllers objectAtIndex:0], newDetailsViewController, nil];
UIViewController <SubstitutableDetailViewController>*vc = (UIViewController <SubstitutableDetailViewController>*)newDetailsViewController;
[vc invalidateRootPopoverButtonItem:_tableViewController.rootPopoverButtonItem];
[_createReportViewController showRootPopoverButtonItem:_tableViewController.rootPopoverButtonItem];
where we have
#protocol SubstitutableDetailViewController
- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem;
- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem;
#end
the delegate that each of your detailsViewControllers should adhere to. You would implement like this:
- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem {
self.navigationItem.leftBarButtonItem = barButtonItem;
}
- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem {
self.navigationItem.leftBarButtonItem = nil;
}
Let me know if this helps you.
I liked Nekto's solution, but it misses one key problem.
It's not clear what action: selector will cause the UISplitViewController to show the MasterViewController in a popover. When I finally figured this out, by examining the BarButtonItem in the debugger, I realized why it was so tricky to figure this out: the action: selector isn't documented anywhere in Apple's iOS SDK. Oops.
Try this:
UIBarButtonItem *showListView = [[UIBarButtonItem alloc] initWithTitle:#"List" style:UIBarButtonItemStyleBordered target:[self splitViewController] action:#selector(toggleMasterVisible:)];
[[detailViewController navigationItem] setLeftBarButtonItem:showListView];
You may want to surround this code with a conditional that checks the window is in in portrait mode, such as if ([self interfaceOrientation] == UIInterfaceOrientationPortrait)
When you are setting new view controllers placed on navigation stack, probably, all navigation buttons are reset. You can manually add appropriate buttons after changing navigation stack.
For example, you can pick code from - (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc where default popover controller button is created:
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:#"Menu" style:UIBarButtonItemStyleBordered target:self action:#selector(appropriateSelector)];
[self.navigationItem setLeftBarButtonItem:barButtonItem];
self.popoverController = pc;

calling addSubview in initWithNibName: causes viewDidLoad (and other UI Object inits)to fire before the addSubview Call executes

I'm adding a button in the middle of my initWithNibName:bundle:, when i add the button view to self.view, the view goes to start to initialize before it add's the button. So the Code in viewDidLoad gets fires before the initWithNibName:bundle: is finished. There is code below the addSubview that is relied on in the viewDidLoad and causes it to crash/not work since the init code has not run.
I've had the same experience when I added the button code to the viewDidLoad method. There is a UITableView in the .xib and the table gets inited before the rest of the viewDidLoad gets run and caused the tableView to get bad Data.
What is the best practice for adding a view to a view when you are initing and loading the view? just put all the addSubViews before the Return?
Thanks!
Here is my initWithNibName:bundle:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nil];
[self setIoUIDebug:(IoUIDebugSelectorNames)];
if (IoUIDebug & IoUIDebugSelectorNames) {
NSLog(#"%# - %#", [self description], NSStringFromSelector(_cmd) );
}
CGRect frame = CGRectMake(20, 521, 500, 37);
saveButton = [UIButton newButtonWithTitle:NSLocalizedStringFromTable(#"Save Animation Label",#"ScreenEditor",#"Save Animation Label")
target:self
selector:#selector(saveButtonPressedAction:)
frame:frame
image:[UIImage imageNamed:#"BlueButtonSmall.png"]
imagePressed:[UIImage imageNamed:#"BlueButtonSmallPressed.png"]
darkTextColor:NO];
[self.view addSubview:saveButton]; // <- Right here I'll hit breakpoints in other parts of viewDidLoad and cellForRowAtIndexPath, before the lined below get executed.
[saveButton setEnabled: NO];
[saveButton setUserInteractionEnabled: NO];
newAnimation = nil;
selectedSysCDAnimation = nil;
selectedIoCDTag = nil;
animationSaved = NO;
return self;
}
You should add the subviews inside viewDidLoad this will mean that the views are added when the main view is loaded into memory. I would reserve your initWithNibName:bundle: call for custom initialization and not interacting with the UI as this what viewDidLoad is designed for.
In regards to your tableView, you should put a call to load the tables datasource inside of viewDidLoad. Once the datasource is loaded, you can simply call reloadData on the tableview to load the data into the tableview.
For Example:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:saveButton];
[self loadDataSource];
}
- (void)loadDataSource {
// load datasource here
[self.tableView reloadData];
}
Any access to the view property of the view controller will lazily initialize the view. This will trigger a call to viewDidLoad which will execute before the access to the view property returns in initWithNibName:. You should add the sub view in viewDidLoad or using interface builder.

"EXC_BAD_ACCESS" in switching to another view

I have MainMenuViewController with button which action is
- (IBAction) goToFirstView {
FirstViewController *fvc = [[FirstViewController alloc] init];
[self.view addSubview:fvc.view];
[fvc release];
}
FirstViewController have UIButton with action
- (IBAction) rightArrow {
SecondViewController *svc = [[SecondViewController alloc] init];
[self.view addSubview:svc.view];
[svc release];
}
But when I press "rightArrow" button app crashes with "EXC_BAD_ACCESS". Can't found my problem. Help me please.
[svc release];
The problem is here. When releasing the view controller, the view's events will target a freed object, and make your program crash (probably in viewDidLoad or viewDidAppear if it's instant but it doesn't matter). Note that a view does not (normally, AFAIK) retain it's view controller, if that might have been your assumption...
When you say [self.view addSubview:svc.view] you're adding SecondViewController's view to FirstViewController's view. Similar with MainViewController and FirstViewController. What you'll end up with is a view hierarchy that looks like this:
main view
first view
second view
I doubt that's really what you want. Instead, use a navigation controller with your MainViewController as the nav controller's root controller, and then use -pushViewController:animated: to push the controllers (not the views!) onto the navigation stack.