DRY

Web関連の技術の事、食事/お酒の事、旅行の事など

Objecive-Cで独自のPIN&Annotationを使用する方法

また調べていて、あまりサンプルソースが見当たらなかったので自分メモ用に。


やりたかった事は単純にMKMapViewを利用してPINを立てる事と、Annotationの中に住所を入れて表示したかっただけです。

iOS5.1に対応しているので、位置情報はApple推奨のCLGeocoderを使用してます。

■LocationViewController
MKMapViewDelegate、CLLocationManagerDelegateを持っているViewControllerクラス

■MapAnnotation
Map表示時に、地図上に配置するAnnotationクラス

■CalloutAnnotation
PINをタップした際に呼ばれる、Annotationクラス

■CalloutAnnotationView
PINをタップした際に表示される、MKAnnotationViewクラス

LocationViewController.h


#import
#import
#import

#import "TalkViewController.h"
#import "MapAnnotation.h"
#import "CalloutAnnotation.h"
#import "CalloutAnnotationView.h"


@interface LocationViewController : UIViewController
{
@private
BOOL first;
int openMode;
}

@property (strong, nonatomic)id parent;
@property (strong, nonatomic)IBOutlet MKMapView *mapView;
@property (strong, nonatomic)CLLocationManager *locationManager;
@property (strong, nonatomic)CLLocation *presentLocation;
@property (strong, nonatomic)NSMutableDictionary* annotationDictionary;
@property (strong, nonatomic)NSString *addressTxt;


- (id)initWithNibNameAndLocation:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil lat:(double)_lat lng:(double)_lng;

- (IBAction)close:(id)sender;
- (IBAction)focasLocation:(id)sender;
@end

LocationViewController.m

#import "LocationViewController.h"


@interface LocationViewController ()
@end


@implementation LocationViewController
@synthesize parent = _parent;
@synthesize mapView = _mapView;
@synthesize locationManager = _locationManager;
@synthesize presentLocation = _presentLocation;
@synthesize annotationDictionary = _annotationDictionary;
@synthesize addressTxt = _addressTxt;


#define KEY_CLEAR -1
enum LOCATION_VIEW_MODE
{
LOCATION_TELL_MODE, // When you tell your position.
LOCATION_VIEW_MODE, // When you show their posiiton.
};


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
first = YES;
openMode = LOCATION_TELL_MODE;
}
return self;
}


- (id)initWithNibNameAndLocation:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil lat:(double)_lat lng:(double)_lng
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
first = YES;
openMode = LOCATION_VIEW_MODE;

MKCoordinateSpan CoordinateSpan = MKCoordinateSpanMake (0.005,0.005);
self.presentLocation= [[CLLocation alloc] initWithLatitude:_lat longitude:_lng];
CLLocationCoordinate2D mapCenter = self.presentLocation.coordinate;
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(mapCenter,CoordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

}
return self;
}


- (void)viewDidLoad
{
LOG_FUNC
[super viewDidLoad];

// Do any additional setup after loading the view from its nib.
self.title = @"マップ";

self.mapView.delegate = self;
self.mapView.mapType = MKMapTypeStandard;
if (openMode == LOCATION_TELL_MODE)
self.mapView.showsUserLocation =YES;

if (self.locationManager == nil)
self.locationManager = [[CLLocationManager alloc] init];

self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // Accuracy is set up
self.locationManager.distanceFilter = kCLDistanceFilterNone; // Standard distance

// Its present location information reception start.
[self.locationManager startUpdatingLocation];

self.annotationDictionary = [NSMutableDictionary dictionary];
}

- (void)setPinToCoordinate:(CLLocation*)location
{
// setup default span
MKCoordinateSpan span;
if (self.mapView.region.span.latitudeDelta > 100)
span = MKCoordinateSpanMake(0.005, 0.005);
else
span = self.mapView.region.span;

// set the map view to location
CLLocationCoordinate2D centerCoordinate = location.coordinate;
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMake(centerCoordinate, span);
[self.mapView setRegion:coordinateRegion animated:YES];

// lat, lngから地名を逆引き。仕方ないのでココで実行
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:self.presentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if(placemarks && placemarks.count > 0)
{
CLPlacemark *topResult = [placemarks objectAtIndex:0];
self.addressTxt = [NSString stringWithFormat:@"%@ %@ %@ %@ %@",
[topResult name],
[topResult subAdministrativeArea],
[topResult administrativeArea],
[topResult postalCode],
[topResult country]];

LOG(@"%@", self.addressTxt);

// add annotation
MapAnnotation *annotation = [[MapAnnotation alloc] init];
annotation.coordinate = self.presentLocation.coordinate;
annotation.title = self.addressTxt;

[self.mapView addAnnotation:annotation];
[self setAnnotation:annotation forCoordinate:location.coordinate];
}
}];
}

- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
LOG_FUNC

// Get location
if (self.presentLocation == nil)
self.presentLocation = newLocation;

if (first)
{
first = NO;
[self setPinToCoordinate:self.locationManager.location];
}

LOG(@"lat = %f lng = %f", self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude);
MKCoordinateSpan coordinateSpan = MKCoordinateSpanMake (0.005, 0.005);
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(self.presentLocation.coordinate, coordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

[self.locationManager stopUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
LOG_FUNC

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"エラー" message:@"位置情報が取得できませんでした。" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alertView show];
}

#pragma mark - MapView delegate.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation
{
MKAnnotationView *annotationView;
NSString *identifier;

if ([annotation isKindOfClass:[MapAnnotation class]])
{
// Map annotation.
identifier = @"Pin";
annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
}
annotationView.image = [UIImage imageNamed:@"location.png"];
}
else if ([annotation isKindOfClass:[CalloutAnnotation class]])
{
// Callout annotation.
identifier = @"Callout";
annotationView = (CalloutAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil)
{
annotationView = [[CalloutAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
}

//
CalloutAnnotation *calloutAnnotation = (CalloutAnnotation *)annotation;
((CalloutAnnotationView *)annotationView).title = calloutAnnotation.title;
[(CalloutAnnotationView *)annotationView initRect];
[(CalloutAnnotationView *)annotationView presentPointingAtView:self.mapView inView:(CalloutAnnotationView *)annotationView];

// Position adjustment of annotation
((CalloutAnnotationView *)annotationView).centerOffset = CGPointMake(10, -80);

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(calloutTapped:)];
[annotationView addGestureRecognizer:tapGesture];
}

annotationView.annotation = annotation;
return annotationView;
}


/*MKMapViewのピンをタッチせずにコールアウトを出す
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
[mapView selectAnnotation:[mapView.annotations lastObject] animated:YES];
}
*/

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
LOG_FUNC
if ([view.annotation isKindOfClass:[MapAnnotation class]])
{
// Selected the pin annotation.
CalloutAnnotation *calloutAnnotation = [[CalloutAnnotation alloc] init];

MapAnnotation *mapAnnotation = ((MapAnnotation *)view.annotation);
calloutAnnotation.title = mapAnnotation.title;
calloutAnnotation.coordinate = mapAnnotation.coordinate;
mapAnnotation.calloutAnnotation = calloutAnnotation;
[mapView addAnnotation:calloutAnnotation];
}
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
LOG_FUNC
if ([view.annotation isKindOfClass:[MapAnnotation class]])
{
// Deselected the pin annotation.
MapAnnotation *mapAnnotation = ((MapAnnotation *)view.annotation);

[mapView removeAnnotation:mapAnnotation.calloutAnnotation];
mapAnnotation.calloutAnnotation = nil;
}
}

// Processing when the tap of the annotation is carried out
- (void) calloutTapped:(id) sender
{
LOG_FUNC
if (openMode == LOCATION_TELL_MODE)
{
MKCoordinateSpan coordinateSpan = MKCoordinateSpanMake(0.005,0.005);
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(self.presentLocation.coordinate ,coordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

// lat, lngから地名を逆引き。仕方ないのでココで実行
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:self.presentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if(placemarks && placemarks.count > 0)
{
CLPlacemark *topResult = [placemarks objectAtIndex:0];
NSString* txt = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@",
[topResult name],
[topResult subAdministrativeArea],
[topResult administrativeArea],
[topResult postalCode],
[topResult country]];

[self.parent willCloseLocationPicker:txt lat:self.presentLocation.coordinate.latitude lng:self.presentLocation.coordinate.longitude];
}
}];
}
else
{
[self selectMapPicker];
}
}

- (IBAction)close:(id)sender
{
LOG_FUNC
[self dismissModalViewControllerAnimated:YES];
}

- (IBAction)focasLocation:(id)sender
{
LOG_FUNC
[self selectMapPicker];
}

- (void)selectMapPicker
{
UIActionSheet *as = [[UIActionSheet alloc] init];
as.tag = 0;
as.delegate = self;
[as addButtonWithTitle:@"Open in Maps"];
[as addButtonWithTitle:@"Directions From Current Location"];
[as addButtonWithTitle:@"Send to Another Chat"];
[as addButtonWithTitle:@"Cancel"];
as.cancelButtonIndex = 3;
//as.destructiveButtonIndex = 0;
[as showInView:self.view];
}

- (void)actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (actionSheet.tag == 0)
{
switch (buttonIndex)
{
case 0: // "Open in Maps"
{
NSString* urlstr = [NSString stringWithFormat:@"http://maps.google.com/maps?q=%@@%1.6f,%1.6f",
[self.addressTxt stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude];

NSURL* url = [NSURL URLWithString: urlstr];
if ([[UIApplication sharedApplication] canOpenURL:url])
{
[[UIApplication sharedApplication] openURL:url];
}
break;
}
case 1: // "Directions From Current Location"
{
NSString* urlstr = [NSString stringWithFormat:@"http://maps.google.com/maps?saddr=%1.6f,%1.6f&daddr=%1.6f,%1.6f&dirflg=w",
self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude,
self.locationManager.location.coordinate.latitude, self.locationManager.location.coordinate.longitude];

NSURL* url = [NSURL URLWithString: urlstr];
if ([[UIApplication sharedApplication] canOpenURL:url])
{
[[UIApplication sharedApplication] openURL:url];
}

break;
}
case 2: // "Send to Another Chat"
{
UIAlertView *alert =
[[UIAlertView alloc] initWithTitle:@"Sorry" message:@"工事中です"
delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];

break;
}
}
}
}

- (void)viewDidUnload
{
LOG_FUNC
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark -
#pragma mark
- (void)setAnnotation:(MapAnnotation*)annotation forCoordinate:(CLLocationCoordinate2D)coordinate
{
NSValue* coordinateValue = [NSValue value:&coordinate
withObjCType:@encode(CLLocationCoordinate2D)];
[self.annotationDictionary setObject:annotation
forKey:coordinateValue];
}

- (MapAnnotation*)annotationForCoordinate:(CLLocationCoordinate2D)coordinate
{
NSValue* coordinateValue = [NSValue value:&coordinate
withObjCType:@encode(CLLocationCoordinate2D)];
MapAnnotation* annotation = [self.annotationDictionary objectForKey:coordinateValue];
[self.annotationDictionary removeObjectForKey:coordinateValue];

return annotation;
}
@end

LocationViewController.xib


MapAnnotation.h


#import
#import
#import "CalloutAnnotation.h"

@interface MapAnnotation : NSObject

@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (strong , nonatomic) NSString *title;
@property (nonatomic, strong) CalloutAnnotation *calloutAnnotation;


@end

MapAnnotation.m


#import "MapAnnotation.h"

@implementation MapAnnotation
@synthesize coordinate = _coordinate;
@synthesize title = _title;
@synthesize calloutAnnotation = _calloutAnnotation;

@end

CalloutAnnotation.h


#import
#import


@interface CalloutAnnotation : NSObject

@property (nonatomic, strong) NSString *title;
@property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;

@end

CalloutAnnotation.m


#import "CalloutAnnotation.h"

@implementation CalloutAnnotation

@synthesize title = _title;
@synthesize coordinate = _coordinate;

@end

最後が一番重要な吹き出しを作成するCalloutAnnotationViewなんですが、これの描画周りはコチラをかなり利用させて貰いました。
ただし、吹き出しは常に上に出ていて欲しいので、その辺りを改変しました。
※MITライセンスとの事ですが、何か問題ありましたらご連絡下さい。

CalloutAnnotationView.h


#import
#import


@interface CalloutAnnotationView : MKAnnotationView
{
@private
NSString *title;

@private
UIColor *backgroundColor;
UIColor *textColor;
UIFont *textFont;
UIColor *borderColor;
CGFloat borderWidth;

@private
CGSize bubbleSize;
CGFloat cornerRadius;
BOOL highlight;
CGFloat sidePadding;
CGFloat topMargin;
CGFloat pointerSize;
CGPoint targetPoint;
}

@property (nonatomic, strong)NSString *title;
@property (nonatomic, strong)UIColor *backgroundColor;
@property (nonatomic, strong)UIColor *textColor;
@property (nonatomic, strong)UIFont *textFont;
@property (nonatomic, assign)UITextAlignment textAlignment;
@property (nonatomic, strong)UIColor *borderColor;
@property (nonatomic, assign)CGFloat borderWidth;


- (void)initRect;
- (void)presentPointingAtView:(UIView *)targetView inView:(UIView *)containerView;

@end

CalloutAnnotationView.m


//
// CMPopTipView.m
//
// Created by Chris Miles on 18/07/10.
// Copyright (c) Chris Miles 2010-2012.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//


#import "CalloutAnnotationView.h"


@implementation CalloutAnnotationView
@synthesize title = _title;

@synthesize backgroundColor;
@synthesize textColor;
@synthesize textFont;
@synthesize textAlignment;
@synthesize borderColor;
@synthesize borderWidth;


#define TEXT_SIZE 10.0
#define TEXT_WIDTH 320.0


- (float)textHeight:(NSString*)text
{
CGSize boundingSize = CGSizeMake (TEXT_WIDTH, CGFLOAT_MAX);

//文字の横幅から高さを算出
CGSize labelsize = [text sizeWithFont:[UIFont systemFontOfSize:TEXT_SIZE]
constrainedToSize:boundingSize
lineBreakMode:UILineBreakModeWordWrap];

NSLog(@"%f", labelsize.width);
NSLog(@"%f", labelsize.height);
return labelsize.height;
}

// Initialize Rect
- (void)initRect
{
self.opaque = NO;
[self setFrame:CGRectMake (0, 0, TEXT_WIDTH, 0)];

cornerRadius = 10.0;
topMargin = 2.0;
pointerSize = 12.0;
sidePadding = 2.0;
borderWidth = 1.0;

self.textFont = [UIFont boldSystemFontOfSize:TEXT_SIZE];
self.textColor = [UIColor whiteColor];
self.textAlignment = UITextAlignmentCenter;
self.backgroundColor = [UIColor grayColor];
self.borderColor = [UIColor blackColor];
}

// Initialize point
- (void)presentPointingAtView:(UIView *)targetView inView:(UIView *)containerView
{
LOG_FUNC

// Size of rounded rect
CGFloat rectWidth = (int)(containerView.frame.size.width * 2 / 3);

CGSize textSize = CGSizeZero;
if (self.title != nil)
{
textSize= [self.title sizeWithFont:textFont constrainedToSize:CGSizeMake(rectWidth, 99999.0) lineBreakMode:UILineBreakModeWordWrap];
}
bubbleSize = CGSizeMake (textSize.width + cornerRadius * 2, textSize.height + cornerRadius * 2);

CGPoint targetOriginInContainer = [targetView convertPoint:CGPointMake(0.0, 0.0) toView:containerView];
// Y coordinate of pointer target (within containerView)
CGFloat pointerY = targetOriginInContainer.y;

CGFloat W = containerView.frame.size.width;
CGPoint p = [targetView.superview convertPoint:targetView.center toView:containerView];
CGFloat x_p = p.x;
CGFloat x_b = x_p - roundf(bubbleSize.width / 2);
if (x_b < sidePadding)
{
x_b = sidePadding;
}
if (x_b + bubbleSize.width + sidePadding > W)
{
x_b = W - bubbleSize.width - sidePadding;
}
if (x_p - pointerSize < x_b + cornerRadius)
{
x_p = x_b + cornerRadius + pointerSize;
}
if (x_p + pointerSize > x_b + bubbleSize.width - cornerRadius)
{
x_p = x_b + bubbleSize.width - cornerRadius - pointerSize;
}

CGFloat fullHeight = bubbleSize.height + pointerSize + 10.0;
CGFloat y_b = pointerY - fullHeight;
targetPoint = CGPointMake(x_p - x_b, fullHeight - 2.0);

CGRect finalFrame = CGRectMake(x_b - sidePadding, y_b,
bubbleSize.width + sidePadding * 2, fullHeight);

self.alpha = 0.0;
CGRect startFrame = finalFrame;
startFrame.origin.y += 10;
self.frame = startFrame;

[self setNeedsDisplay];

[UIView beginAnimations:nil context:nil];
self.alpha = 1.0;
self.frame = finalFrame;
[UIView commitAnimations];
}


- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
LOG_FUNC
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];

if (self)
{
}

return self;
}

- (CGRect)bubbleFrame
{
CGRect bubbleFrame = CGRectMake(2.0, targetPoint.y - pointerSize - bubbleSize.height, bubbleSize.width, bubbleSize.height);
return bubbleFrame;
}

- (CGRect)contentFrame
{
CGRect bubbleFrame = [self bubbleFrame];
CGRect contentFrame = CGRectMake(bubbleFrame.origin.x + cornerRadius,
bubbleFrame.origin.y + cornerRadius,
bubbleFrame.size.width - cornerRadius * 2,
bubbleFrame.size.height - cornerRadius * 2);
return contentFrame;
}

- (void)drawRect:(CGRect)rect
{
LOG_FUNC

CGRect bubbleRect = [self bubbleFrame];
CGContextRef c = UIGraphicsGetCurrentContext();

CGContextSetRGBStrokeColor(c, 0.0, 0.0, 0.0, 1.0); // black
CGContextSetLineWidth(c, borderWidth);

CGMutablePathRef bubblePath = CGPathCreateMutable();

CGPathMoveToPoint(bubblePath, NULL, targetPoint.x, targetPoint.y);
CGPathAddLineToPoint(bubblePath, NULL, targetPoint.x - pointerSize, targetPoint.y - pointerSize);

CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x, bubbleRect.origin.y + bubbleRect.size.height,
bubbleRect.origin.x, bubbleRect.origin.y + bubbleRect.size.height - cornerRadius,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x, bubbleRect.origin.y,
bubbleRect.origin.x + cornerRadius, bubbleRect.origin.y,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y + cornerRadius,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y+bubbleRect.size.height,
bubbleRect.origin.x + bubbleRect.size.width - cornerRadius, bubbleRect.origin.y + bubbleRect.size.height,
cornerRadius);
CGPathAddLineToPoint(bubblePath, NULL, targetPoint.x + pointerSize, targetPoint.y - pointerSize);

CGPathCloseSubpath(bubblePath);

// Draw shadow
CGContextAddPath(c, bubblePath);
CGContextSaveGState(c);
CGContextSetShadow(c, CGSizeMake(0, 3), 5);
CGContextSetRGBFillColor(c, 0.0, 0.0, 0.0, 0.9);
CGContextFillPath(c);
CGContextRestoreGState(c);

// Draw clipped background gradient
CGContextAddPath(c, bubblePath);
CGContextClip(c);
CGFloat bubbleMiddle = (bubbleRect.origin.y + (bubbleRect.size.height / 2)) / self.bounds.size.height;


CGGradientRef myGradient;
CGColorSpaceRef myColorSpace;
size_t locationCount = 5;
CGFloat locationList[] = {0.0, bubbleMiddle - 0.03, bubbleMiddle, bubbleMiddle + 0.03, 1.0};

CGFloat colourHL = 0.0;
if (highlight)
colourHL = 0.25;

CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
int numComponents = CGColorGetNumberOfComponents([backgroundColor CGColor]);
const CGFloat *components = CGColorGetComponents([backgroundColor CGColor]);
if (numComponents == 2)
{
red = components[0];
green = components[0];
blue = components[0];
alpha = components[1];
}
else
{
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
}
CGFloat colorList[] = {
//red, green, blue, alpha
red*1.16+colourHL, green*1.16+colourHL, blue*1.16+colourHL, alpha,
red*1.16+colourHL, green*1.16+colourHL, blue*1.16+colourHL, alpha,
red*1.08+colourHL, green*1.08+colourHL, blue*1.08+colourHL, alpha,
red +colourHL, green +colourHL, blue +colourHL, alpha,
red +colourHL, green +colourHL, blue +colourHL, alpha
};
myColorSpace = CGColorSpaceCreateDeviceRGB();
myGradient = CGGradientCreateWithColorComponents(myColorSpace, colorList, locationList, locationCount);
CGPoint startPoint, endPoint;
startPoint.x = 0;
startPoint.y = 0;
endPoint.x = 0;
endPoint.y = CGRectGetMaxY(self.bounds);

CGContextDrawLinearGradient(c, myGradient, startPoint, endPoint,0);
CGGradientRelease(myGradient);
CGColorSpaceRelease(myColorSpace);


//Draw Border
int numBorderComponents = CGColorGetNumberOfComponents([borderColor CGColor]);
const CGFloat *borderComponents = CGColorGetComponents(borderColor.CGColor);
CGFloat r, g, b, a;
if (numBorderComponents == 2)
{
r = borderComponents[0];
g = borderComponents[0];
b = borderComponents[0];
a = borderComponents[1];
}
else
{
r = borderComponents[0];
g = borderComponents[1];
b = borderComponents[2];
a = borderComponents[3];
}

CGContextSetRGBStrokeColor(c, r, g, b, a);
CGContextAddPath(c, bubblePath);
CGContextDrawPath(c, kCGPathStroke);

CGPathRelease(bubblePath);

// Draw text
if (self.title)
{
[textColor set];
CGRect textFrame = [self contentFrame];
[self.title drawInRect:textFrame
withFont:textFont
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentCenter];
}
}
@end