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)
Related
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.
I've a viewController (with a mapView) which is included in a tabBarController (my viewController called MapViewController is a section of TabBarController).
My TabBarController is included in a navigationController.
I created the annotationView that are added (via the delegate method MKMapViewDelegate) to the map. I allow the callout to show title and subtitle. Title and subtitle are taken from the queries made to my database. From these queries I get title, details, ID (in string version) and images.
I have no problems in setting up title and subtitle in the callout of each annotation.
But when I set the images for each callout as LeftCalloutAccessoryView, the system puts me all the same image.
And also when I go to click on the RightCalloutAccessoryView (that is a button which push to another viewController) which should open (so push) as a new window navigationController, give me back the wrong ID (ID of another annotation) and therefore are the details wrong in the new window.
I know that maybe explaining it so it is a bit difficult to understand, but instead here's the code:
+ (CGFloat)annotationPadding;
{
return 10.0f;
}
+ (CGFloat)calloutHeight;
{
return 40.0f;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[_mapView setDelegate:self];
_arrPosters = [[NSMutableArray alloc] init];
_script = [[DBGMScr alloc] init]; //Is a class that provides script database connection
//With fieldsRequest I get the array with the field results sought
_arrID = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:#"SELECT ID FROM Event ORDER BY DateTime DESC"]];
_arrEventNames = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:#"SELECT Name FROM Event ORDER BY DateTime DESC"]];
_arrEventLatitude = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:#"SELECT Latitude FROM Event ORDER BY DateTime DESC"]];
_arrEventLongitude = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:#"SELECT Longitude FROM Event ORDER BY DateTime DESC"]];
_arrEventDescription = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:#"SELECT Description FROM Event ORDER BY DateTime DESC"]];
for (int i = 0; i < [_arrID count]; i++) {
//With getPoster I get the poster for the event (located on the server) by using a script (it works perfectly), and add it to the array of posters
UIImage *poster = [_script getPoster:_arrID[i]];
[_arrPosters insertObject:poster atIndex:i];
}
for (int i = 0; i < [_arrID count]; i++) {
MKPointAnnotation *aAnnotationPoint = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = [_arrEventLatitude[i] doubleValue];
theCoordinate.longitude = [_arrEventLongitude[i] doubleValue];
aAnnotationPoint.coordinate = theCoordinate;
aAnnotationPoint.title = _arrEventNames[i];
aAnnotationPoint.subtitle = _arrEventDescription[i];
// Add the annotationPoint to the map
[_mapView addAnnotation:aAnnotationPoint];
}
}
#pragma mark - MKMapViewDelegate
-(MKAnnotationView*)mapView:(MKMapView*)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
CGRect myRect = CGRectMake(-20, -20, 40, 40);
EventAnnotationView *viewAnno = [[EventAnnotationView alloc] initWithFrame:myRect];
viewAnno.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:
UIButtonTypeDetailDisclosure];
viewAnno.rightCalloutAccessoryView = rightButton;
UIImageView *iconView = [[UIImageView alloc] initWithFrame:myRect];
iconView.image = defaultImage; //defaultImage for now, but I want to show a different image for each annotation
viewAnno.leftCalloutAccessoryView = iconView;
NSString *viewTitle = [viewAnno.annotation title];
return viewAnno;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
EventViewerViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"EventViewer"];
[self.navigationController pushViewController:controller animated:YES];
NSDictionary *selection = #{#"ID" : _arrID[_eventIndex] //but it get an incorrect ID,
#"eventName" : _arrEventNames[_eventIndex]};
[controller setValue:selection forKey:#"selection"];
}
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//_eventIndex is an NSInteger declared in file .h
_eventIndex = [_mapView.annotations indexOfObject:view.annotation];
}
EventAnnotationView is subclass of MKAnnotationView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// make sure the x and y of the CGRect are half it's
// width and height, so the callout shows when user clicks
// in the middle of the image
CGRect viewRect = CGRectMake(-20, -20, 40, 40);
UIImageView* imageView = [[UIImageView alloc] initWithFrame:viewRect];
// keeps the image dimensions correct
// so if you have a rectangle image, it will show up as a rectangle,
// instead of being resized into a square
imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView = imageView;
[self addSubview:imageView];
}
return self;
}
- (void)setImage:(UIImage *)image
{
// when an image is set for the annotation view,
// it actually adds the image to the image view
_imageView.image = image;
}
There are several issues here but to answer your two main questions first:
To set leftCalloutAccessoryView to a different image for each annotation, you need to look at some value in the annotation parameter that is passed to the viewForAnnotation delegate method. At the crudest level, you could set the image based on the value of annotation.title (eg. if title is "A" then set image to "1" else if title is "B" then set image to "2" etc).
Tapping on the rightCalloutAccessoryView gives you the wrong Event Id because of this code in didSelectAnnotationView:
_eventIndex = [_mapView.annotations indexOfObject:view.annotation];
The above code assumes that the annotations array that the map view returns will be in the same exact order that you added the annotations in (and that it will only contain annotations that you added which isn't the case if showsUserLocation is YES). This assumption is false. Do not assume the annotations array is in any particular order -- do not rely on its order.
A better solution is to use the annotation object itself that you can access directly from the calloutAccessoryControlTapped delegate method using view.annotation.
Since you need the Event's Id and the default MKPointAnnotation class obviously has no property to store that anywhere, you should create your own class (eg. EventAnnotation) that implements MKAnnotation and add all the event properties you need to know for each annotation. Then, you'll be able to access event-specific values directly using the annotation object in the map view's delegate methods without trying to reference back to your original arrays.
The EventAnnotation class might be declared like this:
#interface EventAnnotation : NSObject<MKAnnotation>
#property (assign) CLLocationCoordinate2D coordinate;
#property (copy) NSString *title;
#property (copy) NSString *subtitle;
#property (copy) NSString *eventId;
//Not sure if your event id is a string.
//Change to "#property (assign) int eventId;" if it's an integer.
#end
Then to create the annotation, create an EventAnnotation instead of MKPointAnnotation:
EventAnnotation *aAnnotationPoint = [[EventAnnotation alloc] init];
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = [_arrEventLatitude[i] doubleValue];
theCoordinate.longitude = [_arrEventLongitude[i] doubleValue];
aAnnotationPoint.coordinate = theCoordinate;
aAnnotationPoint.title = _arrEventNames[i];
aAnnotationPoint.subtitle = _arrEventDescription[i];
//store event id in the annotation itself...
aAnnotationPoint.eventId = _arrID[i];
[_mapView addAnnotation:aAnnotationPoint];
Finally, in calloutAccessoryControlTapped (you won't need to implement didSelectAnnotationView), you could do this:
EventViewerViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"EventViewer"];
[self.navigationController pushViewController:controller animated:YES];
//cast the view's annotation object to EventAnnotation...
EventAnnotation *eventAnn = (EventAnnotation *)view.annotation;
NSDictionary *selection = #{#"ID" : eventAnn.eventId,
#"eventName" : eventAnn.title };
[controller setValue:selection forKey:#"selection"];
Another thing you should do in viewForAnnotation is explicitly set the view's annotation property immediately after creating it. Although it may be getting assigned automatically somehow, I'd rather do it explicitly to be safe:
EventAnnotationView *viewAnno = [[EventAnnotationView alloc] initWithFrame:myRect];
viewAnno.annotation = annotation; // <-- add this
Two unrelated points:
Instead of creating a separate array for each event property (Ids, Names, Descriptions, Latitudes, etc), I would highly recommend creating an "Event" class with all those properties and then you could create just one array of Event objects. In fact, you could make this Event class implement MKAnnotation itself (instead of creating two classes Event and EventAnnotation).
The code is executing a separate SQL query for each column. All the queries are identical except for the column retrieved. This could be improved greatly by getting all the columns in a single SQL query (eg. SELECT ID, Name, Latitude, Longitude, Description FROM Event ORDER BY DateTime DESC). I am not familiar with this DBGMScr class but you should really look into it. If the table contains 500 annotations and 5 columns, you are currently retrieving a total of 2500 rows when it could be just 500 in one shot.
I create an annotation, which contains several elements textbubble and pin. I turn the bubble on when I show annotation, but later I want to shut the bubble off and leave the annotation.
Here are my two methods. The add subview works, but remove subview does not.
-(void)hideETACountdown {
self.etaView.hidden = YES;
[self.etaView removeFromSuperview];
}
-(void)showETACountdown {
self.etaView = [[UIView alloc] initWithFrame:CGRectMake(-34, -97, 89, 59)];
UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"WaitBubble_backgroundandshadow.png"]];
[self.etaView addSubview:bg];
UILabel *minLabel = [[UILabel alloc] initWithFrame:CGRectMake(7, 24, 42, 21)];
minLabel.text = #"min";
minLabel.textAlignment = UITextAlignmentCenter;
minLabel.font = [UIFont systemFontOfSize:10];
self.etaLabel = [[UILabel alloc] initWithFrame:CGRectMake(13, 4, 30, 27)];
self.etaLabel.font = [UIFont boldSystemFontOfSize:22];
self.etaLabel.textAlignment = UITextAlignmentCenter;
self.etaLabel.text = #"";
[self.etaView addSubview:minLabel];
[self.etaView addSubview:self.etaLabel];
[self addSubview:self.etaView];
self.etaView.hidden = NO;
}
- (id) initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
self.canShowCallout = YES;
self.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
self.innerImage = [[UIImageView alloc] initWithImage:nil];
self.innerImage.frame = CGRectMake(-15, -38, 32, 39);
[self addSubview:self.innerImage];
if(self.showETA) {
[NSNotificationCenter addUniqueObserver:self
selector:#selector(handleEtaTimeUpdate:)
name:kEtaUpdate
object:nil];
[self showETACountdown];
}
}
return self;
}
// UPDATE /////
There seems to be some confusion. This code above is not in the viewController that holds my mkmap, but rather the code inside my custom annotation. Further, I don't want to hide or show the entire annotation based on selecting or deselecting. The self.etaView is custom view which is just part of the annotation. My annotation consists of a custom map pin and an eta bubble. Once the ETA is counted down to 0, I want to remove the bubble (aka self.etaView), but the annotation (map pin) needs to stay on the map the entire time. I just want to hide the ETA bubble.
I am using the proper addAnnotation methods, in the proper way, in my viewController that holds my mkmap. Again, this code above is inside my custom annotation and I want my custom annotation to be responsible for removing its own elements, NOT removing itself from the map.
Come on, why using this weird logics with addSubView and removeFromSuperView. MKMapView is built to support "datasource" for pins. I dunno what kind of view you are trying to acheive but this CGRectMake(-34, -97, 89, 59) looks awful. So please, use method:
-(MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id<MKAnnotation>)annotation
This way you will have no difficulties managing the annotation using method
- (void)deselectAnnotation:(id < MKAnnotation >)annotation animated:(BOOL)animated
For example:
[mapView deselectAnnotation:[mapView.selectedAnnotations objectAtIndex:0] animated:YES];
The method to remove the bubble was getting called, but it just wasn't getting removed? So what I have done is create notification listener on my annotation and post a notification when I want it removed and it removes it. Not sure why it doesn't just work by calling an instance method?
Anyway, notifications solved it. Need to move on so I can launch the app.
Currently learning iOS development. I need to set my instance of MKMapView to display with satellite view. I know I can do this through the attribute settings, but I wish to do it through code using [myMapView setMapType:MKMapTypeSatellite], but my question is, where do I put this so that as soon as the mapView loads on the screen, it is already in satellite mode. My instinct is to send this message once my instance of mapview is instantiated, but where does this occur?
Typically this type of code is put into the view controller's viewDidLoad method, which is called once for each instance of a controller.
- (void)viewDidLoad {
[super viewDidLoad];
myMapView = [[MKMapView alloc] initWithFrame:CGRectMake(0,0,200,200)];
myMapView.mapType = MKMapTypeSatellite;
[self.view addSubview:myMapView];
}
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];
}