From 4cb0b99a3c21526f3c034fe49d95044f64bab540 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Thu, 12 Feb 2015 11:36:34 -0500 Subject: [PATCH 1/6] Added a way to add custom margin to the contentView using UIEdgeInsets. (Default properties are overridden if contentViewMargin is set) --- SMCalloutView.h | 3 +++ SMCalloutView.m | 31 +++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/SMCalloutView.h b/SMCalloutView.h index 0ff0b31..4844119 100755 --- a/SMCalloutView.h +++ b/SMCalloutView.h @@ -54,6 +54,9 @@ extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView; // Custom "content" view that can be any width/height. If this is set, title/subtitle/titleView/subtitleView are all ignored. @property (nonatomic, retain) UIView *contentView; +// Custom content view margin +@property (nonatomic, assign) UIEdgeInsets contentViewMargin; + // 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; diff --git a/SMCalloutView.m b/SMCalloutView.m index 858dabe..728aeda 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -25,7 +25,7 @@ @interface UIView (SMFrameAdditions) #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 CONTENT_VIEW_MARGIN 12 // margin around content view when present +#define CONTENT_VIEW_MARGIN 12 // margin around content view when present, can be overridden with contentViewMargin #define ANCHOR_MARGIN 27 // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right #define ANCHOR_HEIGHT 13 // effective height of the anchor #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. @@ -38,6 +38,7 @@ @interface SMCalloutView () @property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel; @property (nonatomic, assign) SMCalloutArrowDirection currentArrowDirection; @property (nonatomic, assign) BOOL popupCancelled; +@property (nonatomic, assign) BOOL useCustomContentViewMargin; @end @implementation SMCalloutView @@ -62,6 +63,7 @@ - (id)initWithFrame:(CGRect)frame { self.dismissAnimation = SMCalloutAnimationFade; self.backgroundColor = [UIColor clearColor]; self.containerView = [UIButton new]; + self.useCustomContentViewMargin = false; [self.containerView addTarget:self action:@selector(highlightIfNecessary) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside]; [self.containerView addTarget:self action:@selector(unhighlightIfNecessary) forControlEvents:UIControlEventTouchDragOutside | UIControlEventTouchCancel | UIControlEventTouchUpOutside | UIControlEventTouchUpInside]; @@ -179,6 +181,8 @@ - (CGFloat)rightAccessoryHorizontalMargin { - (CGFloat)innerContentMarginLeft { if (self.leftAccessoryView) return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.$width + TITLE_HMARGIN; + else if(self.useCustomContentViewMargin) + return self.contentViewMargin.left; else return TITLE_HMARGIN; } @@ -186,6 +190,8 @@ - (CGFloat)innerContentMarginLeft { - (CGFloat)innerContentMarginRight { if (self.rightAccessoryView) return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.$width + TITLE_HMARGIN; + else if(self.useCustomContentViewMargin) + return self.contentViewMargin.right; else return TITLE_HMARGIN; } @@ -195,14 +201,26 @@ - (CGFloat)calloutHeight { } - (CGFloat)calloutContainerHeight { - if (self.contentView) - return self.contentView.$height + CONTENT_VIEW_MARGIN * 2; + if (self.contentView) { + + if(self.useCustomContentViewMargin) { + return self.contentView.$height + self.contentViewMargin.bottom + self.contentViewMargin.top; + } + else { + return self.contentView.$height + CONTENT_VIEW_MARGIN * 2; + } + } else if (self.subtitleView || self.subtitle.length > 0) return CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT; else return CALLOUT_DEFAULT_CONTAINER_HEIGHT; } +- (void) setContentViewMargin:(UIEdgeInsets)contentViewMargin { + self.useCustomContentViewMargin = true; + _contentViewMargin = contentViewMargin; +} + - (CGSize)sizeThatFits:(CGSize)size { // calculate how much non-negotiable space we need to reserve for margin and accessories @@ -535,7 +553,12 @@ - (void)layoutSubviews { if (self.contentView) { self.contentView.$x = self.innerContentMarginLeft; - self.contentView.$y = CONTENT_VIEW_MARGIN + dy; + if(self.useCustomContentViewMargin) { + self.contentView.$y = self.contentViewMargin.top + dy; + } + else { + self.contentView.$y = CONTENT_VIEW_MARGIN + dy; + } } } From 0b65a72f698feaba2b75c0f4635fa38849d89923 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Thu, 12 Feb 2015 13:37:44 -0500 Subject: [PATCH 2/6] Added anchor height property to the CalloutBackgroundView for custom background view with custom arrow. --- SMCalloutView.h | 1 + SMCalloutView.m | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/SMCalloutView.h b/SMCalloutView.h index 4844119..059626d 100755 --- a/SMCalloutView.h +++ b/SMCalloutView.h @@ -91,6 +91,7 @@ extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView; @property (nonatomic, assign) CGPoint arrowPoint; // indicates where the tip of the arrow should be drawn, as a pixel offset @property (nonatomic, assign) BOOL highlighted; // will be set by the callout when the callout is in a highlighted state @property (nonatomic, assign) CALayer *contentMask; // returns an optional layer whose contents should mask the callout view's contents (not honored by SMClassicCalloutView) +@property (nonatomic, assign) CGFloat anchorHeight; //The arrow height, useful for custom background view with custom arrow @end // Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view. diff --git a/SMCalloutView.m b/SMCalloutView.m index 728aeda..982232c 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -27,7 +27,7 @@ @interface UIView (SMFrameAdditions) #define BETWEEN_ACCESSORIES_MARGIN 7 // margin between accessories when no title/subtitle is present #define CONTENT_VIEW_MARGIN 12 // margin around content view when present, can be overridden with contentViewMargin #define ANCHOR_MARGIN 27 // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right -#define ANCHOR_HEIGHT 13 // effective height of the anchor +#define DEFAULT_ANCHOR_HEIGHT 13 // effective height of the anchor, will be overriden by anchorHeight propertiy of the SMCalloutBackgroundView if asigned. #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 @@ -197,7 +197,13 @@ - (CGFloat)innerContentMarginRight { } - (CGFloat)calloutHeight { - return self.calloutContainerHeight + ANCHOR_HEIGHT; + if(self.backgroundView.anchorHeight) + { + return self.calloutContainerHeight + self.backgroundView.anchorHeight; + } + else { + return self.calloutContainerHeight + DEFAULT_ANCHOR_HEIGHT; + } } - (CGFloat)calloutContainerHeight { From 131089a1b4e2db9d539d9bb5c147c9cc3a2a7ec0 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Mon, 16 Feb 2015 09:42:28 -0500 Subject: [PATCH 3/6] Renamed contentViewMargin to contentViewInset to match Apple's other APIs. --- SMCalloutView.h | 2 +- SMCalloutView.m | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SMCalloutView.h b/SMCalloutView.h index 059626d..9ac9539 100755 --- a/SMCalloutView.h +++ b/SMCalloutView.h @@ -55,7 +55,7 @@ extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView; @property (nonatomic, retain) UIView *contentView; // Custom content view margin -@property (nonatomic, assign) UIEdgeInsets contentViewMargin; +@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; diff --git a/SMCalloutView.m b/SMCalloutView.m index 982232c..656c163 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -25,7 +25,7 @@ @interface UIView (SMFrameAdditions) #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 CONTENT_VIEW_MARGIN 12 // margin around content view when present, can be overridden with contentViewMargin +#define CONTENT_VIEW_MARGIN 12 // margin around content view when present, can be overridden with contentViewInset #define ANCHOR_MARGIN 27 // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right #define DEFAULT_ANCHOR_HEIGHT 13 // effective height of the anchor, will be overriden by anchorHeight propertiy of the SMCalloutBackgroundView if asigned. #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. @@ -182,7 +182,7 @@ - (CGFloat)innerContentMarginLeft { if (self.leftAccessoryView) return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.$width + TITLE_HMARGIN; else if(self.useCustomContentViewMargin) - return self.contentViewMargin.left; + return self.contentViewInset.left; else return TITLE_HMARGIN; } @@ -191,7 +191,7 @@ - (CGFloat)innerContentMarginRight { if (self.rightAccessoryView) return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.$width + TITLE_HMARGIN; else if(self.useCustomContentViewMargin) - return self.contentViewMargin.right; + return self.contentViewInset.right; else return TITLE_HMARGIN; } @@ -210,7 +210,7 @@ - (CGFloat)calloutContainerHeight { if (self.contentView) { if(self.useCustomContentViewMargin) { - return self.contentView.$height + self.contentViewMargin.bottom + self.contentViewMargin.top; + return self.contentView.$height + self.contentViewInset.bottom + self.contentViewInset.top; } else { return self.contentView.$height + CONTENT_VIEW_MARGIN * 2; @@ -222,9 +222,9 @@ - (CGFloat)calloutContainerHeight { return CALLOUT_DEFAULT_CONTAINER_HEIGHT; } -- (void) setContentViewMargin:(UIEdgeInsets)contentViewMargin { +- (void) setContentViewInset:(UIEdgeInsets)contentViewInset { self.useCustomContentViewMargin = true; - _contentViewMargin = contentViewMargin; + _contentViewInset = contentViewInset; } - (CGSize)sizeThatFits:(CGSize)size { @@ -560,7 +560,7 @@ - (void)layoutSubviews { if (self.contentView) { self.contentView.$x = self.innerContentMarginLeft; if(self.useCustomContentViewMargin) { - self.contentView.$y = self.contentViewMargin.top + dy; + self.contentView.$y = self.contentViewInset.top + dy; } else { self.contentView.$y = CONTENT_VIEW_MARGIN + dy; From 96206ec19d6696d8a6d438b5908f58bbf8a4e383 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Mon, 16 Feb 2015 11:33:07 -0500 Subject: [PATCH 4/6] contentViewInset now have a default value at init. --- SMCalloutView.m | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/SMCalloutView.m b/SMCalloutView.m index 656c163..06c629d 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -38,7 +38,6 @@ @interface SMCalloutView () @property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel; @property (nonatomic, assign) SMCalloutArrowDirection currentArrowDirection; @property (nonatomic, assign) BOOL popupCancelled; -@property (nonatomic, assign) BOOL useCustomContentViewMargin; @end @implementation SMCalloutView @@ -63,7 +62,7 @@ - (id)initWithFrame:(CGRect)frame { self.dismissAnimation = SMCalloutAnimationFade; self.backgroundColor = [UIColor clearColor]; self.containerView = [UIButton new]; - self.useCustomContentViewMargin = false; + 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]; @@ -181,19 +180,15 @@ - (CGFloat)rightAccessoryHorizontalMargin { - (CGFloat)innerContentMarginLeft { if (self.leftAccessoryView) return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.$width + TITLE_HMARGIN; - else if(self.useCustomContentViewMargin) - return self.contentViewInset.left; else - return TITLE_HMARGIN; + return self.contentViewInset.left; } - (CGFloat)innerContentMarginRight { if (self.rightAccessoryView) return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.$width + TITLE_HMARGIN; - else if(self.useCustomContentViewMargin) - return self.contentViewInset.right; else - return TITLE_HMARGIN; + return self.contentViewInset.right; } - (CGFloat)calloutHeight { @@ -208,13 +203,7 @@ - (CGFloat)calloutHeight { - (CGFloat)calloutContainerHeight { if (self.contentView) { - - if(self.useCustomContentViewMargin) { - return self.contentView.$height + self.contentViewInset.bottom + self.contentViewInset.top; - } - else { - return self.contentView.$height + CONTENT_VIEW_MARGIN * 2; - } + return self.contentView.$height + self.contentViewInset.bottom + self.contentViewInset.top; } else if (self.subtitleView || self.subtitle.length > 0) return CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT; @@ -222,11 +211,6 @@ - (CGFloat)calloutContainerHeight { return CALLOUT_DEFAULT_CONTAINER_HEIGHT; } -- (void) setContentViewInset:(UIEdgeInsets)contentViewInset { - self.useCustomContentViewMargin = true; - _contentViewInset = contentViewInset; -} - - (CGSize)sizeThatFits:(CGSize)size { // calculate how much non-negotiable space we need to reserve for margin and accessories @@ -559,12 +543,7 @@ - (void)layoutSubviews { if (self.contentView) { self.contentView.$x = self.innerContentMarginLeft; - if(self.useCustomContentViewMargin) { - self.contentView.$y = self.contentViewInset.top + dy; - } - else { - self.contentView.$y = CONTENT_VIEW_MARGIN + dy; - } + self.contentView.$y = self.contentViewInset.top + dy; } } From b0e8b9cc55db2e18c0f1ca4409f9b47e070f14d6 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Mon, 16 Feb 2015 11:38:47 -0500 Subject: [PATCH 5/6] anchorHeight is now a NSNumber so we can use a custom height of 0 for the arrow. --- SMCalloutView.h | 2 +- SMCalloutView.m | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/SMCalloutView.h b/SMCalloutView.h index 9ac9539..a5d9d90 100755 --- a/SMCalloutView.h +++ b/SMCalloutView.h @@ -91,7 +91,7 @@ extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView; @property (nonatomic, assign) CGPoint arrowPoint; // indicates where the tip of the arrow should be drawn, as a pixel offset @property (nonatomic, assign) BOOL highlighted; // will be set by the callout when the callout is in a highlighted state @property (nonatomic, assign) CALayer *contentMask; // returns an optional layer whose contents should mask the callout view's contents (not honored by SMClassicCalloutView) -@property (nonatomic, assign) CGFloat anchorHeight; //The arrow height, useful for custom background view with custom arrow +@property (nonatomic, strong) NSNumber *anchorHeight; //The arrow height, useful for custom background view with custom arrow @end // Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view. diff --git a/SMCalloutView.m b/SMCalloutView.m index 06c629d..afab03e 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -193,12 +193,9 @@ - (CGFloat)innerContentMarginRight { - (CGFloat)calloutHeight { if(self.backgroundView.anchorHeight) - { - return self.calloutContainerHeight + self.backgroundView.anchorHeight; - } - else { + return self.calloutContainerHeight + self.backgroundView.anchorHeight.floatValue; + else return self.calloutContainerHeight + DEFAULT_ANCHOR_HEIGHT; - } } - (CGFloat)calloutContainerHeight { From 9c14b9fa6a5b99908a432cad694a9cab5f67dff0 Mon Sep 17 00:00:00 2001 From: Olivier Rousseau Date: Mon, 16 Feb 2015 11:40:48 -0500 Subject: [PATCH 6/6] Added a anchorMargin property so we can set this attribute if needed for custom SMCalloutBackgroundView. Default is still 27. --- SMCalloutView.h | 1 + SMCalloutView.m | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SMCalloutView.h b/SMCalloutView.h index a5d9d90..d11515a 100755 --- a/SMCalloutView.h +++ b/SMCalloutView.h @@ -92,6 +92,7 @@ extern NSTimeInterval const kSMCalloutViewRepositionDelayForUIScrollView; @property (nonatomic, assign) BOOL highlighted; // will be set by the callout when the callout is in a highlighted state @property (nonatomic, assign) CALayer *contentMask; // returns an optional layer whose contents should mask the callout view's contents (not honored by SMClassicCalloutView) @property (nonatomic, strong) NSNumber *anchorHeight; //The arrow height, useful for custom background view with custom arrow +@property (nonatomic, assign) CGFloat anchorMargin; // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right @end // Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view. diff --git a/SMCalloutView.m b/SMCalloutView.m index afab03e..12f5db2 100755 --- a/SMCalloutView.m +++ b/SMCalloutView.m @@ -25,8 +25,6 @@ @interface UIView (SMFrameAdditions) #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 CONTENT_VIEW_MARGIN 12 // margin around content view when present, can be overridden with contentViewInset -#define ANCHOR_MARGIN 27 // the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right #define DEFAULT_ANCHOR_HEIGHT 13 // effective height of the anchor, will be overriden by anchorHeight propertiy of the SMCalloutBackgroundView if asigned. #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 @@ -329,8 +327,8 @@ - (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIV calloutX = constrainedRect.origin.x+constrainedRect.size.width-self.$width; // what's the farthest to the left and right that we could point to, given our background image constraints? - CGFloat minPointX = calloutX + ANCHOR_MARGIN; - CGFloat maxPointX = calloutX + self.$width - ANCHOR_MARGIN; + CGFloat minPointX = calloutX + self.backgroundView.anchorMargin; + CGFloat maxPointX = calloutX + self.$width - self.backgroundView.anchorMargin; // we may need to scoot over to the left or right to point at the correct spot CGFloat adjustX = 0; @@ -588,6 +586,8 @@ - (id)initWithFrame:(CGRect)frame { grayArrowImage = [self image:blackArrowImage withColor:[UIColor colorWithWhite:0.85 alpha:1]]; } + 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];