diff options
Diffstat (limited to 'platform/ios/vendor/SMCalloutView')
-rwxr-xr-x | platform/ios/vendor/SMCalloutView/SMCalloutView.h | 217 | ||||
-rwxr-xr-x | platform/ios/vendor/SMCalloutView/SMCalloutView.m | 939 |
2 files changed, 0 insertions, 1156 deletions
diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.h b/platform/ios/vendor/SMCalloutView/SMCalloutView.h deleted file mode 100755 index 5bb73d4c84..0000000000 --- a/platform/ios/vendor/SMCalloutView/SMCalloutView.h +++ /dev/null @@ -1,217 +0,0 @@ -#import <UIKit/UIKit.h> -#import <QuartzCore/QuartzCore.h> - -/* - -SMCalloutView -------------- -Created by Nick Farina (nfarina@gmail.com) -Version 2.1.5 - -*/ - -/// options for which directions the callout is allowed to "point" in. -typedef NS_OPTIONS(NSUInteger, MGLSMCalloutArrowDirection) { - MGLSMCalloutArrowDirectionUp = 1 << 0, - MGLSMCalloutArrowDirectionDown = 1 << 1, - MGLSMCalloutArrowDirectionAny = MGLSMCalloutArrowDirectionUp | MGLSMCalloutArrowDirectionDown -}; - -/// options for the callout present/dismiss animation -typedef NS_ENUM(NSInteger, MGLSMCalloutAnimation) { - /// the "bounce" animation we all know and love from @c UIAlertView - MGLSMCalloutAnimationBounce, - /// a simple fade in or out - MGLSMCalloutAnimationFade, - /// grow or shrink linearly, like in the iPad Calendar app - MGLSMCalloutAnimationStretch -}; - -NS_ASSUME_NONNULL_BEGIN - -/// when delaying our popup in order to scroll content into view, you can use this amount to match the -/// animation duration of UIScrollView when using @c -setContentOffset:animated. -extern NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView; - -@protocol MGLSMCalloutViewDelegate; -@class MGLSMCalloutBackgroundView; - -// -// Callout view. -// - -// iOS 10+ expects CAAnimationDelegate to be set explicitly. -#if __IPHONE_OS_VERSION_MAX_ALLOWED < 100000 -@interface MGLSMCalloutView : UIView -#else -@interface MGLSMCalloutView : UIView <CAAnimationDelegate> -#endif - -@property (nonatomic, weak, nullable) id<MGLSMCalloutViewDelegate> delegate; -/// title/titleView relationship mimics UINavigationBar. -@property (nonatomic, copy, nullable) NSString *title; -@property (nonatomic, copy, nullable) NSString *subtitle; - -/// Left accessory view for the call out -@property (nonatomic, strong, nullable) UIView *leftAccessoryView; -/// Right accessoty view for the call out -@property (nonatomic, strong, nullable) UIView *rightAccessoryView; -/// Default @c SMCalloutArrowDirectionDown -@property (nonatomic, assign) MGLSMCalloutArrowDirection permittedArrowDirection; -/// The current arrow direction -@property (nonatomic, readonly) MGLSMCalloutArrowDirection currentArrowDirection; -/// if the @c UIView you're constraining to has portions that are overlapped by nav bar, tab bar, etc. you'll need to tell us those insets. -@property (nonatomic, assign) UIEdgeInsets constrainedInsets; -/// default is @c SMCalloutMaskedBackgroundView, or @c SMCalloutDrawnBackgroundView when using @c SMClassicCalloutView -@property (nonatomic, strong) MGLSMCalloutBackgroundView *backgroundView; - -/** - @brief Custom title view. - - @disucssion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on titleView/subtitleView if defined, so your view - may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized. - - @warning If this is set, the respective @c title property will be ignored. - */ -@property (nonatomic, strong, nullable) UIView *titleView; - -/** - @brief Custom subtitle view. - - @discussion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on subtitleView if defined, so your view - may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized. - - @warning If this is set, the respective @c subtitle property will be ignored. - */ -@property (nonatomic, strong, nullable) UIView *subtitleView; - -/// Custom "content" view that can be any width/height. If this is set, title/subtitle/titleView/subtitleView are all ignored. -@property (nonatomic, retain, nullable) UIView *contentView; - -/// Custom content view margin -@property (nonatomic, assign) UIEdgeInsets contentViewInset; - -/// calloutOffset is the offset in screen points from the top-middle of the target view, where the anchor of the callout should be shown. -@property (nonatomic, assign) CGPoint calloutOffset; - -/// default SMCalloutAnimationBounce, SMCalloutAnimationFade respectively -@property (nonatomic, assign) MGLSMCalloutAnimation presentAnimation, dismissAnimation; - -/// Returns a new instance of SMCalloutView if running on iOS 7 or better, otherwise a new instance of SMClassicCalloutView if available. -+ (MGLSMCalloutView *)platformCalloutView; - -/** - @brief Presents a callout view by adding it to "inView" and pointing at the given rect of inView's bounds. - - @discussion Constrains the callout to the bounds of the given view. Optionally scrolls the given rect into view (plus margins) - if @c -delegate is set and responds to @c -delayForRepositionWithSize. - - @param rect @c CGRect to present the view from - @param view view to 'constrain' the @c constrainedView to - @param constrainedView @c UIView to be constrainted in @c view - @param animated @c BOOL if presentation should be animated - */ -- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated; - -/** - @brief Presents a callout view by adding it to "inView" and pointing at the given rect of inView's bounds. - - @discussion Constrains the callout to the rect (in the space of the given view). - - @param rect @c CGRect to present the view from - @param view view to 'constrain' the @c constrainedView to - @param constrainedRect Rect to constrain the callout to - @param animated @c BOOL if presentation should be animated - */ -- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated; - -/** - @brief Present a callout layer in the `layer` and pointing at the given rect of the `layer` bounds - - @discussion Same as the view-based presentation, but inserts the callout into a CALayer hierarchy instead. - @note Be aware that you'll have to direct your own touches to any accessory views, since CALayer doesn't relay touch events. - - @param rect @c CGRect to present the view from - @param layer layer to 'constrain' the @c constrainedLayer to - @param constrainedLayer @c CALayer to be constrained in @c layer - @param animated @c BOOL if presentation should be animated - */ -- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated; - -/** - Dismiss the callout view - - @param animated @c BOOL if dismissal should be animated - */ -- (void)dismissCalloutAnimated:(BOOL)animated; - -/// For subclassers. You can override this method to provide your own custom animation for presenting/dismissing the callout. -- (CAAnimation *)animationWithType:(MGLSMCalloutAnimation)type presenting:(BOOL)presenting; - -@end - -// -// Background view - default draws the iOS 7 system background style (translucent white with rounded arrow). -// - -/// Abstract base class -@interface MGLSMCalloutBackgroundView : UIView -/// indicates where the tip of the arrow should be drawn, as a pixel offset -@property (nonatomic, assign) CGPoint arrowPoint; -/// will be set by the callout when the callout is in a highlighted state -@property (nonatomic, assign) BOOL highlighted; -/// returns an optional layer whose contents should mask the callout view's contents (not honored by @c SMClassicCalloutView ) -@property (nonatomic, assign) CALayer *contentMask; -/// height of the callout "arrow" -@property (nonatomic, assign) CGFloat anchorHeight; -/// the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right -@property (nonatomic, assign) CGFloat anchorMargin; -@end - -/// Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view. -/// Accessories are masked by the shape of the callout (including the arrow itself). -@interface MGLSMCalloutMaskedBackgroundView : MGLSMCalloutBackgroundView -@end - -// -// Delegate methods -// - -@protocol MGLSMCalloutViewDelegate <NSObject> -@optional - -/// Controls whether the callout "highlights" when pressed. default YES. You must also respond to @c -calloutViewClicked below. -/// Not honored by @c SMClassicCalloutView. -- (BOOL)calloutViewShouldHighlight:(MGLSMCalloutView *)calloutView; - -/// Called when the callout view is clicked. Not honored by @c SMClassicCalloutView. -- (void)calloutViewClicked:(MGLSMCalloutView *)calloutView; - -/** - Called when the callout view detects that it will be outside the constrained view when it appears, - or if the target rect was already outside the constrained view. You can implement this selector - to respond to this situation by repositioning your content first in order to make everything visible. - The @c CGSize passed is the calculated offset necessary to make everything visible (plus a nice margin). - It expects you to return the amount of time you need to reposition things so the popup can be delayed. - Typically you would return @c kSMCalloutViewRepositionDelayForUIScrollView if you're repositioning by calling @c [UIScrollView @c setContentOffset:animated:]. - - @param calloutView the @c SMCalloutView to reposition - @param offset caluclated offset necessary to make everything visible - @returns @c NSTimeInterval to delay the repositioning - */ -- (NSTimeInterval)calloutView:(MGLSMCalloutView *)calloutView delayForRepositionWithSize:(CGSize)offset; - -/// Called before the callout view appears on screen, or before the appearance animation will start. -- (void)calloutViewWillAppear:(MGLSMCalloutView *)calloutView; - -/// Called after the callout view appears on screen, or after the appearance animation is complete. -- (void)calloutViewDidAppear:(MGLSMCalloutView *)calloutView; - -/// Called before the callout view is removed from the screen, or before the disappearance animation is complete. -- (void)calloutViewWillDisappear:(MGLSMCalloutView *)calloutView; - -/// Called after the callout view is removed from the screen, or after the disappearance animation is complete. -- (void)calloutViewDidDisappear:(MGLSMCalloutView *)calloutView; - -NS_ASSUME_NONNULL_END -@end 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 |