summaryrefslogtreecommitdiff
path: root/platform/ios/vendor/SMCalloutView/SMCalloutView.m
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/vendor/SMCalloutView/SMCalloutView.m')
-rwxr-xr-xplatform/ios/vendor/SMCalloutView/SMCalloutView.m939
1 files changed, 0 insertions, 939 deletions
diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.m b/platform/ios/vendor/SMCalloutView/SMCalloutView.m
deleted file mode 100755
index 06626f9497..0000000000
--- a/platform/ios/vendor/SMCalloutView/SMCalloutView.m
+++ /dev/null
@@ -1,939 +0,0 @@
-#import "SMCalloutView.h"
-
-//
-// UIView frame helpers - we do a lot of UIView frame fiddling in this class; these functions help keep things readable.
-//
-
-@interface UIView (SMFrameAdditions)
-@property (nonatomic, assign) CGPoint frameOrigin;
-@property (nonatomic, assign) CGSize frameSize;
-@property (nonatomic, assign) CGFloat frameX, frameY, frameWidth, frameHeight; // normal rect properties
-@property (nonatomic, assign) CGFloat frameLeft, frameTop, frameRight, frameBottom; // these will stretch/shrink the rect
-@end
-
-//
-// Callout View.
-//
-
-#define CALLOUT_DEFAULT_CONTAINER_HEIGHT 44 // height of just the main portion without arrow
-#define CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT 52 // height of just the main portion without arrow (when subtitle is present)
-#define CALLOUT_MIN_WIDTH 61 // minimum width of system callout
-#define TITLE_HMARGIN 12 // the title/subtitle view's normal horizontal margin from the edges of our callout view or from the accessories
-#define TITLE_TOP 11 // the top of the title view when no subtitle is present
-#define TITLE_SUB_TOP 4 // the top of the title view when a subtitle IS present
-#define TITLE_HEIGHT 21 // title height, fixed
-#define SUBTITLE_TOP 28 // the top of the subtitle, when present
-#define SUBTITLE_HEIGHT 15 // subtitle height, fixed
-#define BETWEEN_ACCESSORIES_MARGIN 7 // margin between accessories when no title/subtitle is present
-#define TOP_ANCHOR_MARGIN 13 // all the above measurements assume a bottom anchor! if we're pointing "up" we'll need to add this top margin to everything.
-#define COMFORTABLE_MARGIN 10 // when we try to reposition content to be visible, we'll consider this margin around your target rect
-
-NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0;
-
-@interface MGLSMCalloutView ()
-@property (nonatomic, strong) UIButton *containerView; // for masking and interaction
-@property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel;
-@property (nonatomic, assign) MGLSMCalloutArrowDirection currentArrowDirection;
-@property (nonatomic, assign) BOOL popupCancelled;
-@end
-
-@implementation MGLSMCalloutView
-
-+ (MGLSMCalloutView *)platformCalloutView {
- // MGL: Mapbox does not need or include the custom flavor, so this is modified to just use SMCalloutView.
- return [MGLSMCalloutView new];
-}
-
-- (id)initWithFrame:(CGRect)frame {
- if (self = [super initWithFrame:frame]) {
- self.permittedArrowDirection = MGLSMCalloutArrowDirectionDown;
- self.presentAnimation = MGLSMCalloutAnimationBounce;
- self.dismissAnimation = MGLSMCalloutAnimationFade;
- self.backgroundColor = [UIColor clearColor];
- self.containerView = [UIButton new];
- self.containerView.isAccessibilityElement = NO;
- self.isAccessibilityElement = NO;
- self.contentViewInset = UIEdgeInsetsMake(12, 12, 12, 12);
-
- [self.containerView addTarget:self action:@selector(highlightIfNecessary) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside];
- [self.containerView addTarget:self action:@selector(unhighlightIfNecessary) forControlEvents:UIControlEventTouchDragOutside | UIControlEventTouchCancel | UIControlEventTouchUpOutside | UIControlEventTouchUpInside];
- [self.containerView addTarget:self action:@selector(calloutClicked) forControlEvents:UIControlEventTouchUpInside];
- }
- return self;
-}
-
-- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
- UIView *hitView = [super hitTest:point withEvent:event];
-
- // If we tapped on our container (i.e. the UIButton), then ask the background
- // view if the point is "inside". MGLSMCalloutMaskedBackgroundView provides a
- // custom implementation that checks against the main callout and the down arrow.
- // This avoids taps in "blank" space being detected
-
- if (hitView == self.containerView) {
- // Ideally we'd use the background mask to determine whether a tap point
- // is valid, but that's overkill in this situation
- CGPoint backgroundPoint = [self convertPoint:point toView:self.backgroundView];
- if (![self.backgroundView pointInside:backgroundPoint withEvent:event]) {
- return nil;
- }
- }
-
- return hitView;
-}
-
-- (BOOL)supportsHighlighting {
- if (![self.delegate respondsToSelector:@selector(calloutViewClicked:)])
- return NO;
- if ([self.delegate respondsToSelector:@selector(calloutViewShouldHighlight:)])
- return [self.delegate calloutViewShouldHighlight:self];
- return YES;
-}
-
-- (void)highlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = YES; }
-- (void)unhighlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = NO; }
-
-- (void)calloutClicked {
- if ([self.delegate respondsToSelector:@selector(calloutViewClicked:)])
- [self.delegate calloutViewClicked:self];
-}
-
-- (UIView *)titleViewOrDefault {
- if (self.titleView)
- // if you have a custom title view defined, return that.
- return self.titleView;
- else {
- if (!self.titleLabel) {
- // create a default titleView
- self.titleLabel = [UILabel new];
- self.titleLabel.frameHeight = TITLE_HEIGHT;
- self.titleLabel.opaque = NO;
- self.titleLabel.backgroundColor = [UIColor clearColor];
- self.titleLabel.font = [UIFont systemFontOfSize:17];
- self.titleLabel.textColor = [UIColor blackColor];
- }
- return self.titleLabel;
- }
-}
-
-- (UIView *)subtitleViewOrDefault {
- if (self.subtitleView)
- // if you have a custom subtitle view defined, return that.
- return self.subtitleView;
- else {
- if (!self.subtitleLabel) {
- // create a default subtitleView
- self.subtitleLabel = [UILabel new];
- self.subtitleLabel.frameHeight = SUBTITLE_HEIGHT;
- self.subtitleLabel.opaque = NO;
- self.subtitleLabel.backgroundColor = [UIColor clearColor];
- self.subtitleLabel.font = [UIFont systemFontOfSize:12];
- self.subtitleLabel.textColor = [UIColor blackColor];
- }
- return self.subtitleLabel;
- }
-}
-
-- (MGLSMCalloutBackgroundView *)backgroundView {
- // create our default background on first access only if it's nil, since you might have set your own background anyway.
- return _backgroundView ? _backgroundView : (_backgroundView = [self defaultBackgroundView]);
-}
-
-- (MGLSMCalloutBackgroundView *)defaultBackgroundView {
- return [MGLSMCalloutMaskedBackgroundView new];
-}
-
-- (void)rebuildSubviews {
- // remove and re-add our appropriate subviews in the appropriate order
- [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
- [self.containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
- [self setNeedsDisplay];
-
- [self addSubview:self.backgroundView];
- [self addSubview:self.containerView];
-
- if (self.contentView) {
- [self.containerView addSubview:self.contentView];
- }
- else {
- if (self.titleViewOrDefault) [self.containerView addSubview:self.titleViewOrDefault];
- if (self.subtitleViewOrDefault) [self.containerView addSubview:self.subtitleViewOrDefault];
- }
- if (self.leftAccessoryView) [self.containerView addSubview:self.leftAccessoryView];
- if (self.rightAccessoryView) [self.containerView addSubview:self.rightAccessoryView];
-}
-
-// Accessory margins. Accessories are centered vertically when shorter
-// than the callout, otherwise they grow from the upper corner.
-
-- (CGFloat)leftAccessoryVerticalMargin {
- if (self.leftAccessoryView.frameHeight < self.calloutContainerHeight)
- return roundf((self.calloutContainerHeight - self.leftAccessoryView.frameHeight) / 2);
- else
- return 0;
-}
-
-- (CGFloat)leftAccessoryHorizontalMargin {
- return fminf(self.leftAccessoryVerticalMargin, TITLE_HMARGIN);
-}
-
-- (CGFloat)rightAccessoryVerticalMargin {
- if (self.rightAccessoryView.frameHeight < self.calloutContainerHeight)
- return roundf((self.calloutContainerHeight - self.rightAccessoryView.frameHeight) / 2);
- else
- return 0;
-}
-
-- (CGFloat)rightAccessoryHorizontalMargin {
- return fminf(self.rightAccessoryVerticalMargin, TITLE_HMARGIN);
-}
-
-- (CGFloat)innerContentMarginLeft {
- if (self.leftAccessoryView)
- return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.frameWidth + TITLE_HMARGIN;
- else
- return self.contentViewInset.left;
-}
-
-- (CGFloat)innerContentMarginRight {
- if (self.rightAccessoryView)
- return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.frameWidth + TITLE_HMARGIN;
- else
- return self.contentViewInset.right;
-}
-
-- (CGFloat)calloutHeight {
- return self.calloutContainerHeight + self.backgroundView.anchorHeight;
-}
-
-- (CGFloat)calloutContainerHeight {
- if (self.contentView)
- return self.contentView.frameHeight + self.contentViewInset.bottom + self.contentViewInset.top;
- else if (self.subtitleView || self.subtitle.length > 0)
- return CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT;
- else
- return CALLOUT_DEFAULT_CONTAINER_HEIGHT;
-}
-
-- (CGSize)sizeThatFits:(CGSize)size {
-
- // calculate how much non-negotiable space we need to reserve for margin and accessories
- CGFloat margin = self.innerContentMarginLeft + self.innerContentMarginRight;
-
- // how much room is left for text?
- CGFloat availableWidthForText = size.width - margin - 1;
-
- // no room for text? then we'll have to squeeze into the given size somehow.
- if (availableWidthForText < 0)
- availableWidthForText = 0;
-
- CGSize preferredTitleSize = [self.titleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, TITLE_HEIGHT)];
- CGSize preferredSubtitleSize = [self.subtitleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, SUBTITLE_HEIGHT)];
-
- // total width we'd like
- CGFloat preferredWidth;
-
- if (self.contentView) {
-
- // if we have a content view, then take our preferred size directly from that
- preferredWidth = self.contentView.frameWidth + margin;
- }
- else if (preferredTitleSize.width >= 0.000001 || preferredSubtitleSize.width >= 0.000001) {
-
- // if we have a title or subtitle, then our assumed margins are valid, and we can apply them
- preferredWidth = fmaxf(preferredTitleSize.width, preferredSubtitleSize.width) + margin;
- }
- else {
- // ok we have no title or subtitle to speak of. In this case, the system callout would actually not display
- // at all! But we can handle it.
- preferredWidth = self.leftAccessoryView.frameWidth + self.rightAccessoryView.frameWidth + self.leftAccessoryHorizontalMargin + self.rightAccessoryHorizontalMargin;
-
- if (self.leftAccessoryView && self.rightAccessoryView)
- preferredWidth += BETWEEN_ACCESSORIES_MARGIN;
- }
-
- // ensure we're big enough to fit our graphics!
- preferredWidth = fmaxf(preferredWidth, CALLOUT_MIN_WIDTH);
-
- // ask to be smaller if we have space, otherwise we'll fit into what we have by truncating the title/subtitle.
- return CGSizeMake(fminf(preferredWidth, size.width), self.calloutHeight);
-}
-
-- (CGSize)offsetToContainRect:(CGRect)innerRect inRect:(CGRect)outerRect {
- CGFloat nudgeRight = fmaxf(0, CGRectGetMinX(outerRect) - CGRectGetMinX(innerRect));
- CGFloat nudgeLeft = fminf(0, CGRectGetMaxX(outerRect) - CGRectGetMaxX(innerRect));
- CGFloat nudgeTop = fmaxf(0, CGRectGetMinY(outerRect) - CGRectGetMinY(innerRect));
- CGFloat nudgeBottom = fminf(0, CGRectGetMaxY(outerRect) - CGRectGetMaxY(innerRect));
- return CGSizeMake(nudgeLeft ? nudgeLeft : nudgeRight, nudgeTop ? nudgeTop : nudgeBottom);
-}
-
-- (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect {
-
- const CGFloat defaultMargin = 20.0f;
-
- // form our subviews based on our content set so far
- [self rebuildSubviews];
-
- // size the callout to fit the width constraint as best as possible
- CGFloat height = self.calloutHeight;
- CGSize size = [self sizeThatFits:CGSizeMake(0.0f, height)];
-
- // Without re-jigging presentCalloutFromRect, let's just make a best-guess with what we have
- // right now.
- CGFloat horizontalMargin = fmaxf(0, ceilf((CALLOUT_MIN_WIDTH-rect.size.width)/2));
-
- UIEdgeInsets insets = {
- .top = 0.0f,
- .right = -defaultMargin - horizontalMargin,
- .bottom = 0.0f,
- .left = -defaultMargin - horizontalMargin
- };
-
- if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionUp)
- insets.bottom -= (defaultMargin + size.height);
- else
- insets.top -= (defaultMargin + size.height);
-
- return insets;
-}
-
-
-- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated {
- [self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToLayer:constrainedView.layer animated:animated];
-}
-
-- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
- [self presentCalloutFromRect:rect inLayer:layer ofView:nil constrainedToLayer:constrainedLayer animated:animated];
-}
-
-- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated {
- // figure out the constrained view's rect in our popup view's coordinate system
- CGRect constrainedRect = [constrainedLayer convertRect:constrainedLayer.bounds toLayer:layer];
- [self presentCalloutFromRect:rect inLayer:layer ofView:view constrainedToRect:constrainedRect animated:animated];
-}
-
-- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated {
- [self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToRect:constrainedRect animated:animated];
-}
-
-// this private method handles both CALayer and UIView parents depending on what's passed.
-- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated {
-
- // Sanity check: dismiss this callout immediately if it's displayed somewhere
- if (self.layer.superlayer) [self dismissCalloutAnimated:NO];
-
- // cancel all animations that may be in progress
- [self.layer removeAnimationForKey:@"present"];
- [self.layer removeAnimationForKey:@"dismiss"];
-
-
- // apply our edge constraints
- constrainedRect = UIEdgeInsetsInsetRect(constrainedRect, self.constrainedInsets);
-
- constrainedRect = CGRectInset(constrainedRect, COMFORTABLE_MARGIN, COMFORTABLE_MARGIN);
-
- // form our subviews based on our content set so far
- [self rebuildSubviews];
-
- // apply title/subtitle (if present
- self.titleLabel.text = self.title;
- self.subtitleLabel.text = self.subtitle;
-
- // size the callout to fit the width constraint as best as possible
- self.frameSize = [self sizeThatFits:CGSizeMake(constrainedRect.size.width, self.calloutHeight)];
-
- // how much room do we have in the constraint box, both above and below our target rect?
- CGFloat topSpace = CGRectGetMinY(rect) - CGRectGetMinY(constrainedRect);
- CGFloat bottomSpace = CGRectGetMaxY(constrainedRect) - CGRectGetMaxY(rect);
-
- // we prefer to point our arrow down.
- MGLSMCalloutArrowDirection bestDirection = MGLSMCalloutArrowDirectionDown;
-
- // we'll point it up though if that's the only option you gave us.
- if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionUp)
- bestDirection = MGLSMCalloutArrowDirectionUp;
-
- // or, if we don't have enough space on the top and have more space on the bottom, and you
- // gave us a choice, then pointing up is the better option.
- if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionAny && topSpace < self.calloutHeight && bottomSpace > topSpace)
- bestDirection = MGLSMCalloutArrowDirectionUp;
-
- self.currentArrowDirection = bestDirection;
-
- // we want to point directly at the horizontal center of the given rect. calculate our "anchor point" in terms of our
- // target view's coordinate system. make sure to offset the anchor point as requested if necessary.
- CGFloat anchorX = self.calloutOffset.x + CGRectGetMidX(rect);
- CGFloat anchorY = self.calloutOffset.y + (bestDirection == MGLSMCalloutArrowDirectionDown ? CGRectGetMinY(rect) : CGRectGetMaxY(rect));
-
- // we prefer to sit centered directly above our anchor
- CGFloat calloutX = roundf(anchorX - self.frameWidth / 2);
-
- // but not if it's going to get too close to the edge of our constraints
- if (calloutX < constrainedRect.origin.x)
- calloutX = constrainedRect.origin.x;
-
- if (calloutX > constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth)
- calloutX = constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth;
-
- // what's the farthest to the left and right that we could point to, given our background image constraints?
- CGFloat minPointX = calloutX + self.backgroundView.anchorMargin;
- CGFloat maxPointX = calloutX + self.frameWidth - self.backgroundView.anchorMargin;
-
- // we may need to scoot over to the left or right to point at the correct spot
- CGFloat adjustX = 0;
- if (anchorX < minPointX) adjustX = anchorX - minPointX;
- if (anchorX > maxPointX) adjustX = anchorX - maxPointX;
-
- // add the callout to the given layer (or view if possible, to receive touch events)
- if (view)
- [view addSubview:self];
- else
- [layer addSublayer:self.layer];
-
- CGPoint calloutOrigin = {
- .x = calloutX + adjustX,
- .y = bestDirection == MGLSMCalloutArrowDirectionDown ? (anchorY - self.calloutHeight) : anchorY
- };
-
- self.frameOrigin = calloutOrigin;
-
- // now set the *actual* anchor point for our layer so that our "popup" animation starts from this point.
- CGPoint anchorPoint = [layer convertPoint:CGPointMake(anchorX, anchorY) toLayer:self.layer];
-
- // pass on the anchor point to our background view so it knows where to draw the arrow
- self.backgroundView.arrowPoint = anchorPoint;
-
- // adjust it to unit coordinates for the actual layer.anchorPoint property
- anchorPoint.x /= self.frameWidth;
- anchorPoint.y /= self.frameHeight;
- self.layer.anchorPoint = anchorPoint;
-
- // setting the anchor point moves the view a bit, so we need to reset
- self.frameOrigin = calloutOrigin;
-
- // make sure our frame is not on half-pixels or else we may be blurry!
- CGFloat scale = [UIScreen mainScreen].scale;
- self.frameX = floorf(self.frameX*scale)/scale;
- self.frameY = floorf(self.frameY*scale)/scale;
-
- // layout now so we can immediately start animating to the final position if needed
- [self setNeedsLayout];
- [self layoutIfNeeded];
-
- // if we're outside the bounds of our constraint rect, we'll give our delegate an opportunity to shift us into position.
- // consider both our size and the size of our target rect (which we'll assume to be the size of the content you want to scroll into view.
- CGRect contentRect = CGRectUnion(self.frame, rect);
- CGSize offset = [self offsetToContainRect:contentRect inRect:constrainedRect];
-
- NSTimeInterval delay = 0;
- self.popupCancelled = NO; // reset this before calling our delegate below
-
- if ([self.delegate respondsToSelector:@selector(calloutView:delayForRepositionWithSize:)] && !CGSizeEqualToSize(offset, CGSizeZero))
- delay = [self.delegate calloutView:(id)self delayForRepositionWithSize:offset];
-
- // there's a chance that user code in the delegate method may have called -dismissCalloutAnimated to cancel things; if that
- // happened then we need to bail!
- if (self.popupCancelled) return;
-
- // now we want to mask our contents to our background view (if requested) to match the iOS 7 style
- self.containerView.layer.mask = self.backgroundView.contentMask;
-
- // if we need to delay, we don't want to be visible while we're delaying, so hide us in preparation for our popup
- self.hidden = YES;
-
- // create the appropriate animation, even if we're not animated
- CAAnimation *animation = [self animationWithType:self.presentAnimation presenting:YES];
-
- // nuke the duration if no animation requested - we'll still need to "run" the animation to get delays and callbacks
- if (!animated)
- animation.duration = 0.0000001; // can't be zero or the animation won't "run"
-
- animation.beginTime = CACurrentMediaTime() + delay;
- animation.delegate = self;
-
- [self.layer addAnimation:animation forKey:@"present"];
-}
-
-- (void)animationDidStart:(CAAnimation *)anim {
- BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
-
- if (presenting) {
- if ([_delegate respondsToSelector:@selector(calloutViewWillAppear:)])
- [_delegate calloutViewWillAppear:(id)self];
-
- // ok, animation is on, let's make ourselves visible!
- self.hidden = NO;
- }
- else if (!presenting) {
- if ([_delegate respondsToSelector:@selector(calloutViewWillDisappear:)])
- [_delegate calloutViewWillDisappear:(id)self];
- }
-}
-
-- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished {
- BOOL presenting = [[anim valueForKey:@"presenting"] boolValue];
-
- if (presenting && finished) {
- if ([_delegate respondsToSelector:@selector(calloutViewDidAppear:)])
- [_delegate calloutViewDidAppear:(id)self];
- }
- else if (!presenting && finished) {
-
- [self removeFromParent];
- [self.layer removeAnimationForKey:@"dismiss"];
-
- if ([_delegate respondsToSelector:@selector(calloutViewDidDisappear:)])
- [_delegate calloutViewDidDisappear:(id)self];
- }
-}
-
-- (void)dismissCalloutAnimated:(BOOL)animated {
-
- // cancel all animations that may be in progress
- [self.layer removeAnimationForKey:@"present"];
- [self.layer removeAnimationForKey:@"dismiss"];
-
- self.popupCancelled = YES;
-
- if (animated) {
- CAAnimation *animation = [self animationWithType:self.dismissAnimation presenting:NO];
- animation.delegate = self;
- [self.layer addAnimation:animation forKey:@"dismiss"];
- }
- else {
- [self removeFromParent];
- }
-}
-
-- (void)removeFromParent {
- if (self.superview)
- [self removeFromSuperview];
- else {
- // removing a layer from a superlayer causes an implicit fade-out animation that we wish to disable.
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- [self.layer removeFromSuperlayer];
- [CATransaction commit];
- }
-}
-
-- (CAAnimation *)animationWithType:(MGLSMCalloutAnimation)type presenting:(BOOL)presenting {
- CAAnimation *animation = nil;
-
- switch (type)
- {
- case MGLSMCalloutAnimationBounce:
- {
- CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
- fade.duration = 0.23;
- fade.fromValue = presenting ? @0.0 : @1.0;
- fade.toValue = presenting ? @1.0 : @0.0;
- fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
-
- CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
- bounce.duration = 0.23;
- bounce.fromValue = presenting ? @0.7 : @1.0;
- bounce.toValue = presenting ? @1.0 : @0.7;
- bounce.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.59367:0.12066:0.18878:1.5814];
-
- CAAnimationGroup *group = [CAAnimationGroup animation];
- group.animations = @[fade, bounce];
- group.duration = 0.23;
-
- animation = group;
- break;
- }
-
- case MGLSMCalloutAnimationFade:
- {
- CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
- fade.duration = 1.0/3.0;
- fade.fromValue = presenting ? @0.0 : @1.0;
- fade.toValue = presenting ? @1.0 : @0.0;
- animation = fade;
- break;
- }
-
- case MGLSMCalloutAnimationStretch:
- {
- CABasicAnimation *stretch = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
- stretch.duration = 0.1;
- stretch.fromValue = presenting ? @0.0 : @1.0;
- stretch.toValue = presenting ? @1.0 : @0.0;
- animation = stretch;
- break;
- }
- }
-
- // CAAnimation is KVC compliant, so we can store whether we're presenting for lookup in our delegate methods
- [animation setValue:@(presenting) forKey:@"presenting"];
-
- animation.fillMode = kCAFillModeForwards;
- animation.removedOnCompletion = NO;
-
- return animation;
-}
-
-- (void)layoutSubviews {
-
- self.containerView.frame = self.bounds;
- self.backgroundView.frame = self.bounds;
-
- // if we're pointing up, we'll need to push almost everything down a bit
- CGFloat dy = self.currentArrowDirection == MGLSMCalloutArrowDirectionUp ? TOP_ANCHOR_MARGIN : 0;
-
- self.titleViewOrDefault.frameX = self.innerContentMarginLeft;
- self.titleViewOrDefault.frameY = (self.subtitleView || self.subtitle.length ? TITLE_SUB_TOP : TITLE_TOP) + dy;
- self.titleViewOrDefault.frameWidth = self.frameWidth - self.innerContentMarginLeft - self.innerContentMarginRight;
-
- self.subtitleViewOrDefault.frameX = self.titleViewOrDefault.frameX;
- self.subtitleViewOrDefault.frameY = SUBTITLE_TOP + dy;
- self.subtitleViewOrDefault.frameWidth = self.titleViewOrDefault.frameWidth;
-
- self.leftAccessoryView.frameX = self.leftAccessoryHorizontalMargin;
- self.leftAccessoryView.frameY = self.leftAccessoryVerticalMargin + dy;
-
- self.rightAccessoryView.frameX = self.frameWidth - self.rightAccessoryHorizontalMargin - self.rightAccessoryView.frameWidth;
- self.rightAccessoryView.frameY = self.rightAccessoryVerticalMargin + dy;
-
- if (self.contentView) {
- self.contentView.frameX = self.innerContentMarginLeft;
- self.contentView.frameY = self.contentViewInset.top + dy;
- }
-}
-
-#pragma mark - Accessibility
-
-- (NSInteger)accessibilityElementCount {
- return (!!self.leftAccessoryView + !!self.titleViewOrDefault +
- !!self.subtitleViewOrDefault + !!self.rightAccessoryView);
-}
-
-- (id)accessibilityElementAtIndex:(NSInteger)index {
- if (index == 0) {
- return self.leftAccessoryView ? self.leftAccessoryView : self.titleViewOrDefault;
- }
- if (index == 1) {
- return self.leftAccessoryView ? self.titleViewOrDefault : self.subtitleViewOrDefault;
- }
- if (index == 2) {
- return self.leftAccessoryView ? self.subtitleViewOrDefault : self.rightAccessoryView;
- }
- if (index == 3) {
- return self.leftAccessoryView ? self.rightAccessoryView : nil;
- }
- return nil;
-}
-
-- (NSInteger)indexOfAccessibilityElement:(id)element {
- if (element == nil) return NSNotFound;
- if (element == self.leftAccessoryView) return 0;
- if (element == self.titleViewOrDefault) {
- return self.leftAccessoryView ? 1 : 0;
- }
- if (element == self.subtitleViewOrDefault) {
- return self.leftAccessoryView ? 2 : 1;
- }
- if (element == self.rightAccessoryView) {
- return self.leftAccessoryView ? 3 : 2;
- }
- return NSNotFound;
-}
-
-@end
-
-// import this known "private API" from SMCalloutBackgroundView
-@interface MGLSMCalloutBackgroundView (EmbeddedImages)
-+ (UIImage *)embeddedImageNamed:(NSString *)name;
-@end
-
-//
-// Callout Background View.
-//
-
-@interface MGLSMCalloutMaskedBackgroundView ()
-@property (nonatomic, strong) UIView *containerView, *containerBorderView, *arrowView;
-@property (nonatomic, strong) UIImageView *arrowImageView, *arrowHighlightedImageView, *arrowBorderView;
-@end
-
-static UIImage *blackArrowImage = nil, *whiteArrowImage = nil, *grayArrowImage = nil;
-
-@implementation MGLSMCalloutMaskedBackgroundView
-
-- (id)initWithFrame:(CGRect)frame {
- if (self = [super initWithFrame:frame]) {
-
- // Here we're mimicking the very particular (and odd) structure of the system callout view.
- // The hierarchy and view/layer values were discovered by inspecting map kit using Reveal.app
-
- self.containerView = [UIView new];
- self.containerView.backgroundColor = [UIColor whiteColor];
- self.containerView.alpha = 0.96;
- self.containerView.layer.cornerRadius = 8;
- self.containerView.layer.shadowRadius = 30;
- self.containerView.layer.shadowOpacity = 0.1;
-
- self.containerBorderView = [UIView new];
- self.containerBorderView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
- self.containerBorderView.layer.borderWidth = 0.5;
- self.containerBorderView.layer.cornerRadius = 8.5;
-
- if (!blackArrowImage) {
- blackArrowImage = [MGLSMCalloutBackgroundView embeddedImageNamed:@"CalloutArrow"];
- whiteArrowImage = [self image:blackArrowImage withColor:[UIColor whiteColor]];
- grayArrowImage = [self image:blackArrowImage withColor:[UIColor colorWithWhite:0.85 alpha:1]];
- }
-
- self.anchorHeight = 13;
- self.anchorMargin = 27;
-
- self.arrowView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, blackArrowImage.size.width, blackArrowImage.size.height)];
- self.arrowView.alpha = 0.96;
- self.arrowImageView = [[UIImageView alloc] initWithImage:whiteArrowImage];
- self.arrowHighlightedImageView = [[UIImageView alloc] initWithImage:grayArrowImage];
- self.arrowHighlightedImageView.hidden = YES;
- self.arrowBorderView = [[UIImageView alloc] initWithImage:blackArrowImage];
- self.arrowBorderView.alpha = 0.1;
- self.arrowBorderView.frameY = 0.5;
-
- [self addSubview:self.containerView];
- [self.containerView addSubview:self.containerBorderView];
- [self addSubview:self.arrowView];
- [self.arrowView addSubview:self.arrowBorderView];
- [self.arrowView addSubview:self.arrowImageView];
- [self.arrowView addSubview:self.arrowHighlightedImageView];
- }
- return self;
-}
-
-// Make sure we relayout our images when our arrow point changes!
-- (void)setArrowPoint:(CGPoint)arrowPoint {
- [super setArrowPoint:arrowPoint];
- [self setNeedsLayout];
-}
-
-- (void)setHighlighted:(BOOL)highlighted {
- [super setHighlighted:highlighted];
- self.containerView.backgroundColor = highlighted ? [UIColor colorWithWhite:0.85 alpha:1] : [UIColor whiteColor];
- self.arrowImageView.hidden = highlighted;
- self.arrowHighlightedImageView.hidden = !highlighted;
-}
-
-- (UIImage *)image:(UIImage *)image withColor:(UIColor *)color {
-
- UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
- CGRect imageRect = (CGRect){.size=image.size};
- CGContextRef c = UIGraphicsGetCurrentContext();
- CGContextTranslateCTM(c, 0, image.size.height);
- CGContextScaleCTM(c, 1, -1);
- CGContextClipToMask(c, imageRect, image.CGImage);
- [color setFill];
- CGContextFillRect(c, imageRect);
- UIImage *whiteImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return whiteImage;
-}
-
-- (void)layoutSubviews {
-
- BOOL pointingUp = self.arrowPoint.y < self.frameHeight/2;
-
- // if we're pointing up, we'll need to push almost everything down a bit
- CGFloat dy = pointingUp ? TOP_ANCHOR_MARGIN : 0;
-
- self.containerView.frame = CGRectMake(0, dy, self.frameWidth, self.frameHeight - self.arrowView.frameHeight + 0.5);
- self.containerBorderView.frame = CGRectInset(self.containerView.bounds, -0.5, -0.5);
-
- self.arrowView.frameX = roundf(self.arrowPoint.x - self.arrowView.frameWidth / 2);
-
- if (pointingUp) {
- self.arrowView.frameY = 1;
- self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
- }
- else {
- self.arrowView.frameY = self.containerView.frameHeight - 0.5;
- self.arrowView.transform = CGAffineTransformIdentity;
- }
-}
-
-- (CALayer *)contentMask {
-
- UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
-
- [self.layer renderInContext:UIGraphicsGetCurrentContext()];
-
- UIImage *maskImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- CALayer *layer = [CALayer layer];
- layer.frame = self.bounds;
- layer.contents = (id)maskImage.CGImage;
- return layer;
-}
-
-- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
-
- // Only interested in providing a custom pointInside for touches.
- if (event.type != UIEventTypeTouches) {
- return [super pointInside:point withEvent:event];
- }
-
- NSArray *views = @[self.containerView, self.arrowView];
- for (UIView *view in views) {
- CGPoint viewPoint = [self convertPoint:point toView:view];
- if (CGRectContainsPoint(view.bounds, viewPoint)) {
- return YES;
- }
- }
-
- return NO;
-}
-
-@end
-
-@implementation MGLSMCalloutBackgroundView
-
-+ (NSData *)dataWithBase64EncodedString:(NSString *)string {
- //
- // NSData+Base64.m
- //
- // Version 1.0.2
- //
- // Created by Nick Lockwood on 12/01/2012.
- // Copyright (C) 2012 Charcoal Design
- //
- // Distributed under the permissive zlib License
- // Get the latest version from here:
- //
- // https://github.com/nicklockwood/Base64
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source distribution.
- //
- const char lookup[] = {
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
- 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
- 99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99
- };
-
- NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
- long long inputLength = [inputData length];
- const unsigned char *inputBytes = [inputData bytes];
-
- long long maxOutputLength = (inputLength / 4 + 1) * 3;
- NSMutableData *outputData = [NSMutableData dataWithLength:(NSUInteger)maxOutputLength];
- unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
-
- int accumulator = 0;
- long long outputLength = 0;
- unsigned char accumulated[] = {0, 0, 0, 0};
- for (long long i = 0; i < inputLength; i++) {
- unsigned char decoded = lookup[inputBytes[i] & 0x7F];
- if (decoded != 99) {
- accumulated[accumulator] = decoded;
- if (accumulator == 3) {
- outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4);
- outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2);
- outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3];
- }
- accumulator = (accumulator + 1) % 4;
- }
- }
-
- //handle left-over data
- if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4);
- if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2);
- if (accumulator > 2) outputLength++;
-
- //truncate data to match actual output length
- outputData.length = (NSUInteger)outputLength;
- return outputLength? outputData: nil;
-}
-
-+ (UIImage *)embeddedImageNamed:(NSString *)name {
- CGFloat screenScale = [UIScreen mainScreen].scale;
- if (screenScale > 1.0) {
- name = [name stringByAppendingString:@"_2x"];
- screenScale = 2.0;
- }
-
- SEL selector = NSSelectorFromString(name);
-
- if (![(id)self respondsToSelector:selector]) {
- NSLog(@"Could not find an embedded image. Ensure that you've added a class-level method named +%@", name);
- return nil;
- }
-
- // We need to hush the compiler here - but we know what we're doing!
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- NSString *base64String = [(id)self performSelector:selector];
- #pragma clang diagnostic pop
-
- UIImage *rawImage = [UIImage imageWithData:[self dataWithBase64EncodedString:base64String]];
- return [UIImage imageWithCGImage:rawImage.CGImage scale:screenScale orientation:UIImageOrientationUp];
-}
-
-+ (NSString *)CalloutArrow { return @"iVBORw0KGgoAAAANSUhEUgAAACcAAAANCAYAAAAqlHdlAAAAHGlET1QAAAACAAAAAAAAAAcAAAAoAAAABwAAAAYAAADJEgYpIwAAAJVJREFUOBFiYIAAdn5+fkFOTkE5Dg5eW05O3lJOTr6zQPyfDhhoD28pxF5BOZA7gE5ih7oLN8XJyR8MdNwrGjkQaC5/MG7biZDh4OBXBDruLpUdeBdkLhHWE1bCzs6nAnTcUyo58DnIPMK2kqAC6DALIP5JoQNB+i1IsJZ4pcBEm0iJ40D6ibeNDJVAx00k04ETSbUOAAAA//+SwicfAAAAe0lEQVRjYCAdMHNy8u7l5OT7Tzzm3Qu0hpl0q8jQwcPDIwp02B0iHXeHl5dXhAxryNfCzc2tC3TcJwIO/ARSR74tFOjk4uL1BzruHw4H/gPJU2A85Vq5uPjTgY77g+bAPyBxyk2nggkcHPxOnJz8B4AOfAGiQXwqGMsAACGK1kPPMHNBAAAAAElFTkSuQmCC"; }
-
-+ (NSString *)CalloutArrow_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAE4AAAAaCAYAAAAZtWr8AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAAA0AAAAoAAAADQAAAA0AAAFMRh0LGwAAARhJREFUWAnclbENwjAQRZ0mih2fDYgsQEVDxQZMgKjpWYAJkBANI8AGDIEoM0WkzBDRAf8klB44g0OkU1zE3/+9RIpS7VVY730/y/woTWlsjJ9iPcN9pbXfY85auyvm/qcDNmb0e2Z+sk/ZBTthN0oVttX12mJIWeaWEFf+kbySmZQa0msu3nzaGJprTXV3BVLNDG/if7bNOTeAvFP35NGJu39GL7Abb27bFXncVQBZLgJf3jp+ebSWIxZMgrxdvPJoJ4gqHpXgV36ITR46HUGaiNMKB6YQd4lI3gV8qTBjmDhrbQFxVQTyKu4ShjJQap7nE4hrfiiv4Q6B8MLGat1bQNztB/JwZm8Rli5wujFu821xfGZgLPUAAAD//4wvm4gAAAD7SURBVOWXMQ6CMBiFgaFpi6VyBEedXJy4hMQTeBSvRDgJEySegI3EQWOivkZnqUB/k0LyL7R9L++D9G+DwP0TCZGUqCdRlYgUuY9F4JCmqQa0hgBcY7wIItFZMLZYS5l0ruAZbXhs6BIROgmhcoB7OIAHTZUTRqG3wp9xmhqc0aRPQu8YAlwxIbwCEUL6GH9wfDcLXY2HpyvvmkHf9+BcrwCuHQGvNRp9Pl6OY0PPAO42AB7WqMxLKLahpFR7gLv/AA9zPe+gtvAMCIC7WMC7CqEPtrqzmBfHyy3A1V/g1Th27GYBY0BIxrk6Ap65254/VZp30GID9JwteQEZrVMWXqGn8gAAAABJRU5ErkJggg=="; }
-
-@end
-
-//
-// Our UIView frame helpers implementation
-//
-
-@implementation UIView (SMFrameAdditions)
-
-- (CGPoint)frameOrigin { return self.frame.origin; }
-- (void)setFrameOrigin:(CGPoint)origin { self.frame = (CGRect){ .origin=origin, .size=self.frame.size }; }
-
-- (CGFloat)frameX { return self.frame.origin.x; }
-- (void)setFrameX:(CGFloat)x { self.frame = (CGRect){ .origin.x=x, .origin.y=self.frame.origin.y, .size=self.frame.size }; }
-
-- (CGFloat)frameY { return self.frame.origin.y; }
-- (void)setFrameY:(CGFloat)y { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=y, .size=self.frame.size }; }
-
-- (CGSize)frameSize { return self.frame.size; }
-- (void)setFrameSize:(CGSize)size { self.frame = (CGRect){ .origin=self.frame.origin, .size=size }; }
-
-- (CGFloat)frameWidth { return self.frame.size.width; }
-- (void)setFrameWidth:(CGFloat)width { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=width, .size.height=self.frame.size.height }; }
-
-- (CGFloat)frameHeight { return self.frame.size.height; }
-- (void)setFrameHeight:(CGFloat)height { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=height }; }
-
-- (CGFloat)frameLeft { return self.frame.origin.x; }
-- (void)setFrameLeft:(CGFloat)left { self.frame = (CGRect){ .origin.x=left, .origin.y=self.frame.origin.y, .size.width=fmaxf(self.frame.origin.x+self.frame.size.width-left,0), .size.height=self.frame.size.height }; }
-
-- (CGFloat)frameTop { return self.frame.origin.y; }
-- (void)setFrameTop:(CGFloat)top { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=top, .size.width=self.frame.size.width, .size.height=fmaxf(self.frame.origin.y+self.frame.size.height-top,0) }; }
-
-- (CGFloat)frameRight { return self.frame.origin.x + self.frame.size.width; }
-- (void)setFrameRight:(CGFloat)right { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=fmaxf(right-self.frame.origin.x,0), .size.height=self.frame.size.height }; }
-
-- (CGFloat)frameBottom { return self.frame.origin.y + self.frame.size.height; }
-- (void)setFrameBottom:(CGFloat)bottom { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=fmaxf(bottom-self.frame.origin.y,0) }; }
-
-@end