How to display 2 lines of text for subtitle of MKAnnotation and change the image for the button on the right?
Asked Answered
F

8

15

I am looking at Apple's MapCallouts example for map annotations and callouts (==bubbles that appear when you click on a pin). Every annotation there has coordinates, title and subtitle. I would like to display subtitle in 2 lines, i tried with:

- (NSString *)subtitle
{
return @"Founded: June 29, 1776\nSecond line text";
}

but the text "Second line text" stays in one line and makes bubble wider.I get this:

enter image description here

I would also like to change image of the button to one of my own's, the code that sets the button is currently like this:

UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

Any ideas?

EDIT: I tried 7KV7's advice. Button change is successful, but i still cant get subtitles in 2 lines. My code:

MKPinAnnotationView* customPinView = [[[MKPinAnnotationView alloc]
                                        initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier] autorelease];
        customPinView.pinColor = MKPinAnnotationColorPurple;
        customPinView.animatesDrop = YES;


        // Button
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0, 0, 23, 23);
        button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

        //UIEdgeInsets titleInsets = UIEdgeInsetsMake(7.0, -20.0, 7.0, 7.0);
        //button.titleEdgeInsets = titleInsets;

        [button setImage:[UIImage imageNamed:@"ic_phone_default.png"] forState:UIControlStateNormal];
        //[button setImage:[UIImage imageNamed:@"ic_phone_selected.png"] forState:UIControlStateSelected];
        [button setImage:[UIImage imageNamed:@"ic_phone_selected.png"] forState:UIControlStateHighlighted];
        [button addTarget:self action:@selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];

        customPinView.rightCalloutAccessoryView = button;

        //two labels
        UIView *leftCAV = [[UIView alloc] initWithFrame:CGRectMake(0,0,50,50)];
        //[leftCAV addSubview : button];
        UILabel *l1=[[UILabel alloc] init];
        l1.frame=CGRectMake(0, 15, 50, 50);
        l1.text=@"First line of subtitle"; 
        l1.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];

        UILabel *l2=[[UILabel alloc] init];
        l2.frame=CGRectMake(0, 30, 50, 50);
        l2.text=@"Second line of subtitle";
        l2.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];
        [leftCAV addSubview : l1];
        [leftCAV addSubview : l2];
        customPinView.leftCalloutAccessoryView = leftCAV;
        //customPinView.
        customPinView.canShowCallout = YES;

        return customPinView;

I get this:

enter image description here

Flagrant answered 29/4, 2011 at 11:25 Comment(1)
did you found a solution to this problem?Drugi
A
5
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{   
    MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"loc"];

    // Button
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 23, 23);
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

[button setImage:[UIImage imageNamed:yourImageName] forState:UIControlStateNormal];
[advertButton addTarget:self action:@selector(buttonPress:) forControlEvents:UIControlEventTouchUpInside];

annView.rightCalloutAccessoryView = button;


    // Image and two labels
    UIView *leftCAV = [[UIView alloc] initWithFrame:CGRectMake(0,0,23,23)];
    [leftCAV addSubview : yourImageView];
    [leftCAV addSubview : yourFirstLabel];
    [leftCAV addSubview : yourSecondLabel];
    annotationView.leftCalloutAccessoryView = leftCAV;

    annotationView.canShowCallout = YES;

    return pin;
}

UPDATE

The default style for annotations only supports the title and subtitle. Neither title nor subtitle can include line breaks. You cannot do this without subclassing.

To use a custom view review Apple's sample code:

http://developer.apple.com/library/ios/#samplecode/WeatherMap/Introduction/Intro.html

I also think there is a problem in your code

UILabel *l1=[[UILabel alloc] init];
l1.frame=CGRectMake(0, 15, 50, 50);
l1.text=@"First line of subtitle"; 
l1.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];

UILabel *l2=[[UILabel alloc] init];
l2.frame=CGRectMake(0, 30, 50, 50);
l2.text=@"Second line of subtitle";
l2.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];
[leftCAV addSubview : l1];
[leftCAV addSubview : l2];

l1 has a frame (0, 15, 50, 50) and l2 has (0, 30, 50, 50). Wont these two overlap? I mean l1 will start from y=15 and its height is 50. so when l2 starts from 30 it may overlap.. Can you pls check by changing the frames

Ardene answered 29/4, 2011 at 11:30 Comment(0)
E
6

plz use this

 - (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {

        if ([annotation isKindOfClass:[MKUserLocation class]])
            return nil;
        if ([annotation isKindOfClass:[CustomAnnotation class]]) {
            CustomAnnotation *customAnnotation = (CustomAnnotation *) annotation;

            MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomAnnotation"];

            if (annotationView == nil)
                annotationView = customAnnotation.annotationView;
            else
                annotationView.annotation = annotation;

            //Adding multiline subtitle code 

            UILabel *subTitlelbl = [[UILabel alloc]init];
            subTitlelbl.text = @"sri ganganagar this is my home twon.sri ganganagar this is my home twon.sri ganganagar this is my home twon.  ";

            annotationView.detailCalloutAccessoryView = subTitlelbl;

            NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:subTitlelbl attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:150];

             NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:subTitlelbl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
            [subTitlelbl setNumberOfLines:0];
            [subTitlelbl addConstraint:width];
            [subTitlelbl addConstraint:height];




            return annotationView;
        } else
            return nil;
    }

enter image description here

Exophthalmos answered 26/8, 2016 at 6:56 Comment(0)
A
5
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{   
    MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"loc"];

    // Button
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 23, 23);
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

[button setImage:[UIImage imageNamed:yourImageName] forState:UIControlStateNormal];
[advertButton addTarget:self action:@selector(buttonPress:) forControlEvents:UIControlEventTouchUpInside];

annView.rightCalloutAccessoryView = button;


    // Image and two labels
    UIView *leftCAV = [[UIView alloc] initWithFrame:CGRectMake(0,0,23,23)];
    [leftCAV addSubview : yourImageView];
    [leftCAV addSubview : yourFirstLabel];
    [leftCAV addSubview : yourSecondLabel];
    annotationView.leftCalloutAccessoryView = leftCAV;

    annotationView.canShowCallout = YES;

    return pin;
}

UPDATE

The default style for annotations only supports the title and subtitle. Neither title nor subtitle can include line breaks. You cannot do this without subclassing.

To use a custom view review Apple's sample code:

http://developer.apple.com/library/ios/#samplecode/WeatherMap/Introduction/Intro.html

I also think there is a problem in your code

UILabel *l1=[[UILabel alloc] init];
l1.frame=CGRectMake(0, 15, 50, 50);
l1.text=@"First line of subtitle"; 
l1.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];

UILabel *l2=[[UILabel alloc] init];
l2.frame=CGRectMake(0, 30, 50, 50);
l2.text=@"Second line of subtitle";
l2.font=[UIFont fontWithName:@"Arial Rounded MT Bold" size:(10.0)];
[leftCAV addSubview : l1];
[leftCAV addSubview : l2];

l1 has a frame (0, 15, 50, 50) and l2 has (0, 30, 50, 50). Wont these two overlap? I mean l1 will start from y=15 and its height is 50. so when l2 starts from 30 it may overlap.. Can you pls check by changing the frames

Ardene answered 29/4, 2011 at 11:30 Comment(0)
E
3

This is how I achieved it without subclassing the annotation view, or using undocumented APIs:

MKAnnotationView* pinView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"];

// Add a custom view to the the callout to allow mutli-line text
UIView *calloutView = [[UIView alloc] initWithFrame:CGRectMake(0, 10, 250, 250)];
UIView *calloutViewHeight = [[UIView alloc] initWithFrame:CGRectMake(0, 10, 1, 250)]; // The reason for this will become apparent below
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 250, 21)];
[title setText:<<your single-line title text>>];
[calloutView addSubview:title];
UILabel *subtitle = [[UILabel alloc] initWithFrame:CGRectMake(10, 35, 250, 250)];
[subtitle setNumberOfLines:0];
[subtitle setText:<<your multi-line title text>>];
CGSize subtitleSize = [subtitle sizeThatFits:CGSizeMake(250, 250)];
[subtitle setFrame:CGRectMake(10, 35, 250, subtitleSize.height)];
[calloutView addSubview:subtitle];
[calloutView setFrame:CGRectMake(0, 10, 250, 35+subtitleSize.height+10)];
[calloutViewHeight setFrame:CGRectMake(0, 0, 1, 35+subtitleSize.height+10)];
pinView.leftCalloutAccessoryView = calloutView;
pinView.rightCalloutAccessoryView = calloutViewHeight;  // The height of the callout is calculated by the right accessory *not* the left, so this fake view is required

You can customize the font/color of the text by adjusting title and subtitle accordingly.

Sure it's a bit of a hack, but it achieves the desired results without subclassing, and allows you to customize with whatever additional buttons/images you'd like to add calloutView (and you can still place buttons in the right-accessory view if you wish, as long as you retain the height).

Euphemia answered 26/11, 2014 at 20:29 Comment(0)
C
2

Two lines in a callout can't be done without getting into unsupported APIs. If you do that, your app will be rejected.

If you have an iPad, you can see an example of a customized callout. Their callout transforms into a popout view, which sticks to the map location in the same way as a callout. So it may be that in the next version of iOS, Apple will expand the callout API. For now you're stuck with one line subtitles.

If you really want to do this, you'll have to make your own callout class and make it stick to the map as it's scrolled. This would probably be difficult.

Another idea is to fake a callout. Make a custom annotation view containing 2 views. The first is a map pin. The second is a callout. When the pin receives a touch, you can unhide your callout view. It will be missing the "pop" animation, but you can put as many lines in as you like.

I've never tried this and it sounds like there would be lots of details to get right, but it might give you a start.

Cinematograph answered 3/5, 2011 at 5:56 Comment(0)
F
1

For setting the button you can use the below code

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
    MKAnnotationView *x=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"someIdentifier"];
    UIButton *button=[UIButton buttonWithType:UIButtonTypeCustom];
    //set the target, title, image etc.
    [x setRightCalloutAccessoryView:button];
    return [x autorelease];
}
Fonzie answered 29/4, 2011 at 11:36 Comment(1)
it is not helped me but i voted this up because i got another idea with this answerNicolette
B
0

You won't be able to do it with public API and will have to reimplement your own custom annotation view.
You can have a look at this code that reimplements the annotation view but mimics the standard iOS one, and you can then enrich it to your own need :
http://blog.asolutions.com/2010/09/building-custom-map-annotation-callouts-part-1/

Bengali answered 4/5, 2011 at 14:34 Comment(0)
T
0

Result Image

This solution sets "justified" text, hope be useful:

Swift 3:

import MapKit

extension MKAnnotationView {
func setCustomLines(customLines lines: [String]) {
    if lines.count <= 0 {
        return
    }

    // get longest line
    var longest = NSString()
    if let max = lines.max(by: { $1.characters.count > $0.characters.count }) {
        longest = max as NSString
    }
    else {
        fatalError("Can't get longest line.")
    }

    // get longest text width
    let font = UIFont.systemFont(ofSize: 15.0)
    let width = (longest.size(attributes: [NSFontAttributeName: font])).width

    // get text from lines
    var text = lines.first
    for i in 1..<lines.count {
        text = text! + " " + lines[i]
    }

    // label
    let label = UILabel()
    label.text = text
    label.font = font
    label.numberOfLines = lines.count
    label.textAlignment = .justified

    // set width of label
    let con = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: width)
    label.addConstraint(con)

    self.detailCalloutAccessoryView = label
}
}
Tetrastichous answered 26/9, 2017 at 4:53 Comment(0)
T
0

It can be done in 9 steps.

I created a label and added it to the annotationView?.detailCalloutAccessoryView property (step 5). I also set the label's text to the annotation.subtitle text (step 2 & step 4). I set the label's .numberOfLines = 0 .This will make the subtitle extend however many # of lines needed (set to 2 if you only need 2).

For the button I created a UIButton and set it's type to .detailDisclosure (step 7). I then set the button's image property to the custom image I want to use (step 8). And finally I set the button to the annotationView's .rightCalloutAccessoryView property (step 9).

I also offset the annotationView's .calloutOffset property (step 6). I got the offset from ray wenderlich

Steps are in the comments above the code

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {

    if annotation.isKindOfClass(MKUserLocation) {
        return nil
    }

    let reuseIdentifier = "reuseIdentifier"

    var annotationView = mapView.mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? MKPinAnnotationView

    if annotationView == nil {

        annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)

        // 1. set the annotationView's canShowCallout property to true
        annotationView?.canShowCallout = true

        // 2. get the subtitle text from the annontation's subtitle property
        let subtitleText = annotation.subtitle ?? "you have no subtitle"

        // 3. create a label for the subtitle text
        let subtitleLabel = UILabel()
        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false

        // 4. set the subtitle's text to the label's text property and number of lines to 0
        subtitleLabel.text = subtitleText
        subtitleLabel.numberOfLines = 0

        // 5. set the annotation's detailCalloutAccessoryView property to the subtitleLabel
        annotationView?.detailCalloutAccessoryView = subtitleLabel

        // 6. offset the annotationView's .calloutOffset property
        annotationView?.calloutOffset = CGPoint(x: -5, y: 5)

        // 7. create a button and MAKE SURE to set it's type to .detailDisclosure or it won't appear
        let button = UIButton(type: .detailDisclosure)

        // 8. set the button's image to whatever custom UIImage your using
        button.setImage(UIImage(named: "yourCustomImage"), for: .normal)

        // 9. set the button to the annotationView's .rightCalloutAccessoryView property
        annotationView?.rightCalloutAccessoryView = button

    } else {
        annotationView!.annotation = annotation
    }

    return annotationView
}
Tannatannage answered 2/1, 2019 at 3:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.