Memory Leak with MapView ios7 - ios

I have 2 viewControllers: The first one is the mainViewController that sends after pressing a button to the second viewController with a Push method, the second viewController has a mapView that shows my position. The problem is that the first time I start the app the memory used by the mainViewController is near 15mb but after switching to the second viewController and pressing the back button in order to go back to mainViewController the memory used it's near 40mb, I'm using storyboards and arc does anyone have a solution for this?

Try this in your viewWillDisappear:(BOOL)animated method in your second view controller, the one with the map:
mapView.showsUserLocation = NO;
mapView.delegate = nil;
[mapView removeFromSuperview];
mapView = nil;

Related

Why is viewDidLoad being called twice

I have a GPS app that uses Google Maps to handle location based events. The app handles all location events within the app and does not switch to Googles own Google Maps app.
The storyboards can be seen in the image below.
In the app I have a main map view (My Map View Controller as in the StoryBoard) that displays the users current location as well as a list of marked locations around the user on the map. This map also contains a button that will take the user to a list of their marked points (List of Points Table View Controller). Selecting any of the list points takes them to a detailed description of the point (Log a Point). And finally clicking "View on Map" button on this view takes them to the last view (Submit Point Map View Controller) where they can see this point zoomed in on another views map.
Both the map views (My Map View Controller AND Submit Point Map View Controller) use similar code as listed below. However, when I run run the code and I get to "Submit Point Map View Controller", this views viewDidLoad method is executed twice as I have noticed while stepping through the code. This causes 2 views to load, one right after the other. I can also see this happening in the emulator. On the emulator the first view that loads has a back button titled "Log a Point" as would be expected as this was the previous view in the stack. The next view that loads simply has "Back" for the back button - as can be seen on the images below.
This is not an issue on the emulator and I can navigate back to Log a Point view. But on my phone the app crashes when I try and navigate back to Log a Point view and gives error "nested push animation can result in corrupted navigation bar. Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted."
I have checked and I am not segue-ing to this view twice or doing anything that I am not doing on the first map view. Does anyone know why this views viewDidLoad method could be called twice? I have read on SO that this error is thrown from List views but I am not coming from a list view in this case - even though there is a list view earlier in the process.
Below is my Submit Point Map View Controller .h and .m files (some code omitted for brevity)
SubmitPointMapViewController.h
#import <UIKit/UIKit.h>
#import <GoogleMaps/GoogleMaps.h>
#interface SubmitPointMapViewController : UIViewController <CLLocationManagerDelegate>
{
}
#property (nonatomic) CLLocationCoordinate2D *location;
#property double latitudes;
#property double longitudes;
#end
SubmitPointMapViewController.m
#import "SubmitPointMapViewController.h"
#import <GoogleMaps/GoogleMaps.h>
#import <Parse/Parse.h>
#interface SubmitPointMapViewController () <GMSMapViewDelegate>
#end
#implementation SubmitPointMapViewController
{
GMSMapView *mapView;
CLLocationManager *locationManager;
}
#synthesize latitudes;
#synthesize longitudes;
- (void)viewDidLoad
{
// This entire method called twice - one right after the other
mapView.delegate = self;
locationManager = [[CLLocationManager alloc] init];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude: latitudes longitude: longitudes zoom:17];
mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
mapView.myLocationEnabled = YES;
[mapView setMapType:kGMSTypeNormal];
self.view = mapView;
// Set the MyLocationButton and add the button to the MapView
mapView.settings.myLocationButton = YES;
// Setup Markers on the Map
[self setupMarkersOnMap];
}
#end
EDIT: Below is my Connections inspector on the Log a Point view, as well as the segue code when the View on map button is pushed on the same view
- (IBAction)viewOnMapButtonPreseed:(id)sender
{
[self performSegueWithIdentifier:#"SubmitPointmapViewSegue" sender:sender];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"SubmitPointmapViewSegue"])
{
SubmitPointMapViewController *vc = [segue destinationViewController];
vc.latitudes = pointObject.latitude;
vc.longitudes = pointObject.longitude;
}
}
As suggested by #Simon Goldeen and #pbasdf above - the issue was that I was pushing 2 map view controllers onto the stack. There was and old segue that I was using previously for debugging that was causing the issue. I deleted all segues to this map view and instead segued to the map view as follows:
SubmitPointMapViewController *vc = [[SubmitPointMapViewController alloc] init];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
vc = (SubmitPointMapViewController *)[storyboard instantiateViewControllerWithIdentifier:#"SubmitPointMapViewControllerStoryboardID"];
[[self navigationController] pushViewController:vc animated:YES];
Thought I would post my answer here in case anyone else had the same issue.
All credit to #Simon Goldeen and #pbasdf for helping me troubleshoot this issue.

Multiple ViewControllers with MKMapView

In my app I've got a UINavigationController.
In some of the pages I got a MKMapView as the back view (allows changes to map view the overlay UIView UI, so I can't make it as a image).
In some combinations, It's possible to have 3 or so MKMapView alloc'd.
The problem is that each MKMapView takes nearly 60MB, so the memory jumps up to 180+ MB only from the maps. not to mention if the user opens more ViewControllers with map, he'll get a OutOfMemoryException.
All the maps have the same content, I tried moving a map from ViewController to another, but according to another post in StackOverflow, moving UIView from ViewController to another is against MVC, not to mention it removes it from the caller ViewController.
Basically since all the maps contains the same content, and I want them the contain all the annotations it'd be best to just move the map but I had hard time with that.
I've got a solution out of the box which is to remove the MKMapView from its superView and on viewWillAppear realloc it, but this doesn't seem like the best idea.
I don't see any problem with passing a pointer to a single map view when you move from controller to controller. Unless you're viewing more than one map at a time, I don't see why there should be more that one instance. Remove the view in viewWillDisappear, and pass a pointer to the map in prepareForSegue:
- (void)viewDidLoad {
[super viewDidLoad];
if (! self.mapView) {
self.mapView = [[MKMapView alloc] initWithFrame:self.view.frame];
}
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.view addSubview:self.mapView];
[self.view sendSubviewToBack:self.mapView];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.mapView removeFromSuperview];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SecondViewController *secVC = segue.destinationViewController;
secVC.mapView = self.mapView;
}
Subsequent controllers could have the same code in viewWillAppear, viewWillDisappear, and prepareForSegue (except for what controller is the destinationViewController). The map only needs to be instantiated once in the viewDidLoad method of the first controller. An even simpler implementation would be to have all the subsequent view controllers inherit from the first controller. If you do it that way, you won't need to put anything in viewWillAppear or viewWillDisappear. You only need to override prepareForSegue.

iOS presentViewController is not working right

As part of my updating my apps to replace the deprecated presentModalViewController with presentViewController, I did some testing.
What I found was disturbing. Whereas presentModalViewController always works and there is no question about it working, I have found the presentViewController method often will not display my VC at all. There is no animation and it never shows up.
My loadView are called without problems, but the actual view does not appear.
So here is what I am doing:
User taps a button in my main view controller.
In the callback for that tap, I create a new view controller and display it as shown above.
The VC never appears (it is an intermittent problem though) but because this VC begins playing some audio, I know that its loadView was called, which looks like as follows.
My button-pressed callback is as follows:
- (void) buttonTapped: (id) sender {
VC *vc = [[VC alloc] init];
[self presentViewController: vc animated:YES completion: nil];
[vc release];
}
Here is my loadview in the VC class:
- (void) loadView {
UIView *v = [UIView new];
self.view = v;
[v release];
... create and addsubview various buttons etc here ...
}
Thanks.
Make sure the controller that calls the function has its view currently displayed (or is a parent to the one currently displayed) and it should work.

iOS: Pushing a view controller with animation only works once

I am creating an iPhone client for one of my apps that has an API. I am using the GTMOAuth2 library for authentication. The library takes care of opening a web view for me with the correct url. However I have to push the view controller myself. Let me show you some code to make things more clear:
- (void)signInWithCatapult
{
[self signOut];
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithAuthentication:[_account catapultAuthenticaiton]
authorizationURL:[NSURL URLWithString:kCatapultAuthURL]
keychainItemName:kCatapultKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[self.navigationController pushViewController:viewController animated:YES];
}
I have a "plus"/"add" button that I add to the view dynamically and that points to that method:
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(signInWithCatapult)];
When I press the "add" button, what is supposed to happen is to open the web view with an animation, and then add an account to the accounts instance variable which populates the table view. This works fine if I add one account, but as soon as I try to add a second account, the screen goes black and two errors appear in the console:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The only way that I found to avoid this problem was to disable animations when pushing the view controller.
What am I doing wrong please?
Typical situations
You push or pop controllers inside viewWillAppear: or similar methods.
You override viewWillAppear: (or similar methods) but you are not calling [super viewWillAppear:].
You are starting two animations at the same time, e.g. running an animated pop and then immediately running an animated push. The animations then collide. In this case, using [UINavigationController setViewControllers:animated:] must be used.
Have you tried the following for dismissing once you're in?
[self dismissViewControllerAnimated:YES completion:nil];
I got the nested pop animation can result in corrupted navigation bar message when I was trying to pop a view controller before it had appeared. Override viewDidAppear to set a flag in your UIViewController subclass indicating that the view has appeared (remember to call [super viewDidAppear] as well). Test that flag before you pop the controller. If the view hasn't appeared yet, you may want to set another flag indicating that you need to immediately pop the view controller, from within viewDidAppear, as soon as it has appeared. Like so:
#interface MyViewController : UIViewController {
bool didAppear, needToPop;
}
...and in the #implementation...
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
didAppear = YES;
if (needToPop)
[self.navigationController popViewControllerAnimated:YES];
}
- (void)myCrucialBackgroundTask {
// this task was presumably initiated when view was created or loaded....
...
if (myTaskFailed) { // o noes!
if (didAppear)
[self.navigationController popViewControllerAnimated:YES];
else
needToPop = YES;
}
}
The duplicated popViewControllerAnimated call is a bit ugly, but the only way I could get this to work in my currently-tired state.

Popovers cannot be presented from a view which does not have a window

What does this error indicate:
"Popovers cannot be presented from a view which does not have a window."
the thing that saved my life:
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(44, yCoord, 111, 111) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
by adding if condition it doesn´t crash anymore. I don´t really get it because the presentPopoverFromRect function is ALWAYS called. There is no situation where window would be nil but anyway it did the trick.
edit: I have this code in viewDidAppear.
Nevertheless in most cases it's enough to move presentPopoverFromRect to viewDidAppear or didMoveToWindow but in my case for some reason the if condition was necessary.
the view you're adding the popover to has to already have been added to a window with the "addSubview:" method.
Try waiting until
- (void) didMoveToWindow
is called for the view and then load the popover
I got this problem.
I had a UITabBarController as the detail view, and I set the barButtonItem as the leftBarButtonItem on all three navigation controllers in the tab bar.
vcChart.navigationItem.leftBarButtonItem = barButtonItem;
vcAnalysis.navigationItem.leftBarButtonItem = barButtonItem;
vcTechnicals.navigationItem.leftBarButtonItem = barButtonItem;
Turns out only the last one added is valid, and the previous two would throw the exception when tapped on.
To fix it, I only set the leftBarButtonItem for the visible view controller, and just switched the barButtonItem to the visible view controller every time the user switched tabs.
Just encountered this issue. Turned out that the inView: parameter was using an IBOutlet that wasn't connected in IB. Thus, an attempt was made to launch the popover in nil. That doesn't work.
So, make sure you are using a valid view.
There are many ways to get to this error. Basically you need to wait to call the presentPopover command until your calling view is added to a window. I did it this way.
- (void)viewDidAppear:(BOOL)animated
{
[self methodThatDisplaysPopOver];
}
My presentPopoverFromRect call is inside my methodThatDisplaysPopOver function.
You could protect every presentPopover call like MobiMaciek suggests with this.
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(10, 10, 100, 100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
However, I think it would be better to understand when self.view.window gets assigned and make sure that you present you popover after the view has a window.
I received the same error message when assigning the same UIBarButtonItem to multiple navigation items as did Lewis. My example was slightly more complicated as I was using a UISplitViewController.
In my RootViewController I have an array of arrays to accomplish multiple sections within my table. Each time that the user clicks a row in the table, a new "detail" view controller is placed in the right pane of my splitViewController. Prior to setting the leftBarButtonItem = nil, I would receive a segfault after 3-4 clicks of the "Menu" button with the same error as a111. I updated my code to actually retrieve the previous detail view controller and set the leftBarButtonItem item to nil.
allData is my NSMutableArray that contains several other NSMutableArrays as objects.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Retrieve the new detail view controller
UIViewController *detailViewController = [[self.allData objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
// Add the detail view controller to a navigation controller and set the bar style
UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
detailNavigationController.navigationBar.barStyle = [[NSUserDefaults standardUserDefaults] integerForKey:#"UIBarStyle"];
// Retrieve previous detail view controller and remove the leftBarButtonItem
UINavigationController *previousDetailNavigationController = [splitViewController.viewControllers objectAtIndex:1];
UIViewController *previousDetailViewController = [[previousDetailNavigationController viewControllers] lastObject];
previousDetailViewController.navigationItem.leftBarButtonItem = nil;
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailNavigationController, nil];
splitViewController.viewControllers = viewControllers;
[detailNavigationController release];
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// This sets the left bar to nil when in landscape and equal to "Menu" when in portrait.
// We need to remove rootPopoverButtonItem from the previous viewController...
detailViewController.navigationItem.leftBarButtonItem = rootPopoverButtonItem;
}
The error message was slightly deceiving at first but the answers above helped me out. I wonder why I could click the "Menu" button up to 3-4 different times before the segfault... I'll investigate further.
This error also occurred when the inView: Parameter is incorrect - to test try self.view
yes, you are right but still we can add subview from parent class in it. so it can be represented from a view which have a window:
[popoverController.contentViewController.view addSubview:mySubView];
I had the same error message as the OP, in a very similar situation to that reported by TPoschel, except I had a split view controller with an embedded tab bar controller in the detail pane, and a navigation controller within this. The bar button item is added as the navigation bar leftBarButtonItem.
Only on iOS5.0 (not 5.1) does it seem to require you invalidate the bar button item on the tab bar you are leaving by setting it to nil. Before then adding the bar button to the navigation bar on the tab you are going to.
If I don't do that, from debugging my own code, the window property of the bar button item stays set to nil, and causes the exception, on returning to a screen you'd previously been to. I'm guessing as a side effect of setting the leftBarButtonItem in the navigation item, it goes off and sets the frame. But it doesn't seem to bother unless the button is different from what is currently set there. Hence, the need to set it to nil when leaving a tab, even though it is technically the same button that's being passed around.
I would upvote TPoschel's answer, except SO won't let me.
I had a problem like this. Received this message when clicking a customized UIBarButton item that invoked a selector method with did performSeque.
The problem was my segue was still attached to the UIBarButton item. It should have been attached to the main view of of the view controller. Changed this and worked fine.
P.S., all this got started because I wanted to add and "info" button to my UIToolBar. This isn't one in the system provided list and should be.
There will be a view from which you asks to display your popover.The reason for this error is because you didn't made this view as a subview of the window.
[self.view addSubview:displayPopOverVC];
where displayPopOverVC is the view controller from which the popOver appears
i had the same problem, after adding PresentPopOver in viewDidAppear this was solved
- (void) viewDidAppear:(BOOL)animated{
CGRect popoverRect = screenBounds;
popoverRect.size.width = MIN(popoverRect.size.width,0) ;
popoverRect.origin.x = screenBounds.origin.x;
[popoverController
presentPopoverFromRect:popoverRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
this was happening as inView:self.view should be called after viewDidLoad as suggested by #hey68You and MobiMaciek..
I replaced
[actionSheet showFromBarButtonItem:self.navigationController.navigationItem.leftBarButtonItem animated:YES];
with
[actionSheet showInView:self.view];

Resources