Skip to content

Commit 31c2bc7

Browse files
committed
Initial code commit
Features: * add view hierarchy sensitive tutorial items * can show multiple tutorial items at the same time * tap on tutorial message hides tutorial item for a certain amount of time
1 parent 02ccc11 commit 31c2bc7

289 files changed

Lines changed: 19495 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

FFXInteractiveTutorial.podspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source 'https://github.com/CocoaPods/Specs.git'
2+
pod 'Mixpanel'

FFXInteractiveTutorial.xcodeproj/project.pbxproj

Lines changed: 623 additions & 0 deletions
Large diffs are not rendered by default.

FFXInteractiveTutorial.xcodeproj/project.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

FFXInteractiveTutorial.xcworkspace/contents.xcworkspacedata

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// FFXInteractiveTutorial.h
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 02/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
@import UIKit;
10+
11+
#import "FFXInteractiveTutorialItem.h"
12+
13+
@interface FFXInteractiveTutorialMetrics : NSObject
14+
15+
/**
16+
* Highlight view color
17+
*/
18+
@property (nonatomic, strong) UIColor* highlightColor;
19+
20+
/**
21+
* Tutorial text view background color
22+
*/
23+
@property (nonatomic, strong) UIColor* backgroundColor;
24+
25+
/**
26+
* Tutorial text view title font
27+
*/
28+
@property (nonatomic, strong) UIFont* titleFont;
29+
30+
/**
31+
* Tutorial text view title color
32+
*/
33+
@property (nonatomic, strong) UIColor* titleColor;
34+
35+
/**
36+
* Tutorial text view subtitle font
37+
*/
38+
@property (nonatomic, strong) UIFont* subtitleFont;
39+
40+
/**
41+
* Tutorial text view subtitle color
42+
*/
43+
@property (nonatomic, strong) UIColor* subtitleColor;
44+
45+
@end
46+
47+
@interface FFXInteractiveTutorial : NSObject
48+
49+
@property (nonatomic, readonly) FFXInteractiveTutorialMetrics* metrics;
50+
51+
- (instancetype)initWithWindow:(UIWindow*) window items:(NSArray<FFXInteractiveTutorialItem*>*)tutorialItems;
52+
53+
- (void) triggerCheck;
54+
55+
- (void) fullfillItemWithIdentifier:(NSString*) identifier;
56+
57+
@end
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// FFXInteractiveTutorial.m
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 02/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
#import "FFXInteractiveTutorial.h"
10+
11+
#import <Mixpanel/MPObjectSelector.h>
12+
13+
#import "FFXInteractiveTutorialItemsController.h"
14+
15+
#define FFX_INTERACTIVE_TUTORIAL_HIDE_ITEM_INTERVAL 60.0
16+
17+
@implementation FFXInteractiveTutorialMetrics
18+
@end
19+
20+
@interface FFXInteractiveTutorial()
21+
22+
@property (nonatomic, strong) FFXInteractiveTutorialMetrics* metrics;
23+
24+
@property (nonatomic, strong) UIWindow* window;
25+
@property (nonatomic, strong) NSMutableArray* items;
26+
27+
@property (nonatomic, strong) NSMutableArray* activeItems;
28+
@property (nonatomic, strong) FFXInteractiveTutorialItemsController* viewController;
29+
30+
@end
31+
32+
@implementation FFXInteractiveTutorial
33+
34+
#pragma mark Private
35+
36+
- (void) showItems:(NSArray<FFXInteractiveTutorialItem*>*)items{
37+
[self.viewController setItems:items animated:YES];
38+
}
39+
40+
- (FFXInteractiveTutorialMetrics*) defaultMetrics{
41+
FFXInteractiveTutorialMetrics* metrics = [[FFXInteractiveTutorialMetrics alloc] init];
42+
metrics.titleColor = metrics.subtitleColor = [UIColor whiteColor];
43+
metrics.backgroundColor = metrics.highlightColor = [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0];
44+
return metrics;
45+
}
46+
47+
#pragma mark Public
48+
- (instancetype)initWithWindow:(UIWindow*) window items:(NSArray<FFXInteractiveTutorialItem*>*)tutorialItems{
49+
self = [super init];
50+
if (self) {
51+
self.window = window;
52+
self.items = [tutorialItems mutableCopy];
53+
54+
self.activeItems = [NSMutableArray array];
55+
self.viewController = [[FFXInteractiveTutorialItemsController alloc] initWithWindow:window];
56+
57+
__weak __typeof(self) weakSelf = self;
58+
self.viewController.itemMessageTappedBlock = ^(FFXInteractiveTutorialItem* item){
59+
[item disable:FFX_INTERACTIVE_TUTORIAL_HIDE_ITEM_INTERVAL];
60+
[weakSelf triggerCheck];
61+
};
62+
63+
self.metrics = [self defaultMetrics];
64+
self.viewController.metrics = self.metrics;
65+
}
66+
return self;
67+
}
68+
69+
-(void)setWindow:(UIWindow *)window{
70+
if (window != _window) {
71+
_window = window;
72+
}
73+
}
74+
75+
-(void)triggerCheck{
76+
NSMutableArray* candidates = [NSMutableArray array];
77+
78+
if (self.window.rootViewController) {
79+
for (FFXInteractiveTutorialItem* item in self.items) {
80+
if (!item.active) {
81+
continue;
82+
}
83+
84+
MPObjectSelector* sel = [[MPObjectSelector alloc] initWithString:item.viewPath];
85+
NSArray* result = [sel selectFromRoot:self.window.rootViewController.view];
86+
NSLog(@"path %@ => %@", item.viewPath, result);
87+
88+
if (result.count) {
89+
item.currentView = [result firstObject];
90+
[candidates addObject:item];
91+
}
92+
}
93+
}
94+
95+
[self.activeItems removeAllObjects];
96+
for (FFXInteractiveTutorialItem* item in candidates) {
97+
if (item.unique) {
98+
// only add unique item if there is no other high priority tutorial item
99+
if (self.activeItems.count == 0) {
100+
[self.activeItems addObject:item];
101+
}
102+
return;
103+
} else {
104+
[self.activeItems addObject:item];
105+
}
106+
}
107+
108+
[self showItems:self.activeItems];
109+
}
110+
111+
- (void)fullfillItemWithIdentifier:(NSString *)identifier{
112+
NSUInteger index = [self.items indexOfObjectPassingTest:^BOOL(FFXInteractiveTutorialItem* _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
113+
return [item.identifier isEqualToString:identifier];
114+
}];
115+
if (index != NSNotFound) {
116+
NSMutableArray* items = [self.items mutableCopy];
117+
[items removeObjectAtIndex:index];
118+
self.items = items;
119+
}
120+
}
121+
122+
-(void)dealloc{
123+
124+
}
125+
126+
@end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// FFXInteractiveTutorialHightlightView.h
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 03/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
11+
#import "FFXInteractiveTutorialItem.h"
12+
13+
@interface FFXInteractiveTutorialHightlightView : UIView
14+
15+
@property (nonatomic, weak) FFXInteractiveTutorialItem* item;
16+
17+
@property (nonatomic, assign) BOOL current;
18+
19+
@end
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// FFXInteractiveTutorialHightlightView.m
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 03/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
#import "FFXInteractiveTutorialHightlightView.h"
10+
11+
@implementation FFXInteractiveTutorialHightlightView
12+
13+
- (instancetype)initWithFrame:(CGRect)frame{
14+
self = [super initWithFrame:frame];
15+
if (self) {
16+
self.layer.borderColor = self.tintColor.CGColor;
17+
self.layer.borderWidth = 5.0;
18+
self.backgroundColor = [UIColor clearColor];
19+
}
20+
return self;
21+
}
22+
23+
- (void) layoutSubviews{
24+
[super layoutSubviews];
25+
26+
self.layer.cornerRadius = self.bounds.size.width/2.0;
27+
self.layer.borderColor = self.current ? self.tintColor.CGColor : [self.tintColor colorWithAlphaComponent:0.25].CGColor;
28+
}
29+
30+
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event{
31+
return NO;
32+
}
33+
34+
- (void)setCurrent:(BOOL)current{
35+
if (_current != current) {
36+
_current = current;
37+
[self setNeedsLayout];
38+
}
39+
}
40+
41+
@end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// FFXInteractiveTutorialItem.h
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 02/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
@import UIKit;
10+
11+
@interface FFXInteractiveTutorialItem : NSObject<NSCopying>
12+
13+
+ (instancetype) itemWithIdentifier:(NSString*)identifier viewPath:(NSString*)viewPath title:(NSString*)title;
14+
15+
- (instancetype) initWithIdentifier:(NSString*)identifier ViewPath:(NSString*)viewPath title:(NSString*)title;
16+
17+
@property (nonatomic, strong) NSString* identifier;
18+
19+
/**
20+
* String representing the path of the view to be used in a tutorial;
21+
*/
22+
@property (nonatomic, copy) NSString* viewPath;
23+
24+
/**
25+
* Is this tutorial item allowed to show only once?
26+
* Default: YES
27+
*/
28+
@property (nonatomic, assign) BOOL unique;
29+
30+
/**
31+
* Should the matched view be highlighted?
32+
* Default: YES
33+
*/
34+
@property (nonatomic, assign) BOOL highlightView;
35+
36+
@property (nonatomic, copy) NSString* title;
37+
38+
@property (nonatomic, copy) NSString* subtitle;
39+
40+
@property (nonatomic, weak) UIView* currentView;
41+
42+
@property (nonatomic, readonly) BOOL active;
43+
44+
/**
45+
* Disables a tutorial item for a certain timeframe
46+
*
47+
* @param timeInterval
48+
*/
49+
- (void) disable:(NSTimeInterval) timeInterval;
50+
51+
@end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// FFXInteractiveTutorialItem.m
3+
// FFXInteractiveTutorial
4+
//
5+
// Created by Robert Biehl on 02/10/2015.
6+
// Copyright © 2015 Robert Biehl. All rights reserved.
7+
//
8+
9+
#import "FFXInteractiveTutorialItem.h"
10+
11+
@interface FFXInteractiveTutorialItem()
12+
13+
@property (nonatomic, strong) NSDate* disabledUntil;
14+
15+
@end
16+
17+
@implementation FFXInteractiveTutorialItem
18+
19+
+ (instancetype) itemWithIdentifier:(NSString*)identifier viewPath:(NSString *)viewPath title:(NSString *)title{
20+
return [[FFXInteractiveTutorialItem alloc] initWithIdentifier:identifier viewPath:viewPath title:title];
21+
}
22+
23+
- (instancetype) initWithIdentifier:(NSString*)identifier viewPath:(NSString *)viewPath title:(NSString *)title{
24+
self = [super init];
25+
if (self) {
26+
self.identifier = identifier;
27+
self.viewPath = viewPath;
28+
self.title = title;
29+
}
30+
return self;
31+
}
32+
33+
- (BOOL)active{
34+
return (_disabledUntil==nil) || ([_disabledUntil timeIntervalSinceNow] <= 0.0);
35+
}
36+
37+
- (void)disable:(NSTimeInterval)timeInterval{
38+
_disabledUntil = [NSDate dateWithTimeIntervalSinceNow:timeInterval];
39+
}
40+
41+
- (id)copy{
42+
FFXInteractiveTutorialItem* copy = [[FFXInteractiveTutorialItem alloc] initWithIdentifier:self.identifier viewPath:self.viewPath title:self.title];
43+
copy.unique = self.unique;
44+
copy.highlightView = self.highlightView;
45+
copy.subtitle = self.subtitle;
46+
copy.currentView = self.currentView;
47+
return copy;
48+
}
49+
50+
- (id)copyWithZone:(NSZone *)zone{
51+
FFXInteractiveTutorialItem* copy = [[FFXInteractiveTutorialItem allocWithZone:zone] initWithIdentifier:self.identifier viewPath:self.viewPath title:self.title];
52+
copy.unique = self.unique;
53+
copy.highlightView = self.highlightView;
54+
copy.subtitle = self.subtitle;
55+
copy.currentView = self.currentView;
56+
return copy;
57+
}
58+
59+
@end

0 commit comments

Comments
 (0)