I'm a new guy in the XCode realm and was wondering if someone could help me out.
Essentially, I'm playing around with WWDC2010's TileMap project example and am trying to figure out a way to hide their NOAA chart using a segmented controller.
I can activate the overlay and it displays fine, but I can't for the life of me remove it using a segmented controller.
Here's some code from the header file:
#interface ViewController : UIViewController <MKMapViewDelegate> {
IBOutlet MKMapView *map;
IBOutlet UISegmentedControl *controller;
}
- (IBAction)switchMap:(id)sender;
#end
and here's the code for the .m:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"initial view loaded");
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay {
TileOverlayView *view = [[TileOverlayView alloc] initWithOverlay:overlay];
view.tileAlpha = 1;
return view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)switchMap:(id)overlay {
if (controller.selectedSegmentIndex == 0) {
NSLog(#"welp... it loaded...");
[map removeOverlay:overlay];
}
if (controller.selectedSegmentIndex == 1) {
NSLog(#"Map Overlay works");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map addOverlay:overlay];
MKMapRect visibleRect = [map mapRectThatFits:overlay.boundingMapRect];
visibleRect.size.width /= 2;
visibleRect.size.height /= 2;
visibleRect.origin.x += visibleRect.size.width / 2;
visibleRect.origin.y += visibleRect.size.height / 2;
map.visibleMapRect = visibleRect;
}
if (controller.selectedSegmentIndex == 2) {
NSLog(#"But... overlay isnt hiding waa");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map removeOverlay:overlay]; }
}
In a control action method, the first parameter (no matter what you name it) is always the object that called the method.
Here, the control is a UISegmentedControl so the parameter that gets passed to switchMap: is a reference to that control. In the .h you've declared the parameter with the name sender but in the .m it's named overlay.
Regardless of the name, it's still the segmented control object so passing it to removeOverlay is meaningless and will do nothing.
So in this code:
if (controller.selectedSegmentIndex == 0) {
NSLog(#"welp... it loaded...");
[map removeOverlay:overlay];
}
overlay is pointing to the segmented control and so the removeOverlay does nothing.
In this code:
if (controller.selectedSegmentIndex == 2) {
NSLog(#"But... overlay isnt hiding waa");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map removeOverlay:overlay]; }
You are creating a new local overlay object (the compiler is also probably giving you a warning about a local variable hiding the parameter). This new object is separate from the overlay that was already added to the map. Calling removeOverlay on this new object does nothing because this new instance was never added to the map in the first place.
To remove an existing overlay, you either have to keep an ivar reference to it when you add it and pass that ivar to remove or find it in the map view's overlays array.
However, if you will ever have only one overlay, you can pass the first object in the map view's overlays array or just call removeOverlays (plural) and pass the whole array:
if (map.overlays.count > 0)
[map removeOverlay:[map.overlays objectAtIndex:0]];
//OR...
if (map.overlays.count > 0)
[map removeOverlays:map.overlays];
Related
I've implemented -mapView: markerInfoWindow: on my application and until now everything was working perfectly, but yesterday I've updated the Google Maps iOS SDK to the latest version (1.12.23211.0) and since then the info window doesn't being shown and the default info window (white bar with a title) is displayed instead.
I tried to reinstall the last version of the SDK (using Cocoa pods) but it's still not working (my custom info window isn't being shown).
Can anyone help me to solve this problem?
My code:
myVC:
-(UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker
{
//Creating "infoWindow"(infoWindow) and setting it with nib file called "infoWindow"
infoWindow *infoWindow=[[[NSBundle mainBundle] loadNibNamed:#"infoWindow" owner:self options:nil] firstObject];
//Setting "infoWindow"(infoWindow)'s storeNameLabel's text to "marker"(customMarker)'s title
infoWindow.storeNameLabel.text=((customMarker*)marker).title;
//Creating "fullAddress"(NSString) ands setting it to "marker"(customMarker)'s address
NSString *fullAddress = ((customMarker*)marker).address;
//Creating "addressArray"(NSArray) and adding it "address" without the city name (until the "," char)
NSArray *addressArray = [fullAddress componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#","]];
//Setting "infoWindow"(infoWindow)'s storeAddressLabel's text to "addressArray"(NSArray) first object
infoWindow.storeAddressLabel.text=[addressArray firstObject];
//Returning "infoWindow"(infoWindow)
return infoWindow;
}
-(void)createMarkerWithStore:(Store*)store
{
dispatch_async(dispatch_get_main_queue(), ^{
customMarker *marker=[[customMarker alloc] initWithPosition:store.geoPoint andTitle:store.storeName andAddress:store.address andIcon:store.category.markerIcon];
marker.map=self.mapView;
store.marker=marker;
});
}
customMarker:
-(void)awakeFromNib
{
self.storeNameLabel.adjustsFontSizeToFitWidth=YES;
self.storeAddressLabel.adjustsFontSizeToFitWidth=YES;
}
customMarker:
-(instancetype)initWithPosition:(CLLocationCoordinate2D)position andTitle:(NSString*)title andAddress:(NSString*)address andIcon:(UIImage*)icon
{
self=[super init];
if (self) {
self.position=position;
self.title=title;
self.address=address;
self.icon=icon;
self.flat=YES;
self.appearAnimation=kGMSMarkerAnimationPop;
}
return self;
}
Thank you very much!
The problem was that I forgot to set the delegate to the map
So I want to highlight a specific UIView in iCarousel when I click a button within that view and I want to be able to highlight as many views as I want within the carousel. So I have a button that sets the alpha of a UIImageview within the view the problem that I'm running into is although it knows the index of the view I don't know how to call the corresponding index of the UIImageview within that view. So I setup the UIImageview within a custom nib and I have this code setting up the iCarousel's view it:
UPDATED based on #danh answer
//.h
#property (nonatomic, strong)NSMutableSet *selected;
//.m
- (void)viewDidLoad
{
self.selected = [NSMutableSet set];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
//create new view if no view is available for recycling
if (view == nil)
{
view = [[[NSBundle mainBundle] loadNibNamed:#"ItemView" owner:self options:nil] lastObject];
}
int chkTag = checkImg.tag;
checkImg = (UIImageView *)[checkImg viewWithTag:chkTag];
NSNumber *indexNum = [NSNumber numberWithInt:index];
// here's where the view state gets set
checkImg.alpha = ([self.selected member:indexNum])? 1.0 : 0.0;
return view;
}
Then I have this called when the specific view is being selected:
- (void)carouselCurrentItemIndexUpdated:(iCarousel *)carousel1{
NSInteger indexInt = carousel1.currentItemIndex;
NSNumber *index = [NSNumber numberWithInt:indexInt];
if ([self.selected member:index]) {
[self.selected removeObject:index];
} else {
[self.selected addObject:index];
}
// now just reload that item, and let the viewForItemAtIndex
// take care of the selection state
[carousel reloadItemAtIndex:indexInt animated:NO];
}
Which works but only allows me to select one item at a time and check it. I want to be able to select/unselect multiple items at a time.
It works like a table view. You need is a model for selected items. This needs to outlive any given carousel view. So add a property NSMutableSet *selectedItems and initialize it with self.selected = [NSMutableSet set];
When the button is pressed, you want to add the current index to that set (does that button toggle selection? if so, then you want to add it if it's absent, or remove it if it's present).
NSInteger indexInt = carousel1.currentItemIndex;
NSNumber *index = [NSNumber numberWithInt:indexInt];
if ([self.selected member:index]) {
[self.selected removeObject:iIndex];
} else {
[self.selected addObject:index];
}
// now just reload that item, and let the viewForItemAtIndex
// take care of the selection state
[carousel reloadItemAtIndex:indexInt animated:NO];
The last step is to react to the selection state when you configure a view:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view {
if (view == nil) {
view = [[[NSBundle mainBundle] loadNibNamed:#"ItemView" owner:self options:nil] lastObject];
}
// get the checkImg view however you do that already. if you're confused about this
// give it a tag when you create it, then find it like this:
UIImageView *checkImg = (UIImageView *)[view viewWithTag:SOME_TAG];
NSNumber *indexNum = [NSNumber numberWithInt:index];
// here's where the view state gets set
checking.alpha = ([self.selected member:indexNum])? 1.0 : 0.0;
return view;
}
EDIT - Part of the problem seems to be creating and then later getting subviews of the carousel. UIView provides a tag property that will help, but the posted code makes a couple errors. Here's how to use tags:
If you create the image view in IB, give it a tag using the properties inspector. Here, I gave a view a tag of 32.
Now SOME_TAG in my suggestion above should be 32, so...
UIImageView *checkImg = (UIImageView *)[view viewWithTag:32];
NSLog(#"%#", checkImg);
// if this doesn't log an image view, then something is wrong.
have a view controller which has a UIViewController with UIScrollView.
I add a UItapgesturerecognizer to the scroll view, so that when the image on scrollView is tapped the singleTapGessureCaptured method is called.
Question
What I am trying to do is pass the image from the selected UIscrollView (from the specific cell) into a new View Controller.
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture{
CGPoint touchPoint = [gesture locationInView:scrollView];
NSUInteger touchedPage = floorf(touchPoint.x / scrollView.frame.size.width);
if ([imageArray count] > 1) {
touchedPage = touchedPage % ([imageArray count] - 1);
}
//Push the image to another view
it's kind of tough to know exactly what you're doing. If I'm correct you want to create a new view controller when the user clicks on the image right? Here's one way that you can do this.
1) You can create an instance of the View Controller that you want to pass that image to, and then set the image for that instance to the selected image. Something like this:
#interface NewViewControllerToStoreImage : UIViewController
#property (strong, nonatomic) UIImageView *imageView; //You need to display this image somewhere on the view for the view controller.
- (NewViewControllerToStoreImage *) init; // you need to write the implementation for this method so your view controller can look the way you want it to.
#end
#implementation NewViewControllerToStoreImage
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 200, 200)];
}
#end
Then in the method you've created.
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture{
CGPoint touchPoint = [gesture locationInView:scrollView];
NSUInteger touchedPage = floorf(touchPoint.x / scrollView.frame.size.width);
if ([imageArray count] > 1) {
touchedPage = touchedPage % ([imageArray count] - 1);
}
//self.selectedImage is a placer for the image that is selected. I'm not sure where or how you're storing that selected image.
UIImage *image = self.selectedImage;
NewViewControllerToStoreImage *newViewController = [[NewViewControllerToStoreImage alloc] init];
newViewController.imageView.image = image;
//Then assuming that you're using a UINavigationController. This line will push that new view controller onto the navigation controller's view controllers and display it on screen.
[self.navigationController pushViewController:newViewController animated:YES];
}
Hope this works for you. And hope it makes sense, like I said, it's kind of hard to answer to based off the vague question.
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 am trying to display tiled images with a kml overlay on top of the tiled images (code below) and am receiving the following error:
'NSInvalidArgumentException', reason: '-[MKPolyline tilesInMapRect:zoomScale:]: unrecognized selector sent to instance
Does anyone have any suggestions as to whether or not I am approaching the multiple overlays correctly or why I am getting this error?
Thanks in advance!
(void)viewDidLoad
{
[super viewDidLoad];
// Initialize the map overlay with tiles in the app's bundle.
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
MapOverlay *overlay1 = [[MapOverlay alloc] initWithDirectory:tileDirectory];
// Locate the path to the route.kml file in the application's bundle
// and parse it with the KMLParser.
NSString *path = [[NSBundle mainBundle] pathForResource:#"route" ofType:#"kml"];
NSURL *url = [NSURL fileURLWithPath:path];
kmlParser = [[KMLParser alloc] initWithURL:url];
[kmlParser parseKML];
// Add all of the MKOverlay objects parsed from the KML file to the map.
NSArray *overlay2 = [kmlParser overlays];
[map addOverlay:overlay1];
[map addOverlays:overlay2];
// Set the starting location.
CLLocationCoordinate2D startingLocation;
startingLocation.latitude = 0.00;
startingLocation.longitude =-0.00;
map.region = MKCoordinateRegionMakeWithDistance(startingLocation, 4600, 4600);
[map setCenterCoordinate:startingLocation];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay1
{
MapOverlayView *view = [[MapOverlayView alloc] initWithOverlay:overlay1];
view.overlayAlpha = 1.0;
return view;
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay2:(id <MKOverlay>)overlay2
{
return [kmlParser viewForOverlay:overlay2];
}
#end
At least the viewForOverlay delegate method is not implemented correctly.
You've written two methods: mapView:viewForOverlay: and mapView:viewForOverlay2:.
But the map view will only always call mapView:viewForOverlay: since that is the method name defined by the MKMapViewDelegate protocol.
The mapView:viewForOverlay2: method will be ignored and not called by the map view.
So what happens is that when the overlay2 overlay array is added to the map, it calls the mapView:viewForOverlay: method which creates a MapOverlayView for the overlay (instead of getting the overlay view from kmlParser). This may cause issues (maybe MapOverlayView only handles MapOverlay-type overlays).
All overlays should be handled in the mapView:viewForOverlay: method.
To handle multiple types of overlays, check the overlay class and handle accordingly:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MapOverlay class]])
{
MapOverlayView *view = [[MapOverlayView alloc] initWithOverlay:overlay];
view.overlayAlpha = 1.0;
return view;
}
//if not a MapOverlay, get from kmlParser...
return [kmlParser viewForOverlay:overlay];
}
Remove the mapView:viewForOverlay2: method.