So I've tried this-
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
[self.mapAnnotationViewController.view removeFromSuperview];
MyLocation* location = (MyLocation*)view.annotation;
currentResultDictionary = [location cardJson];
[self.mapAnnotationViewController setAnnotationTitle: [location title]];
[self.mapAnnotationViewController setRating:3.0];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* forwardButtonImage = [UIImage imageNamed:#"forward-50x50.png"];
button.frame = CGRectMake(141,5,25,25);
[button setBackgroundImage:forwardButtonImage forState:UIControlStateNormal];
[button addTarget:self action:#selector(displayCard:) forControlEvents:UIControlEventTouchUpInside];
//Since we are re-using the callout view,
//may need to do additional "cleanup" so that the callout
//shows the new annotation's data.
[view addSubview:self.mapAnnotationViewController.view];
[view addSubview: button];
}
-(IBAction)displayCard:(id)sender{
NSLog(#"DISPLAY CARD CALLED");
}
This adds the button successfully, but when I click on it, the displayCard method is not being called. Why is that happening?
So this is probably why it's not working -
How To add custom View in map's Annotation's Callout's
However, that solution subclasses the view - I only have access to the controller. Can I reduce my problem to be solved by that solution?
Based on your previous question, the problem here is that the button's frame is outside the frame of the pin annotation view. Therefore, touches on the button do nothing.
Assuming you are still using the default MKPinAnnotationView, the default view size is about 32 x 32. Since the button is being added as a subview to it at an x coordinate of 141, it is outside the parent view's frame and touches don't work.
One solution (though it leads to other issues) is to modify the MKPinAnnotationView's frame so that the button will included. So in viewForAnnotation, after the view is created, you could put:
pav = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"];
pav.canShowCallout = NO;
//increase the frame size to include the button...
pav.frame = CGRectMake(0, 0, 200, 40);
//adjust contentMode otherwise default pin image will be distorted...
pav.contentMode = UIViewContentModeTopLeft;
I don't recommend the above. This may work but may lead to other issues.
A better solution probably is to add your custom callout view not to the annotation view but to your main view controller's view. This way, you don't need to mess with the annotation view. You'll need to convert the selected pin's coordinate to the corresponding CGPoint in the main view and set the custom callout view's origin to that point. In didSelectAnnotationView:
[self.mapAnnotationViewController.view removeFromSuperview];
CGRect mavcFrame = mapAnnotationViewController.view.frame;
CGPoint p = [mapView convertCoordinate:view.annotation.coordinate toPointToView:self.view];
//You may need/want to adjust p after the conversion depending on where
//you want the callout view to appear relative to the annotation.
mavcFrame.origin = p;
mapAnnotationViewController.view.frame = mavcFrame;
//add subview to self.view instead of view...
[self.view addSubview:self.mapAnnotationViewController.view];
Also, I don't recommend adding the button in the didSelectAnnotationView as you will end up with duplicate buttons that don't get removed every time an annotation is selected.
Instead, create and add the button once to the mapAnnotationViewController.view right after the mapAnnotationViewController is created.
Related
I want to add working UISliders as a subview to the map view of the DJI-iOS-UXSDK-demo (https://github.com/DJI-Mobile-SDK-Tutorials/iOS-UXSDKDemo). By "working" I just mean they should be freely draggable, right now they can only be moved in tiny steps to the right.
I tried adding the sliders using a .xib-View and just adding them programmatically. Both work fine for a generic MKMapView but not for the map view implemented in DJI-iOS-UXSDK-demo. I tried adjusting the frame sizes of the map view, the sliders and the .xib-view, but none of these changed anything for me.
To reproduce, please clone https://github.com/DJI-Mobile-SDK-Tutorials/iOS-UXSDKDemo and install the pods. Then add the following code to viewDidLoad in DefaultLayoutViewController.m after [super viewDidLoad]:
//Get the map view instance and switch it into the main view
MKMapView *mapView;
if ([self.previewViewController respondsToSelector: #selector(mapWidget)]) {
mapView = [[self.previewViewController valueForKey: #"mapWidget"] mapView];
[self performSelector:#selector(previewViewTapped:) withObject:nil];
};
//Add the slider
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(25, 50, 200, 10)];
[mapView addSubview:slider];
You can ignore the registration error, as fixing that didn't change anything for me. The slider appears on the map view as expected, however it can only be dragged in small steps to the right. Adding a listener with addTarget:action:forControlEvents: also worked for me, however it is useless as long as the slider can't be dragged freely.
I followed your reproduction steps, and was able to reproduce and also fix the issue.
I simply added the slider as a subview of the DefaultLayoutViewController.m's view instead of a subview of the MKMapView and it starting to work smoothly.
//Get the map view instance and switch it into the main view
MKMapView *mapView;
if ([self.previewViewController respondsToSelector: #selector(mapWidget)]) {
mapView = [[self.previewViewController valueForKey: #"mapWidget"] mapView];
[self performSelector:#selector(previewViewTapped:) withObject:nil];
};
//Add the slider
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(25, 50, 200, 10)];
[self.view addSubview:slider]; // <------ Add as a subview of the view controller and not the mapView
I worked around it by adding my own UIPanGestureRecognizer to the slider with the target
- (void)sliderMoved:(UIPanGestureRecognizer *)recognizer {
if([recognizer.view isKindOfClass:[UISlider class]]) {
UISlider *slider = recognizer.view;
slider.value = (slider.maximumValue - slider.minimumValue) * [recognizer locationInView:recognizer.view].x / recognizer.view.frame.size.width + slider.minimumValue;
//call the listener or just place your code here
[self sliderChanged:slider];
}
}
Doesn't solve the underlying problem, but this is what works best for me. I would still be interested in finding a fitting delegate for when the views change places if someone reading this happpens to know it.
This is absolutely confusing. On a 3.5" iPhone simulator, all of the UIButtons on my app work just fine. However, when I launch on the 4" iPhone simulator, all of the UIButtons on the left side of the app do not receive any click events.
Below are screenshots of the 3.5" size and the 4" size. On the 4" size, I've added a line. Left of that line, none of the buttons receive click events. To the right of that line, all buttons behave normally. The left side of buttons 2, 5, and 8 do not respond to clicks, but the right sides of those buttons do respond.
UPDATE----
Thanks to #iccir, I've discovered more info. Apparently, my UIWindow is only 320x480 instead of 568x320 as it should be. I'm not touching the UIWindow in my code except to make it key and visible. In my MainWindow.xib I connect its IBOutlet to my rootViewController.
<UIWindow: 0xc097d50; frame = (0 0; 320 480); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0xc098460>; layer = <UIWindowLayer: 0xc097e70>>
I'm flabberghasted. Any idea why the UIWindow is incorrectly sized?
This is a pretty common issue: Your UIButton is outside of the bounds of one of its superviews. If clipsToBounds/masksToBounds is set to NO (the default), your UIButton is still going to show up, but touch events aren't going to be sent to it.
Let's simplify this case. Suppose a view controller with the following code:
- (void) viewDidLoad
{
[super viewDidLoad];
UIColor *fadedRedColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.25];
UIColor *fadedBlueColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:0.25];
CGRect containerFrame = CGRectMake(25, 25, 100, 100);
CGRect buttonFrame = CGRectMake(100, 100, 64, 44);
UIView *container = [[UIView alloc] initWithFrame:containerFrame];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:#"Button" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[button setFrame:buttonFrame];
[button addTarget:self action:#selector(_handleButton:) forControlEvents:UIControlEventTouchUpInside];
[container setBackgroundColor:fadedRedColor];
[button setBackgroundColor:fadedBlueColor];
[container addSubview:button];
[[self view] addSubview:container];
}
- (void) _handleButton:(id)sender
{
NSLog(#"Moooooo!");
}
Which looks like this:
The button is contained in container, but it resides outside of the container's bounds (the container is 100 pixels wide and 100 pixels tall, the button's origin is at 100, 100).
When you touch the screen, UIKit is going to start at the top of the view hierarchy (UIWindow) and call -[UIView hitTest:withEvent:] recursively until it finds the view that should handle the touch. However, in this example, UIKit will never descend into the container (since you touched outside its boundary), and thus the button subview will not be hit.
If we instead change the buttonFrame to be 50, 50, it looks like this:
The part of the button that overlaps with the container will respond to touch event. The part that resides outside of the container will not:
To debug a view that isn't fully touchable, you can try a debugging function like the following:
static void sDebugViewThatIsntTouchable(UIView *view)
{
UIView *superview = [view superview];
while (superview) {
CGRect rectInSuperview = [view convertRect:[view bounds] toView:superview];
CGPoint topLeft = CGPointMake(CGRectGetMinX(rectInSuperview), CGRectGetMinY(rectInSuperview));
CGPoint topRight = CGPointMake(CGRectGetMaxX(rectInSuperview), CGRectGetMinY(rectInSuperview));
CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rectInSuperview), CGRectGetMaxY(rectInSuperview));
CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rectInSuperview), CGRectGetMaxY(rectInSuperview));
if (![superview pointInside:topLeft withEvent:nil]) {
NSLog(#"Top left point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:topRight withEvent:nil]) {
NSLog(#"Top right point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:bottomLeft withEvent:nil]) {
NSLog(#"Bottom left point of view %# not inside superview %#", view, superview);
}
if (![superview pointInside:bottomRight withEvent:nil]) {
NSLog(#"Bottom right point of view %# not inside superview %#", view, superview);
}
superview = [superview superview];
}
};
Edit:
As you mentioned in the comments, the culprit view was the main UIWindow, which was sized to 320x480 rather than 320x568. Turning on "Full Screen at Launch" in the xib fixed this.
Of course, the question is: "Why?" :)
If you pull up your xib file in a text editor, you will notice that a width of 320 and height of 480 are hardcoded to the window. When the xib is decoded at launch time, the window is initially constructed with this 320x480 frame.
UIKit then queries -[UIWindow resizesToFullScreen] (a private method). If this returns YES, the UIWindow does the equivalent of [self setFrame:[[self window] bounds]].
Toggling the "Full Screen at Launch" flag in Interface Builder directly toggles the private UIWindow.resizesToFullScreen flag.
Let me guess, this happens only in the landscape mode, right ?
I had the same issue in my app when I was developing specifically for the iPhone-4S. But when I began testing on the iPhone-5, touches on the bottom did not work. It were the frame. Make sure frames are set to bounds, both in the code, and the XIB file (if there is one). Different frames/bounds in the man and xib files, might also result in such behaviour.
I eventually removed the xib files, and did everything programmatically. One thing I learnt was, set your frames in viewWillAppear or viewDidAppear methods instead of viewDidLoad. Also check the frame/bounds in your RootViewController. One last thing, try not to use constant value for frames, use referential frames with respect to superview.
PS, one way to know if it really is the frames/bounds that are responsible for this behaviour is, setting the masksToBounds to YES, for the views. That way, your views will not be visible outside their rects.
I have problems getting the coordinate convertion from the local view frame to the window right. I have an "Arrow" image that needs to be moved relative to a UIButton on the page when tapped and when the page loads.
The Arrow image (intense name "interfaceApertureSelectionArrow") is initialized in the "didLoadView" method
- (void)viewDidLoad
{
...
//only initial values here, the image i will be moved using the moveImageView:to: function
CGRect apertureSelectorArrowFrame = CGRectMake(300.0f, 125.0f, 30.0f, 30.0f);
[self addImageView:interfaceApertureSelectionLineArrow withFrame:apertureSelectorArrowFrame];
interfaceApertureSelectionLineArrow = [[UIImageView alloc] initWithFrame:apertureSelectorArrowFrame];
[interfaceApertureSelectionLineArrow setImage:[UIImage imageNamed:#"InterfaceSelectionLineArrow#2x"]];
interfaceApertureSelectionLineArrow.opaque = YES;
[self.view addSubview:interfaceApertureSelectionLineArrow];
...
}
For reacting on the button tap I implemented a Button Action, doing the coordinate conversion using the "convertPoint" method. In this case the conversion and arrow placement works works just right (!)
-(IBAction)setAttributeAperture:(id)sender{
UIButton *button = (UIButton *)sender;
CGPoint superPoint = [button.superview convertPoint:button.frame.origin toView:nil];
[self moveImageView:interfaceApertureSelectionLineArrow toCGPoint:CGPointMake(superPoint.x, superPoint.y)];
}
... so far so good. Now here's the problem. When I try to position the arrow next to the "darkButton" coordinates in the viewDidLoad method to show the selection not only on tapping the button but also on loading the view initially the positioning does not work! Here's what I tried.
Added to the viewDidLoad method:
...
CGPoint superPoint = [darkButton.superview convertPoint:darkButton.frame.origin toView:nil];
[self moveImageView:interfaceApertureSelectionLineArrow toCGPoint:CGPointMake(superPoint.x, superPoint.y)];
...
The move function that animates the movement of the arrow's UIImageView (in case this is relevant as well)
-(void)moveImageView:(UIImageView*)thisImageView
toCGPoint:(CGPoint)thisCGPoint{
[UIView beginAnimations:#"UIImage Move" context:NULL];
[UIView setAnimationDuration:1];
CGSize size = thisImageView.frame.size;
thisImageView.frame = CGRectMake(thisCGPoint.x, thisCGPoint.y, size.width, size.height);
[UIView commitAnimations];
}
Checking the CGPoint value before and after the conversion of the darkButton's CGPoint coordinates shows me that is does not change. How come? (as it did work in the "setAttributeAperture" button method)
I using a custom image for my mapkit annotation. But the main problem it seems that I am running into in using a custom image, is that when zoomed out, the annotation is not in the correct point on the map, and ONLY until I zoom in all the way down, will it show the annotation point in the correct place. It seems that when I use a regular pin MKPinAnnotationView, it works normally, as with the pin being in the correct place zoomed in or out, thanks in advance for anyone that can help.
The code I have used is as follows:
- (MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
NSLog(#"welcome into the map view annotation");
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *pprMapNote = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pprMapNote"];
pprMapNote.image = [UIImage imageNamed:[NSString stringWithFormat:#"GPS_note.png"]];
pprMapNote.canShowCallout = YES;
pprMapNote.centerOffset = CGPointMake(-21,-60);
pprMapNote.calloutOffset = CGPointMake(0, 0);
//[pprMapNote addSubview:pprMapNoteImg];
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetail)
forControlEvents:UIControlEventTouchUpInside];
pprMapNote.rightCalloutAccessoryView = rightButton;
//remember to write in conditional for the different icons that should be loaded based on location
UIImageView *pprNoteLocIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"loc_icon_casino.png"]];
pprMapNote.leftCalloutAccessoryView = pprNoteLocIcon;
[pprNoteLocIcon release];
return pprMapNote;
}
You are setting the centerOffset of the annotation view.
Note that this offset is not scaled with the zoom level. The further you zoom out, the further the image will appear from the coordinate.
In the default MKPinAnnotationView, the centerOffset is left at the default of 0,0 and the pin image is designed such that the bottom point of the pin is on the coordinate. So as you zoom further out, the pin image seems to grow relative to the map under it but the bottom of the pin is still pointing to the coordinate.
You need to either adjust the centerOffset based on your image or modify your image so you don't need to set centerOffset. Or just try commenting out the setting of centerOffset--maybe you don't need it.
Some other unrelated items:
You have a memory leak for the pprMapNote alloc+init (add an autorelease)
You should be using dequeueReusableAnnotationViewWithIdentifier to allow for annotation view re-use.
Instead of using addTarget to call your own method for the callout button press, it's much better to use the map view's own delegate method calloutAccessoryControlTapped
See this answer for an example of the above three points.
The Pin is drawn in a separate view, so it will not zoom based on the status of your view.
You have to set the size of your custom Pin image manually. This can easily be done using the centerOffset. For most cases it is enough to set the height of the frame to half the size of the image. The image is fully filled in the frame, so you can easily use this frame size(height).
aView.image = [UIImage imageNamed ... ];
aView.centerOffset = CGPointMake(0,-aView.frame.size.height*0.5);
I am trying to get a popover to appear at a map kit annotation point but cannot find a "rect" in the annotation view properties to use the rect method of calling uipopovercontroller. If given an annotation on map kit how does one find the appropriate "frame"?
To give paul more information, here is my attempt: I have already used:
- (void)mapView:(MKMapView *)mapView2 annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
NSLog(#"annotationView...");
MyGizmoClass *myGizmoClass= [MyGizmoClass sharedManager];
int choice = 0;
for (NSMutableDictionary *locationDictionary in [myGizmoClass searchResultsForResortLocation])
{
if([view.annotation.title isEqualToString:[locationDictionary objectForKey:#"name"]])
{
DetailViewTableStyleController *controller = [[DetailViewTableStyleController alloc] initWithlocationData:[[myGizmoClass searchResultsForResortLocation] objectAtIndex:choice] nibName:#"DetailViewTableStyle" bundle:[NSBundle mainBundle]];
controller.categoryCode = [locationDictionary objectForKey:#"category_code"] ;
//create a popover controller
popoverControllerDetail = [[UIPopoverController alloc] initWithContentViewController:controller];
// set contentsize
[popoverControllerDetail setPopoverContentSize:CGSizeMake(320,480)];
//present the popover view non-modal
[popoverControllerDetail presentPopoverFromRect:view.rightCalloutAccessoryView.frame inView:mapView2 permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[controller release];
break;
}
choice = choice + 1;
}
}
And... I get a popover at the upper left at the edge of the mapview.
Can anyone tell me why? I am trying to get it to appear near the pin/annotationview.
Okay,
Here is what I found as the only work-around so far:
CGPoint annotationPoint = [mapView2 convertCoordinate:view.annotation.coordinate toPointToView:mapView2];
float boxDY=annotationPoint.y;
float boxDX=annotationPoint.x;
CGRect box = CGRectMake(boxDX,boxDY,5,5);
UILabel *displayLabel = [[UILabel alloc] initWithFrame:box];
[popoverControllerDetail presentPopoverFromRect:displayLabel.frame inView:mapView2 permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[displayLabel release];
The bad part about this is that I am faking putting it at the view of the annotation. I simply get the annotation.coordinate of the annotation view (MKMapKit style coordinates) and convert the coordinates to screen coordinates. Then I create a uilabel (to get a frame-capable construct on the screen) placed where the Annotation View would be. I then tell the popovercontroller to appear at that frame location.
Here is the kicker. I do not add the uilabel to the mapView2. I simply release it once popovercontroller has drawn itself.
Hack, but clean.
The problem with the first example in this thread is in the call to presentPopoverFromRect:inView:permittedArrowDirections:animated:. The value for the inView: parameter is not correct. It should be view.rightCalloutAccessoryView.
That is, the rectangle's coordinates are relative to rightCalloutAccessoryView.
With this change, the behavior will be similar to the maps application which seems right.
If you have the MKAnnotationView, and you imply that you do, then you can use its frame property, as it is a UIView subclass.
Didn't read all the replies... but I had a similar problem. I wanted to show a popover box when I tap the accessory button in the callout bubble.
This is how I solved my problem.
First of, I center the map to the annotation I chose, which I think is always a good idea in this case. Then I implement the popover with a fixed size and position it at the correct position (in my opinion).
Here is a part of my code:
-(void)mapView:(MKMapView *)theMapView annotationView:(MKAnnotationView *)pin calloutAccessoryControlTapped:(UIControl *)control
{
UIViewController *detailController = [[detailedPinView alloc] initWithNibName:#"detailedPinView"
bundle:nil
annotationView:pin];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
UIPopoverController* aPopover = [[UIPopoverController alloc] initWithContentViewController:detailController];
[aPopover setDelegate:self];
[aPopover setPopoverContentSize:CGSizeMake(320, 320) animated:YES];
[detailController setPopover:aPopover];
[detailController release];
[mapView deselectAnnotation:pin.annotation animated:YES];
self.popoverController = aPopover;
[mapView setCenterCoordinate:pin.annotation.coordinate animated:YES];
[self.popoverController presentPopoverFromRect:CGRectMake(382,498,0,0) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
else
{
[self presentModalViewController: detailController animated:YES];
}
[detailController release];
}
The issue with presentPopoverFromRect:inView:permittedArrowDirections:animated is that if the content size of the popover is greater than what fits on the screen (because the annotation is close the the edge of the screen, for example), the system will first (at least partially) disregard the rectangle, and put the tip of the arrow somewhere in the middle of the view. I've solved this by doing it this way:
self.popover = [[UIPopoverController alloc] initWithContentViewController:placeDetailViewController];
[self.popover presentPopoverFromRect:CGRectMake(0, 0, 29, 31) inView:view.rightCalloutAccessoryView permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
The rightCalloutAccessoryView is normally a detail disclosure button, which has a size of 29 by 31. So, what I do, is create a 29 by 31 rectangle, which covers the entire accessory view. Then I present the popover from the accessory view. By increasing the 29 to 40, the popover arrow will be outside the callout bubble, but only if there is enough space to present the entire callout on the screen. If not, the arrow will be moved inside the bubble. To maintain a consistent interface, I left the width at 29.