"EXC_BAD_ACCESS" in switching to another view - objective-c

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.

Related

How do I implement a UINavigationController in this case?

current version of my project :
I have 5 different UIViewControllers in my app. I've set my
FirstViewController to be the Initial View Controller using the
Attributes Inspector. I move back and forth from one ViewController to
another by using buttons to which I assign modal segues, from one
ViewController to another, using the StoryBoard
What I want to change:
I want to keep the navigation buttons obviously, delete the modal segues and use
a UINavigationController instead. If I understand the concept
correctly, when using a UINavigationController I need to go into each
UIButton-IBAction and at the very end of the method I have to push the next
ViewController I want to move to, onto my NavigationController (do I also
have to pop the current one first?). However, I can't figure out how
to implement all that correctly.
What I've done so far:
I removed all modal segues from the storyboard and kept the navigation buttons along with their corresponding IBActions
I unchecked the box in the Attributes Inspector that was making my FirstViewController the initial View Controller of my app
I went into my AppDelegate.m and tried to create the Navigation Controller there and make my FirstViewController be the RootViewController
MyAppDelegate.m
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIViewController *myFirstViewController = [[FirstViewController alloc] init];
UINavigationController *myNavigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
[myNavigationController pushViewController:myFirstViewController animated:YES];
// Override point for customization after application launch.
return YES;
}
I then tried to test if the above was working by going into the IBAction of a
navigation button on my FirstViewController and implemented the
following in order to move to my SecondViewController when the
button is pressed :
FirstViewController.m
- (IBAction)goRightButton:(UIButton *)sender
{
// some code drawing the ButtonIsPressed UIImageView on the current View Controller
UIViewController *mySecondViewController = [[SecondViewController alloc] init];
[self.navigationController pushViewController:mySecondViewController animated:YES];
}
but nothing happens. What am I doing wrong ?
You are not linking your XIB file. Please add your navigation controller as
UIViewController *myFirstViewController = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
Use following code to move from one view to another
UIViewController *mySecondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
[self.navigationController pushViewController:mySecondViewController animated:YES];
If you are using a storyboard, you should just drag in the navigation controller there and hook it up to your app delegates. As long as it is the main storyboard, and you have identified a view controller to load first, you do not need to load any views in your app delegate.
In order to push a view programmatically that's in a storyboard, you need to do something like the following:
//bundle can be nil if in main bundle, which is default
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
MyCustomViewController *customVC = (MyCustomViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"customVC"];
//standard way
[self.navigationController pushViewController:customVC animated:YES];
//custom animation
[UIView transitionWithView:self.navigationController.view duration:0.5 options:UIViewAnimationOptionTransitionCurlUp animations:^{
[self.navigationController pushViewController:customVC animated:NO];
} completion:nil];
You identify the view controller with the identifier you add in the storyboard editor. Below are some screenshots to help show what I mean.

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

Identify when I return to ViewController

I have two views with their controllers.
The application start with the FirstViewController.
Then with a button I open the SecondViewController.
With other button I dismiss the SecondViewController to return to FirstViewController.
Is there any way to detect that in FirstViewController that it has recovered focus?
EDIT: Ok I look the answers and I use viewWillAppear but don't work if I use a UIModalPresentationFormSheet.
-(IBAction)openSecondView{
SecondViewController *screen = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
screen.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:screen animated:YES];
[screen release];
}
And close this view with close button.
viewWillAppear never called.
implement UIViewController's viewWillAppear method
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}
Add a delegate (Protocol) method. Call the delegate method just before dismissing the SecondViewController.
For more info on delegate, see delegate
implementation of the viewWillAppear: callback will work only if you use navigation controller or tabbarcontroller for displaying another controller + with such approach you will need to check somehow if this is only the first appearance of the view, or was called by any other reason;
using a delegate, as described by Gomathi above is a much better option!
Depends how you are setting up the first view controller. Encapsulate it within UINavigationViewController (and if you do not want the navigation bar, you can always set it to hidden ( [self.navigationController.navigationBar setHidden:YES] ). ViewWillAppear will work then.
inside the viewWillAppear of the FirstViewController
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(#"view 1 focused");
}

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;

Back button not appearing on UINavigationController

I have a UINavigationController setup in my AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add the navigation controller's view to the window and display.
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
In my RootViewController I am pushing another view onto the stack:
//Show the deals
DealViewController *dvc = [[DealViewController alloc] initWithNibName:#"DealViewController" bundle:nil];
[self.navigationController.navigationBar setHidden:NO];
[self.navigationController pushViewController:dvc animated:YES];
The view shows up, but there is no back button that is added to my navigation bar. Why is this and how can I resolve it?
Are you setting self.title in RootViewController? Perhaps the UINavigationController doesn't have any text to put on the back button, so it omits it...?
Are you setting hidesBackButton = YES or backBarButtonItem = nil in DealViewController, or does it have a different leftBarButtonItem defined?
Try this:
DetailViewController *detailViewController = [[DetailViewController alloc] init];
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithTitle : #"Back"
style : UIBarButtonItemStyleDone
target : nil
action : nil];
self.navigationItem.backBarButtonItem = back;
[self.navigationController pushViewController : detailViewController animated : YES];
[detailViewController release];
You must think of the navigation controller as a stack of navigation controllers each controlling one screen full of information.
You instantiate the navigation controller with the
-(id)initWithRootViewController:(UIViewController *)rootViewController
method. You specify the root view controller in this call. Then you add the navigation controller's view as a subview to the window, like you did before.
If you want to show your second screen you push another view controller on the stack by using
-(void)pushViewController:detailViewController animated:YES
method.
Using presentModalViewController to show the naviagtionController. Set the navagitionController bar button like so:
[navigationController.navigationBar.topItem setLeftBarButtonItem:
[[[UIBarButtonItem alloc] initWithTitle: #"Back"
style: UIBarButtonItemStylePlain
target: self
action: #selector(dismisstheModal:)] autorelease]];
This happened to me because in my navigation controller's content controller I had set up some navigation controller behavior in viewDidLoad and in another class that inherits from my content controller, and the one that was being presented, i implemented a viewDidLoad as well, and forgot to call [super viewDidLoad] which thereby caused me to override the base class's viewDidLoad where I was setting up my navigation controller buttons. Oooops.