Skip to content

Commit

Permalink
Included coach mark files with sample
Browse files Browse the repository at this point in the history
  • Loading branch information
ddoria921 committed Apr 5, 2014
1 parent 517d60b commit cbb9b31
Show file tree
Hide file tree
Showing 9 changed files with 817 additions and 0 deletions.
32 changes: 32 additions & 0 deletions Coach Marks/DDBubble.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// DDBubble.h
// Coach Marks
//
// Created by Darin Doria on 02/17/2014.
// Copyright (c) 2014 Darin Doria. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface DDBubble : UIView

typedef enum {
CRArrowPositionTop,
CRArrowPositionBottom,
CRArrowPositionRight,
CRArrowPositionLeft
} CRArrowPosition;

@property (nonatomic, assign) CRArrowPosition arrowPosition;
@property (nonatomic, strong) UIView *attachedView;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *bubbleText;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic) CGRect attachedFrame;
@property (nonatomic) BOOL bouncing;
@property (nonatomic) BOOL animationShouldStop;

-(id)initWithAttachedView:(UIView*)view title:(NSString*)title description:(NSString*)description arrowPosition:(CRArrowPosition)arrowPosition andColor:(UIColor*)color;
-(id)initWithFrame:(CGRect)frame title:(NSString*)title description:(NSString*)description arrowPosition:(CRArrowPosition)arrowPosition andColor:(UIColor*)color;
-(void)animate;
@end
231 changes: 231 additions & 0 deletions Coach Marks/DDBubble.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
//
// DDBubble.m
// Coach Marks
//
// Created by Darin Doria on 02/17/2014.
// Copyright (c) 2014 Darin Doria. All rights reserved.
//

#import "DDBubble.h"

#define ARROW_SPACE 6 // space between arrow and highlighted region
#define ARROW_SIZE 8
#define PADDING 8 // padding between text and border of bubble
#define RADIUS 6
#define TEXT_COLOR [UIColor blackColor]
#define TITLE_FONT_SIZE 14

@interface DDBubble ()
{
float _arrowOffset;
}

@end

@implementation DDBubble


#pragma mark - Initialization

-(id)initWithAttachedView:(UIView*)view title:(NSString*)title description:(NSString*)description arrowPosition:(CRArrowPosition)arrowPosition andColor:(UIColor*)color
{
return [self initWithFrame:view.frame title:title description:description arrowPosition:arrowPosition andColor:color];
}

-(id)initWithFrame:(CGRect)frame title:(NSString*)title description:(NSString*)description arrowPosition:(CRArrowPosition)arrowPosition andColor:(UIColor*)color
{
self = [super init];
if(self)
{
if(color!=nil)
self.color=color;
else
self.color=[UIColor whiteColor];

self.attachedFrame = frame;
self.title = title;
self.bubbleText = description;
self.arrowPosition = arrowPosition;
[self setBackgroundColor:[UIColor clearColor]];
}

// position bubble
[self setFrame:[self calculateFrame]];
[self fixFrameIfOutOfBounds];

// calculate and position text
float actualXPosition = [self offsets].width+PADDING*1.5;
float actualYPosition = [self offsets].height+PADDING*1.25;
float actualWidth = self.frame.size.width;
float actualHeight = TITLE_FONT_SIZE+3;

UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(actualXPosition, actualYPosition, actualWidth, actualHeight)];
[titleLabel setTextColor:TEXT_COLOR];
[titleLabel setAlpha:0.9];
[titleLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:TITLE_FONT_SIZE]];
[titleLabel setText:title];
[titleLabel setBackgroundColor:[UIColor clearColor]];
[self addSubview:titleLabel];


[self setNeedsDisplay];
return self;
}


#pragma mark - Positioning and Size

- (void)fixFrameIfOutOfBounds
{
/**
* Description:
*
* Check if bubble is going off the screen using the
* position and size. If it is, return YES.
*/

const float xBounds = 320;
// const float yBounds = 568;

float x = self.frame.origin.x;
float y = self.frame.origin.y;
float width = self.frame.size.width;
float height = self.frame.size.height;

float padding = 3;

// check for right most bound
if (x + width > xBounds) {
_arrowOffset = (x + width) - xBounds;
x = x - _arrowOffset;
}
// check for left most bound
else if (x < 0) {
_arrowOffset = x - padding;
x = x - _arrowOffset + padding;
}

[self setFrame:CGRectMake(x, y, width, height)];
}

-(CGRect)calculateFrame
{
//Calculation of the bubble position
float x = self.attachedFrame.origin.x;
float y = self.attachedFrame.origin.y;


if(self.arrowPosition==CRArrowPositionLeft||self.arrowPosition==CRArrowPositionRight)
{
y+=self.attachedFrame.size.height/2-[self size].height/2;
x+=(self.arrowPosition==CRArrowPositionLeft)? ARROW_SPACE+self.attachedFrame.size.width : -(ARROW_SPACE*2+[self size].width);

}else if(self.arrowPosition==CRArrowPositionTop||self.arrowPosition==CRArrowPositionBottom)
{
x+=self.attachedFrame.size.width/2-[self size].width/2;
y+=(self.arrowPosition==CRArrowPositionTop)? ARROW_SPACE+self.attachedFrame.size.height : -(ARROW_SPACE*2+[self size].height);
}

return CGRectMake(x, y, [self size].width+ARROW_SIZE, [self size].height+ARROW_SIZE);
}

-(CGSize)size
{
// Calcultation of the bubble size
// size of bubble title determined by the strings attributes
CGSize result = [_title sizeWithAttributes:@{
NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:TITLE_FONT_SIZE]

}];

return CGSizeMake(result.width + (PADDING*3), result.height + (PADDING*2.5));
}

-(CGSize)offsets
{
return CGSizeMake((self.arrowPosition==CRArrowPositionLeft)? ARROW_SIZE : 0, (self.arrowPosition==CRArrowPositionTop)? ARROW_SIZE : 0);
}



#pragma mark - Drawing and Animation

- (void)drawRect:(CGRect)rect
{

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);

CGPathRef clippath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake([self offsets].width,[self offsets].height, [self size].width, [self size].height) cornerRadius:RADIUS].CGPath;
CGContextAddPath(ctx, clippath);

CGContextSetFillColorWithColor(ctx, self.color.CGColor);

CGContextClosePath(ctx);
CGContextFillPath(ctx);

[self.color set];

// tip of arrow needs to be centered under highlighted region
// this center area is always arrow size divided by 2
float center = ARROW_SIZE/2;

// points used to draw arrow
// Wide Arrow --> x = center + - ArrowSize
// Skinny Arrow --> x = center + - center
// Normal Arrow -->
CGPoint startPoint = CGPointMake(center - ARROW_SIZE, ARROW_SIZE);
CGPoint midPoint = CGPointMake(center, 0);
CGPoint endPoint = CGPointMake(center + ARROW_SIZE, ARROW_SIZE);


UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addLineToPoint:endPoint];
[path addLineToPoint:midPoint];
[path addLineToPoint:startPoint];


if(self.arrowPosition==CRArrowPositionTop)
{
CGAffineTransform trans = CGAffineTransformMakeTranslation([self size].width/2-(ARROW_SIZE)/2+_arrowOffset, 0);
[path applyTransform:trans];
}else if(self.arrowPosition==CRArrowPositionBottom)
{
CGAffineTransform rot = CGAffineTransformMakeRotation(M_PI);
CGAffineTransform trans = CGAffineTransformMakeTranslation([self size].width/2+(ARROW_SIZE)/2+_arrowOffset, [self size].height+ARROW_SIZE);
[path applyTransform:rot];
[path applyTransform:trans];
}else if(self.arrowPosition==CRArrowPositionLeft)
{
CGAffineTransform rot = CGAffineTransformMakeRotation(M_PI*1.5);
CGAffineTransform trans = CGAffineTransformMakeTranslation(0, ([self size].height+ARROW_SIZE)/2);
[path applyTransform:rot];
[path applyTransform:trans];
}else if(self.arrowPosition==CRArrowPositionRight)
{
CGAffineTransform rot = CGAffineTransformMakeRotation(M_PI*0.5);
CGAffineTransform trans = CGAffineTransformMakeTranslation([self size].width+ARROW_SIZE, ([self size].height-ARROW_SIZE)/2);
[path applyTransform:rot];
[path applyTransform:trans];
}

[path closePath]; // Implicitly does a line between p4 and p1
[path fill]; // If you want it filled, or...
[path stroke]; // ...if you want to draw the outline.
CGContextRestoreGState(ctx);
}

- (void)animate
{
[UIView animateWithDuration:2.0f
delay:0.3
options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
animations:^ {
self.transform = CGAffineTransformMakeTranslation(0, -4);
}
completion:^(BOOL finished) {
}];
}

@end
17 changes: 17 additions & 0 deletions Coach Marks/DDCircleView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// DDCircleView.h
// CoachMarks
//
// Created by Darin Doria on 2/17/14.
// Copyright (c) 2014 Darin Doria. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface DDCircleView : UIView

@property BOOL animationShouldStop;

- (void)swipeInFrame:(CGRect)frame;

@end
112 changes: 112 additions & 0 deletions Coach Marks/DDCircleView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// DDCircleView.m
// CoachMarks
//
// Created by Darin Doria on 2/17/14.
// Copyright (c) 2014 Darin Doria. All rights reserved.
//

#import "DDCircleView.h"

@implementation DDCircleView

- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:CGRectMake(10, 0, 40, 40)];

if (self) {
self.backgroundColor = [UIColor clearColor];
CAShapeLayer *shapeLayer = (CAShapeLayer *) self.layer;
shapeLayer.path = ([UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 40, 40)].CGPath);
shapeLayer.fillColor = [UIColor colorWithWhite:1.000 alpha:1.00].CGColor;
shapeLayer.shadowRadius = 8.0;
shapeLayer.shadowOffset = CGSizeMake(0, 0);
shapeLayer.shadowColor = [UIColor colorWithRed:0.000 green:0.299 blue:0.715 alpha:1.000].CGColor;
shapeLayer.shadowOpacity = 1.0;
shapeLayer.shadowPath = ([UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 40, 40)].CGPath);

self.animationShouldStop = NO;
}

return self;
}

- (void)userTap:(UITapGestureRecognizer *)recognizer {
self.hidden = YES;
[self removeFromSuperview];
}

- (void)swipeInFrame:(CGRect)frame
{
[self centerYPositioninView:self inFrame:frame];
[self animateSwipeRight];
}

- (void)animateSwipeRight
{
if (!_animationShouldStop) {
self.transform = CGAffineTransformMakeScale(2, 2);
self.alpha = 0.0f;
[UIView animateKeyframesWithDuration:0.6 delay:0.3 options:0
animations:^{
// Fade In
self.transform = CGAffineTransformMakeScale(1, 1);
self.alpha = 1.0f;
}
completion:^(BOOL finished){
// End
[UIView animateWithDuration:1.0
animations:^{
// Slide Right
self.transform = CGAffineTransformMakeTranslation(260, 0);
// Fade Out
self.alpha = 0.0f;

}
completion:^(BOOL finished) {
// End
[UIView animateWithDuration:0.5
animations:^{
// Fade Out
//self.alpha = 0.0f;
}
completion:^(BOOL finished) {
// End
[self performSelector:@selector(animateSwipeRight)];
}];
}];
}];
}

}

- (void)centerYPositioninView:(UIView *)view inFrame:(CGRect)frame
{
CGFloat centerY = frame.origin.y + CGRectGetHeight(frame)/2;
CGFloat offsetY = CGRectGetHeight(view.frame)/2;

CGFloat newY = centerY - offsetY;
view.frame = CGRectMake(view.frame.origin.x, newY, 40, 40);
}

- (void)centerXPositioninView:(UIView *)view inFrame:(CGRect)frame
{
CGFloat centerX = frame.origin.x + CGRectGetWidth(frame)/2;
CGFloat offsetX = CGRectGetWidth(view.frame)/2;

CGFloat newX = centerX - offsetX;
view.frame = CGRectMake(newX, view.frame.origin.y, 40, 40);
}

- (void)centerInView:(UIView *)view inFrame:(CGRect)frame
{
[self centerYPositioninView:view inFrame:frame];
[self centerXPositioninView:view inFrame:frame];
}

+ (Class)layerClass
{
return [CAShapeLayer class];
}

@end
Loading

0 comments on commit cbb9b31

Please sign in to comment.