How to observe the dealloc process - ios

In my app I'm downloading the location data and showing them on the map. There are some scenarios which I cannot understand why they are happening. Sometimes, my mapView(Google map) class dealloc is getting called and I'm unable to see the markers on my map(I can still see my current location).
This is how my mapView is setUp in the storyBoard:
[ContainerVc] -embedSegue->[MapView]
Container Vc is the root Vc.
and in my rootVc class:
#property (weak, nonatomic) MapVc *mapVc;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString: #"embeddedMapVc"]) {
self.mapVc = [segue destinationViewController];
self.mapVc.delegate = self;
}
}
and in my mapVC class(all the properties are strong,nonatomic):
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self setupMapView];
}
- (void)setupMapView{
MyLog(#"Map view just refreshed/setup");
//set map view
self.infoWindow = [[MyMapInfoWindow alloc] initWithFrame:CGRectMake(0, 0, 210, 47)];
self.infoWindow.delegate = self;
CLLocation *center = [[MyLocationMonitor sharedInstance] getLocation];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:center.coordinate.latitude longitude:center.coordinate.longitude zoom:18];
self.mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
self.mapView.camera=camera;
[self.mapView setMapType:kGMSTypeNormal];
[self.mapView setMinZoom:14 maxZoom:18];
[self.mapView setDelegate:self];
self.mapView.myLocationEnabled=YES;
self.mapView.settings.rotateGestures = NO;
self.mapView.settings.myLocationButton = YES;
[self.mapView setPadding:UIEdgeInsetsMake(0, 0, 62, 0)];
self.view = self.mapView;
[self setupMarkers];
}
- (void)setupMarkers{
MyLog(#"setting up markers");
self.annotations = nil;
[self.mapView clear];
[[MyLocationMonitor sharedInstance] getBuildingsWithCompletion:^(BOOL success, NSArray *buildings) {
self.buildings = buildings;
MyLog(#"_buildings_: %#", self.buildings);
if (success) {
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] init];
self.annotations = [[NSMutableArray alloc] init];
for (NSDictionary *location in buildings) {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([[location objectForKey:#"Latitude"] doubleValue], [[location objectForKey:#"Longitude"] doubleValue]);
bounds = [bounds includingCoordinate:coordinate];
GMSMarker *marker = [GMSMarker markerWithPosition:coordinate];
marker.title = [location objectForKey:#"name"];
marker.map = self.mapView;
marker.icon = [UIImage imageNamed:#"Boost-BuildingMarkerIcon"];
marker.userData = #{#"building":location};
marker.infoWindowAnchor = CGPointMake(1.0f, -0.1f);
marker.opacity = 1.0;
[self.annotations addObject:marker];
}
[self.mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds withPadding:50.0]];
}
}];
}
-(void)dealloc{
MyLog(#"MApVC dealloc called");
}
So, from the rootVc I move to another controller and after I finish a network call I comeback to rootVc, where the prepareForSegue gets triggered and the call goes to mapVc class, and it setups the mapView(In this process mapVc dealloc gets called sometimes in that case, I'm unable to see the markers on the map).
I couldn't get much info from stacktrace. I would like to know why my mapVc is getting deallocated sometimes ? How to make sure my mapVc is not deallocated at any time ?
Edit:
I logged the mapVc self in the dealloc case:
<MapVc: 0x7ff83f2883c0> On didAppear
<MapVc: 0x7ff83f2883c0> on dealloc (why is this happening ?)
<MapVc: 0x7ff84475b6f0> new instance again on viewDidAppear

How to make sure my mapVc is not deallocated at any time ?
If you require a VC to exist at a time when it is not onscreen (or at least in a navigation stack), then you have broken MVC. I don't think that's quite what's happening here, but it's important to keep in mind. Network operations should happen in your Model layer, and your view layer should observe the model. View controllers are responsible for managing views that are currently visible, not network operations. You likely have organized your view controllers incorrectly.
That said, that probably isn't the actual problem in this case. The problem is that nothing is retaining mapVc. It's weak in the root view controller so it's probably being released as soon as the segue is complete (the segue retains it while it is running). If mapVc is embedded in the root view controller, it should almost certainly be strong.

Related

MapView not loading when in subclass of UIView

I created a custom view and I have a map in it, this is how my view is setup.
- (id)initWithFrame:(CGRect)frame withPackage:(AftershipTracking *)package andLocation:(CLLocation *)location
{
if (self = [super initWithFrame:frame]) {
self = [[[NSBundle mainBundle] loadNibNamed:#"SPShareView" owner:nil options:nil] lastObject];
[self setLabelsWithPackage:package];
_dayLabel.text = _dayLabel.text.uppercaseString;
[self setupMapWithPinLocation:location];
}
return self;
}
Inside of the setupMapWithPinLocation I have this.
- (void)setupMapWithPinLocation:(CLLocation *)location
{
_mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0, 0, 760, 560)];
_mapView.mapType = MKMapTypeStandard;
_mapView.delegate = self;
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:location.coordinate];
[_mapView addAnnotation:annotation];
MKCoordinateRegion region = [_mapView regionThatFits:MKCoordinateRegionMakeWithDistance(location.coordinate , 700, 700)];
region.span.latitudeDelta = 3;
region.span.longitudeDelta = 3;
[_mapView setRegion:region animated:NO];
}
However after creating the view, when I look at it in debug I get a blank map that has not been loaded yet, what would be the correct way to go about implementing this? I added the delegate methods for checking if the MapView loaded and they seem to never get called. Also I do not need the map to be interactive, the map can be turned into an image and that would be good.
Based on the comment:
_mapView is already added in the nib
A NIB isn't a magical code-generation tool, it's data. It's loaded once, then its influence is expended. When it was loaded, whatever its mapView described was added to the NIB. I'll bet you have a map view in your nib that _mapView connects to? That's the one that's displaying.
When you do this:
_mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0, 0, 760, 560)];
You:
create a new instance of MKMapView that nobody else knows about;
discard your reference to the map view that is visible on screen.
Whatever you subsequently do to _mapView will have no effect to the one on screen for the same reason that:
int a = 3;
int b = a;
b = 4;
... does not change the value of a. Changing the identity of the object that _mapView points to has no effect on your view hierarchy. Your views are completely unaware that you have done it.
If you have a map view in your NIB that is being created and displaying as you want then there's no reason also to create one programmatically. Delete the line quoted above and leave everything else alone.
(also: it's very odd that you want to call initWithFrame: and then throw away your instance for a NIB-loaded copy, which you decided not to load via -initWithNibName:bundle:; if you're using a NIB then you should just set your basic view properties up in the NIB)

addAnnotation not updating Map?

I created the following method to add pins to my map view. I have tried calling it to get it to add the single pin but it does nothing when I call it from a different view controller?
-(void)addPins:(NSDecimalNumber*) woX ycoord:(NSDecimalNumber*) woY {
CLLocationCoordinate2D coordinate;
coordinate.latitude = 35;
coordinate.longitude = -80;
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:coordinate];
[annotation setTitle:#"Title"];
[self.mapView addAnnotation:annotation];
}
I can call it from viewDidLoad of the DetailViewController and it will display the single pin:
[self addPins:woX ycoord:woY];
But when I call it from the MasterViewController nothing displays, but will log the comments so I know that it was run?
DetailViewController *firstController = [[DetailViewController alloc] init];
[firstController addPins:woX ycoord:woY];
I think this happens because the map object doesn't loaded yet in memory when you calling [firstController addPins:woX ycoord:woY]; can you try to call it after delay like this way [firstController performSelectorWithObjects:#[woX, woY] afterDelay:1.0f]
Good luck
Anna was on the right path. I was creating a new instance of the DetailViewController.
I was able to solve it by adding
#property (strong, nonatomic) DetailViewController *detailViewController;
to the MasterViewController.h. And then just calling
[self.detailViewController addPins:woX ycoord:woY desc:cellView];
in MasterViewController.m

How to implement iOS Google Maps SDK

I just have a question and can't seem to find it anywhere.
I"m new to iOS development and trying to use Google Maps inside my application.
I went thru the example they give you here.
#import "DemoViewController.h"
#import <GoogleMaps/GoogleMaps.h>
#implementation DemoViewController {
GMSMapView *mapView_;
}
- (void)loadView {
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:1.285
longitude:103.848
zoom:12];
mapView_ = [GMSMapView mapWithFrame:CGRectZero camera:camera];
self.view = mapView_;
}
#end
But as you can see the they set self.view = mapView_; and the UIView class doesn't have a view function.
I want the map to be inside a UIView I have that is inside another ViewController
Did I lose you yet? Either ways here is a picture.
So inside of the view (or whitespace) I want the map to load.
Thanks guys for the help.
So you've got a GMSMapView. And you can make one view a subview of another in the interface with addSubview:. I wouldn't do it in loadView if I were you, though. viewDidLoad is the earliest good opportunity.
I think your real problem is that you're way ahead of yourself. You're trying to do this without know how views work, how view controllers work, etc. I recommend you take a deep breath and learn about iOS programming before you jump in with all four feet. Otherwise you don't know what your code (or Google's code) even means.
I found a solution that works for me if anyone needs to do something similar.
I just used a container view and then made separate UIViewController class (that has the google maps sdk code add a map section) and then hooked it up to my main ViewController by using [self.view addSubview:googleMapsView]; googleMapsView being my container view that I connected to the main ViewController.
I did an exact same thing here.
Simply added a View of type GMSMapView via interface builder and then set up the properties in controller.
Please try below code.
#property (strong, nonatomic) IBOutlet GMSMapView *mapView;
-(void)addAllPinsOnMapView
{
arrMapPin=[[NSMutableArray alloc] init];
for(int i = 0; i < arrOfferList.count; i++)
{
GMSCameraPosition *cameraPosition=[GMSCameraPosition cameraWithLatitude:[[[arrOfferList objectAtIndex:i] objectForKey:#"latitude"] floatValue] longitude:[[[arrOfferList objectAtIndex:i] objectForKey:#"longitude"] floatValue] zoom:12];
// mapView = [GMSMapView mapWithFrame:CGRectZero camera:cameraPosition];
_mapView.camera = cameraPosition;
_mapView.myLocationEnabled=YES;
GMSMarker *marker=[[GMSMarker alloc]init];
marker.position=CLLocationCoordinate2DMake([[[arrOfferList objectAtIndex:i] objectForKey:#"latitude"] floatValue], [[[arrOfferList objectAtIndex:i] objectForKey:#"longitude"] floatValue]);
marker.title = [[arrOfferList objectAtIndex:i] objectForKey:#"business"];
marker.map=_mapView;
_mapView.delegate = self;
}
}
- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:
(GMSMarker *)marker {
NSPredicate *result;
result=[NSPredicate predicateWithFormat:#"business CONTAINS[c] %#",marker.title];
NSArray * array = [arrOfferList filteredArrayUsingPredicate:result];
OfferDetailsViewController *objOfferDetailsViewController = [[OfferDetailsViewController alloc]init];
objOfferDetailsViewController.dictOfferDetails=[array objectAtIndex:0];
[self.navigationController pushViewController:objOfferDetailsViewController animated:YES];
}
- (IBAction)btnLocateMe:(UIButton *)sender
{
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:[[[AppDelegate initAppDelegate].DictLocation objectForKey:#"Latitude"] floatValue] longitude:[[[AppDelegate initAppDelegate].DictLocation objectForKey:#"Longitude"] floatValue] zoom:14];
[_mapView animateToCameraPosition:camera];
}

Mapkit mapview is not showing on first try

I have a tab on the RootViewController.m. The tab has 2 buttons. The first button upon click will go to CorpViewcontroller which has the mapView on it. When I click on the first button on the first try, the map is blank with google label on the bottom. I have to click back then click on the button again then the map show up. Is it possible to always show the map on the first button click?
My rootViewController.m to go to the second screen:
[self.navigationController pushViewController:self.corpController animated:YES];
The second screen called corpViewController has the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Set Remote Location";
self.jsonData = [[NSMutableData alloc] init];
mapView.showsUserLocation = YES;
//Setup the double tap gesture for getting the remote location..
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
tgr.numberOfTapsRequired = 2;
tgr.numberOfTouchesRequired = 1;
[mapView addGestureRecognizer:tgr];
mapView.delegate = self;
NSLog(#"viewDidLoad done");
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"viewWillAppear");
appDelegate = (NBSAppDelegate *)[[UIApplication sharedApplication] delegate];
double curLat = [appDelegate.curLat doubleValue];
MKUserLocation *userLocation = mapView.userLocation;
double miles = 10.0;
double scalingFactor = ABS( (cos(2 * M_PI * curLat / 360.0) ));
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/(scalingFactor * 69.0);
MKCoordinateRegion region2;
region2.span = span;
region2.center = userLocation.coordinate;
[mapView setRegion:region2 animated:YES];
NSLog(#"viewWillAppear done..");
}
Please Advise.
Thank you
Are you initializing the MapView in the viewDidLoad method in your view controller?
If so, try moving it to the viewDidAppear method. That worked for me.
In viewDidLoad you are setting showsUserLocation to YES and in viewWillAppear, you are zooming into the mapView.userLocation coordinate.
The userLocation property isn't usually ready with a valid coordinate immediately after setting showsUserLocation to YES.
The first time you show the view controller, it's still invalid and you are zooming into the coordinate 0,0.
By the time you show the view controller a second time, the user location has been obtained and the coordinate is valid.
Instead of zooming into the user location in viewWillAppear, do it in the delegate method mapView:didUpdateUserLocation: which the map view calls when it gets a user location update.
In addition, you also probably want to move the mapView.showsUserLocation = YES; to viewWillAppear and in viewWillDisappear, set it to NO. This way, the map view will zoom in to the user location every time the view controller is shown instead of just the first time.
An unrelated point is that to zoom in to a specific distance, it's much easier to use the MKCoordinateRegionMakeWithDistance function instead of trying to convert miles to degrees yourself.
Here's an example of the changes suggested in corpViewController:
- (void)viewWillAppear:(BOOL)animated
{
//move this from viewDidLoad to here...
mapView.showsUserLocation = YES;
}
-(void)viewWillDisappear:(BOOL)animated
{
mapView.showsUserLocation = NO;
}
-(void)mapView:(MKMapView *)mv didUpdateUserLocation:(MKUserLocation *)userLocation
//Changed the **internal** parameter name from mapView to mv
//to avoid a compiler warning about it hiding instance var with same name.
//It's better to use the passed parameter variable anyway.
{
NSLog(#"didUpdateUserLocation");
double miles = 10.0;
//Instead of manually calculating span from miles to degrees,
//use MKCoordinateRegionMakeWithDistance function...
//Just need to convert miles to meters.
CLLocationDistance meters = miles * 1609.344;
MKCoordinateRegion region2 = MKCoordinateRegionMakeWithDistance
(userLocation.coordinate, meters, meters);
[mv setRegion:region2 animated:YES];
}

How can I share the variable for mapkit between two viewcontroller?

I want to show the pin annotation on the new view controller when the "map" button is clicked on the upper level view controller.
So I used the "IBAction" method on the method file of the upper level controller as below.
Then the latitude and longitude values appeared normally (in the NSLog) from the property list.
But I can't see the pin annotation on the new view controller.
However, if I put the code marked "code for viewDidLoad" on the new view controller (named "location") then I can see the pin annotation.
But the latitude and longitude value is only 0.00000.
I think the variable isn't shared between two view controller.
Please help me to solve this problem.
- (IBAction) goAddView:(id)sender {
// the code for viewDidLoad
double myLat = [[drink objectForKey:lati_KEY] doubleValue];
double myLong = [[drink objectForKey:long_KEY] doubleValue]; CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = myLat;
theCoordinate.longitude = myLong;
NSLog(#"the latitude = %f",theCoordinate.latitude);
NSLog(#"the longitude = %f",theCoordinate.longitude);
myAnnotation *myAnnotation1=[[myAnnotation alloc] init];
myAnnotation1.coordinate=theCoordinate;
myAnnotation1.title=#"Destination";
myAnnotation1.subtitle=#"in the city";
[self.mapView addAnnotation:myAnnotation1];
// the code end
location *lm= [[location alloc] initWithNibName:#"location" bundle:nil];
[self.navigationController pushViewController:lm animated:YES];
I assume the variable you want to share is drink. If you've just declared drink as an ivar in both view controllers, that won't "share" it automatically. After you alloc+init location in goAddView, drink will be nil in lm. The viewDidLoad method in location will get called when you push/present it.
One way to pass the value to location is using properties. First, declare drink as a property in the location view controller:
//in location.h:
#property (retain) NSDictionary *drink;
//in location.m:
#synthesize drink;
//and release in dealloc if not using ARC
Next, set the property after you alloc+init it in goAddView and before you call pushViewController:
- (IBAction) goAddView:(id)sender
{
location *lm = [[location alloc] initWithNibName:#"location" bundle:nil];
lm.drink = drink; //point drink in lm to the one in current vc
[self.navigationController pushViewController:lm animated:YES];
//and do [lm release]; if not using ARC
}

Resources