├── .gitignore
├── .travis.yml
├── DNTutorial.podspec
├── Example
├── DNTutorial.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── DNTutorial.xcscheme
├── DNTutorial.xcworkspace
│ └── contents.xcworkspacedata
├── DNTutorial
│ ├── DNAppDelegate.h
│ ├── DNAppDelegate.m
│ ├── DNRootController.h
│ ├── DNRootController.m
│ ├── DNTutorial-Info.plist
│ ├── DNTutorial-Prefix.pch
│ ├── DNViewController.h
│ ├── DNViewController.m
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── LaunchImage.launchimage
│ │ │ └── Contents.json
│ │ └── backgroundImage.imageset
│ │ │ ├── Contents.json
│ │ │ └── backgroundImage.png
│ ├── Main_iPad.storyboard
│ ├── Storyboard.storyboard
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ └── main.m
├── DNTutorialTests
│ ├── DNTutorialTests-Info.plist
│ ├── DNTutorialTests-Prefix.pch
│ ├── DNTutorialTests.m
│ ├── completionSound.wav
│ └── en.lproj
│ │ └── InfoPlist.strings
├── Podfile
├── Podfile.lock
└── Pods
│ ├── Headers
│ ├── Private
│ │ └── DNTutorial
│ │ │ ├── DNTutorial.h
│ │ │ ├── DNTutorialAudio.h
│ │ │ ├── DNTutorialBanner.h
│ │ │ ├── DNTutorialElement.h
│ │ │ ├── DNTutorialGesture.h
│ │ │ ├── DNTutorialMovement.h
│ │ │ └── DNTutorialStep.h
│ └── Public
│ │ └── DNTutorial
│ │ ├── DNTutorial.h
│ │ ├── DNTutorialAudio.h
│ │ ├── DNTutorialBanner.h
│ │ ├── DNTutorialElement.h
│ │ ├── DNTutorialGesture.h
│ │ ├── DNTutorialMovement.h
│ │ └── DNTutorialStep.h
│ ├── Local Podspecs
│ └── DNTutorial.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ └── project.pbxproj
│ └── Target Support Files
│ ├── Pods-DNTutorial-DNTutorial
│ ├── Pods-DNTutorial-DNTutorial-Private.xcconfig
│ ├── Pods-DNTutorial-DNTutorial-dummy.m
│ ├── Pods-DNTutorial-DNTutorial-prefix.pch
│ └── Pods-DNTutorial-DNTutorial.xcconfig
│ ├── Pods-DNTutorial
│ ├── Pods-DNTutorial-acknowledgements.markdown
│ ├── Pods-DNTutorial-acknowledgements.plist
│ ├── Pods-DNTutorial-dummy.m
│ ├── Pods-DNTutorial-environment.h
│ ├── Pods-DNTutorial-resources.sh
│ ├── Pods-DNTutorial.debug.xcconfig
│ └── Pods-DNTutorial.release.xcconfig
│ ├── Pods-DNTutorialTests-DNTutorial
│ ├── Pods-DNTutorialTests-DNTutorial-Private.xcconfig
│ ├── Pods-DNTutorialTests-DNTutorial-dummy.m
│ ├── Pods-DNTutorialTests-DNTutorial-prefix.pch
│ └── Pods-DNTutorialTests-DNTutorial.xcconfig
│ └── Pods-DNTutorialTests
│ ├── Pods-DNTutorialTests-acknowledgements.markdown
│ ├── Pods-DNTutorialTests-acknowledgements.plist
│ ├── Pods-DNTutorialTests-dummy.m
│ ├── Pods-DNTutorialTests-environment.h
│ ├── Pods-DNTutorialTests-resources.sh
│ ├── Pods-DNTutorialTests.debug.xcconfig
│ └── Pods-DNTutorialTests.release.xcconfig
├── LICENSE
├── Pod
├── Assets
│ ├── .gitkeep
│ ├── DNTutorialCheck.png
│ ├── DNTutorialCheck@2x.png
│ ├── DNTutorialCheck@3x.png
│ ├── DNTutorialClose.png
│ ├── DNTutorialClose@2x.png
│ └── DNTutorialClose@3x.png
└── Classes
│ ├── .gitkeep
│ ├── DNTutorial.h
│ ├── DNTutorial.m
│ ├── DNTutorialAudio.h
│ ├── DNTutorialAudio.m
│ ├── DNTutorialBanner.h
│ ├── DNTutorialBanner.m
│ ├── DNTutorialElement.h
│ ├── DNTutorialElement.m
│ ├── DNTutorialGesture.h
│ ├── DNTutorialGesture.m
│ ├── DNTutorialMovement.h
│ ├── DNTutorialMovement.m
│ ├── DNTutorialStep.h
│ └── DNTutorialStep.m
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # We recommend against adding the Pods directory to your .gitignore. However
26 | # you should judge for yourself, the pros and cons are mentioned at:
27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
28 | #
29 | # Pods/
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # reference: http://www.objc.io/issue-6/travis-ci.html
2 |
3 | language: objective-c
4 | script:
5 | - xctool test -workspace Example/DNTutorial.xcworkspace -scheme DNTutorial -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO
6 |
--------------------------------------------------------------------------------
/DNTutorial.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint DNTutorial.podspec' to ensure this is a
3 | # valid spec and remove all comments before submitting the spec.
4 | #
5 | # Any lines starting with a # are optional, but encouraged
6 | #
7 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
8 | #
9 |
10 | Pod::Spec.new do |s|
11 | s.name = "DNTutorial"
12 | s.version = "0.1.8"
13 | s.summary = "DNTutorial provides an easy to use introductory tutorial system based on Paper by Facebook."
14 | s.description = <<-DESC
15 | DNTutorial provides an easy to use introductory tutorial system based on Paper by Facebook.
16 | Once the user completes a task, the tutorial message will never be displayed again.
17 | If the user interacts with a feature that could toggle a tutorial message, that message should never be displayed to the user.
18 | s
19 | DESC
20 | s.homepage = "https://github.com/danielniemeyer/DNTutorial"
21 | s.screenshots = "http://f.cl.ly/items/3o0n1K2V2z1L1e0t2X09/tutorial.gif"
22 | s.license = 'MIT'
23 | s.author = { "Daniel Niemeyer" => "danieldn94@gmail.com" }
24 | s.source = { :git => "https://github.com/danielniemeyer/DNTutorial.git", :tag => s.version.to_s }
25 | # s.social_media_url = 'https://twitter.com/danielniemeyer'
26 |
27 | s.platform = :ios, '7.0'
28 | s.requires_arc = true
29 |
30 | s.source_files = 'Pod/Classes'
31 | s.resources = 'Pod/Assets/*.png'
32 |
33 | # s.public_header_files = 'Pod/Classes/**/*.h'
34 | # s.frameworks = 'UIKit', 'MapKit'
35 | # s.dependency 'AFNetworking', '~> 2.3'
36 | end
37 |
--------------------------------------------------------------------------------
/Example/DNTutorial.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/DNTutorial.xcodeproj/xcshareddata/xcschemes/DNTutorial.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Example/DNTutorial.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNAppDelegate.h
3 | // DNTutorial
4 | //
5 | // Created by CocoaPods on 08/01/2014.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DNAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNAppDelegate.m
3 | // DNTutorial
4 | //
5 | // Created by CocoaPods on 08/01/2014.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNAppDelegate.h"
10 |
11 | #import "DNTutorial.h"
12 |
13 | @implementation DNAppDelegate
14 |
15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
16 | {
17 | // Override point for customization after application launch.
18 | [DNTutorial setDebug];
19 |
20 | // Presentation Delay
21 | [DNTutorial setPresentationDelay:1];
22 |
23 | // Check if should present tutorial elements
24 | // [DNTutorial shouldPresentElementsWithBlock:^BOOL {
25 | // return YES;
26 | // }];
27 |
28 | return YES;
29 | }
30 |
31 | - (void)applicationWillResignActive:(UIApplication *)application
32 | {
33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
34 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
35 | }
36 |
37 | - (void)applicationDidEnterBackground:(UIApplication *)application
38 | {
39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
41 | }
42 |
43 | - (void)applicationWillEnterForeground:(UIApplication *)application
44 | {
45 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
46 | }
47 |
48 | - (void)applicationDidBecomeActive:(UIApplication *)application
49 | {
50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
51 | }
52 |
53 | - (void)applicationWillTerminate:(UIApplication *)application
54 | {
55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
56 | }
57 |
58 | @end
59 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNRootController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNRootController.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/29/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "DNTutorial.h"
12 |
13 | @interface DNRootController : UIViewController
14 |
15 | @property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
16 | @property (nonatomic, weak) IBOutlet UIPageControl *pageControl;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNRootController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNRootController.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/29/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNRootController.h"
10 | #import "DNViewController.h"
11 |
12 | NSInteger const sScrollViewPageCount = 2;
13 |
14 | #define kPageControlHeight 45
15 |
16 | @interface DNRootController ()
17 |
18 | @property (nonatomic, strong) NSMutableArray *viewControllers;
19 |
20 | @end
21 |
22 | @implementation DNRootController
23 |
24 |
25 | #pragma mark --
26 | #pragma mark Initialization
27 | #pragma mark --
28 |
29 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
30 | {
31 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
32 | if (self) {
33 | // Custom initialization
34 | }
35 | return self;
36 | }
37 |
38 | - (void)viewDidLoad
39 | {
40 | [super viewDidLoad];
41 | // Do any additional setup after loading the view.
42 | NSMutableArray *controllers = [[NSMutableArray alloc] init];
43 | for (NSUInteger i = 0; i < sScrollViewPageCount; i++)
44 | {
45 | [controllers addObject:[NSNull null]];
46 | }
47 | self.viewControllers = controllers;
48 |
49 | CGSize screenSize = [[UIScreen mainScreen] bounds].size;
50 |
51 | // a page is the width of the scroll view
52 | self.scrollView.pagingEnabled = YES;
53 | self.scrollView.contentSize = CGSizeMake(screenSize.width * sScrollViewPageCount, screenSize.height - kPageControlHeight);
54 | self.scrollView.showsHorizontalScrollIndicator = NO;
55 | self.scrollView.showsVerticalScrollIndicator = NO;
56 | self.scrollView.scrollsToTop = NO;
57 | self.scrollView.delegate = self;
58 |
59 | self.pageControl.numberOfPages = sScrollViewPageCount;
60 | self.pageControl.currentPage = 0;
61 |
62 | [self gotoPage:NO];
63 | }
64 |
65 | - (void)viewWillAppear:(BOOL)animated
66 | {
67 | // Tutorial
68 | [self presentTutorial];
69 |
70 | [super viewWillAppear:animated];
71 | }
72 |
73 | - (void)didReceiveMemoryWarning
74 | {
75 | [super didReceiveMemoryWarning];
76 | // Dispose of any resources that can be recreated.
77 | }
78 |
79 | - (UIStoryboard *)mainStoryboard;
80 | {
81 | NSString *storyboardName = @"Storyboard";
82 |
83 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
84 | storyboardName = @"Main_iPad";
85 |
86 | return [UIStoryboard storyboardWithName:storyboardName bundle:nil];
87 | }
88 |
89 | #pragma mark --
90 | #pragma mark DNTutorial Delegate
91 | #pragma mark --
92 |
93 | - (void)presentTutorial;
94 | {
95 | CGPoint center, buttonCenter, objectCenter;
96 | center = buttonCenter = objectCenter = self.view.center;
97 |
98 | center.x += 50;
99 | buttonCenter.y = 60;
100 |
101 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Tap and swipe left to navigate to the next page. Swipe anywhere left or right to skip pages." completionMessage:@"Congratulations! You now know how to navigate throughout the app." key:@"initialBanner"];
102 | DNTutorialBanner *banner2 = [DNTutorialBanner bannerWithMessage:@"Tap 'complete action' to continue." completionMessage:@"Congratulations! You now know how to complete actions" key:@"secondBanner"];
103 | DNTutorialBanner *banner3 = [DNTutorialBanner bannerWithMessage:@"Tap and swipe down to drag objects across the screen." completionMessage:@"Congratulations! You now know how use swipe gestures" key:@"thirdBanner"];
104 |
105 | [banner2 styleWithColor:[UIColor blackColor] completedColor:[UIColor blueColor] opacity:0.7 font:[UIFont systemFontOfSize:13]];
106 |
107 | DNTutorialGesture *scrollGesture = [DNTutorialGesture gestureWithPosition:center type:DNTutorialGestureTypeScrollLeft key:@"firstGesture"];
108 | DNTutorialGesture *tapGesture = [DNTutorialGesture gestureWithPosition:buttonCenter type:DNTutorialGestureTypeTap key:@"tapGesture"];
109 | DNTutorialGesture *swipeGesture = [DNTutorialGesture gestureWithPosition:objectCenter type:DNTutorialGestureTypeSwipeDown key:@"secondGesture"];
110 |
111 | DNTutorialAudio *audio1 = [DNTutorialAudio audioWithPath:@"completionSound" ofType:@"wav" key:@"firstAudio"];
112 |
113 | // Movement beta
114 | // DNTutorialMovement *movement1 = [DNTutorialMovement movementWithDirection:DNTutorialMovementDirectionUp key:@"firstMovement"];
115 |
116 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, scrollGesture, audio1] forKey:@"firtStep"];
117 | DNTutorialStep *step2 = [DNTutorialStep stepWithTutorialElements:@[banner2, tapGesture] forKey:@"secondStep"];
118 | DNTutorialStep *step3 = [DNTutorialStep stepWithTutorialElements:@[banner3, swipeGesture] forKey:@"thirdStep"];
119 |
120 | [DNTutorial presentTutorialWithSteps:@[step1, step2, step3] inView:self.view delegate:self];
121 | }
122 |
123 | - (BOOL)shouldPresentStep:(DNTutorialStep *)step forKey:(NSString *)aKey;
124 | {
125 | return YES;
126 | }
127 |
128 | - (BOOL)shouldAnimateStep:(DNTutorialStep *)step forKey:(NSString *)aKey;
129 | {
130 | if ([aKey isEqualToString:@"secondStep"] || [aKey isEqualToString:@"thirdStep"])
131 | {
132 | return self.pageControl.currentPage == 1;
133 | }
134 |
135 | return YES;
136 | }
137 |
138 | #pragma mark --
139 | #pragma mark Screen rotation
140 | #pragma mark --
141 |
142 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration;
143 | {
144 | // Tutorial
145 | [DNTutorial willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
146 |
147 | // View rotations
148 | CGSize screenSize = [[UIScreen mainScreen] bounds].size;
149 | self.scrollView.contentSize = CGSizeMake(screenSize.width * sScrollViewPageCount, screenSize.height - kPageControlHeight);
150 |
151 | for (UIViewController *viewController in self.viewControllers)
152 | {
153 | [viewController.view removeFromSuperview];
154 | }
155 |
156 | [self gotoPage:NO];
157 | }
158 |
159 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
160 | {
161 | if ([element isKindOfClass:[DNTutorialGesture class]])
162 | {
163 | // Reposition center
164 | CGPoint center, buttonCenter, objectCenter;
165 | center = buttonCenter = objectCenter = self.view.center;
166 |
167 | center.x += 50;
168 | buttonCenter.y = 60;
169 |
170 | if ([element.key isEqualToString:@"firstGesture"])
171 | {
172 | [(DNTutorialGesture *)element setPosition:center];
173 | }
174 | else if ([element.key isEqualToString:@"tapGesture"])
175 | {
176 | [(DNTutorialGesture *)element setPosition:buttonCenter];
177 | }
178 | else if ([element.key isEqualToString:@"secondGesture"])
179 | {
180 | [(DNTutorialGesture *)element setPosition:objectCenter];
181 | }
182 | }
183 | }
184 |
185 | #pragma mark --
186 | #pragma mark ScrollView
187 | #pragma mark --
188 |
189 | - (void)loadScrollViewWithPage:(NSUInteger)page
190 | {
191 | if (page >= 2)
192 | return;
193 |
194 | DNViewController *controller = [self.viewControllers objectAtIndex:page];
195 | if ((NSNull *)controller == [NSNull null])
196 | {
197 | if (page == 0)
198 | controller = [[self mainStoryboard] instantiateViewControllerWithIdentifier:@"DNFirstController"];
199 | else
200 | controller = [[self mainStoryboard] instantiateViewControllerWithIdentifier:@"DNViewController"];
201 |
202 | [self.viewControllers replaceObjectAtIndex:page withObject:controller];
203 | }
204 |
205 | if (controller.view.superview == nil)
206 | {
207 | CGRect frame = self.scrollView.frame;
208 | CGSize screenSize = [[UIScreen mainScreen] bounds].size;
209 | frame.origin.x = screenSize.width * page;
210 | frame.origin.y = 0;
211 | controller.view.frame = frame;
212 |
213 | [self addChildViewController:controller];
214 | [self.scrollView addSubview:controller.view];
215 | [controller didMoveToParentViewController:self];
216 | }
217 | }
218 |
219 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
220 | {
221 | [DNTutorial scrollViewWillBeginDragging:scrollView];
222 | }
223 |
224 |
225 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
226 | {
227 | [DNTutorial scrollViewDidScroll:scrollView];
228 | }
229 |
230 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
231 | {
232 | CGFloat pageWidth = CGRectGetWidth(self.scrollView.frame);
233 | NSUInteger page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
234 | self.pageControl.currentPage = page;
235 |
236 | [self loadScrollViewWithPage:page - 1];
237 | [self loadScrollViewWithPage:page];
238 | [self loadScrollViewWithPage:page + 1];
239 |
240 | [DNTutorial scrollViewDidEndDecelerating:scrollView];
241 | }
242 |
243 | #pragma mark --
244 | #pragma mark Pagination
245 | #pragma mark --
246 |
247 | - (void)gotoPage:(BOOL)animated
248 | {
249 | NSInteger page = self.pageControl.currentPage;
250 |
251 | [self loadScrollViewWithPage:page - 1];
252 | [self loadScrollViewWithPage:page];
253 | [self loadScrollViewWithPage:page + 1];
254 |
255 | CGRect bounds = self.scrollView.bounds;
256 | bounds.origin.x = CGRectGetWidth(bounds) * page;
257 | bounds.origin.y = 0;
258 | [self.scrollView scrollRectToVisible:bounds animated:animated];
259 | }
260 |
261 | - (IBAction)changePage:(id)sender
262 | {
263 | [self gotoPage:YES];
264 | }
265 |
266 | @end
267 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNTutorial-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | Storyboard
29 | UIMainStoryboardFile
30 | Storyboard
31 | UIMainStoryboardFile~ipad
32 | Main_iPad
33 | UIRequiredDeviceCapabilities
34 |
35 | armv7
36 |
37 | UIStatusBarStyle
38 | UIStatusBarStyleBlackTranslucent
39 | UISupportedInterfaceOrientations
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 | UISupportedInterfaceOrientations~ipad
46 |
47 | UIInterfaceOrientationPortrait
48 | UIInterfaceOrientationPortraitUpsideDown
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNTutorial-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | #import
15 | #import
16 | #endif
17 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNViewController.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface DNViewController : UIViewController
12 |
13 | @property (nonatomic, weak) IBOutlet UIView *square;
14 | @property (nonatomic, weak) IBOutlet UIImageView *imageView;
15 |
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/Example/DNTutorial/DNViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNViewController.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNViewController.h"
10 |
11 | #import "DNTutorial.h"
12 |
13 | @interface DNViewController ()
14 | {
15 | BOOL isMovingSquare;
16 | }
17 |
18 | @property (nonatomic, strong) CMMotionManager *motionManager;
19 |
20 | @end
21 |
22 | @implementation DNViewController
23 |
24 | #pragma mark --
25 | #pragma mark Initialization
26 | #pragma mark --
27 |
28 | - (void)viewDidLoad
29 | {
30 | [super viewDidLoad];
31 | // Do any additional setup after loading the view, typically from a nib.
32 | self.imageView.image = [UIImage imageNamed:@"backgroundImage"];
33 |
34 | self.motionManager = [[CMMotionManager alloc] init];
35 | self.motionManager.accelerometerUpdateInterval = 0.2f;
36 | self.motionManager.gyroUpdateInterval = 0.2f;
37 |
38 | [self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
39 | withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
40 | [self outputAccelerometerData:accelerometerData.acceleration];
41 | if (error)
42 | {
43 | NSLog(@"DNTutorialMovement: Accelerometer queue error: %@", error);
44 | }
45 | }];
46 |
47 | [self.motionManager startGyroUpdatesToQueue:[NSOperationQueue currentQueue]
48 | withHandler:^(CMGyroData *gyroData, NSError *error) {
49 | [self outputRotationData:gyroData.rotationRate];
50 | }];
51 | }
52 |
53 | - (void)viewDidDisappear:(BOOL)animated
54 | {
55 | [self.motionManager stopAccelerometerUpdates];
56 | [self.motionManager stopGyroUpdates];
57 | self.motionManager = nil;
58 | }
59 |
60 | - (void)didReceiveMemoryWarning
61 | {
62 | [super didReceiveMemoryWarning];
63 | // Dispose of any resources that can be recreated.
64 | // [DNTutorial advanceTutorialSequenceWithName:@"example"];
65 | }
66 |
67 | #pragma mark --
68 | #pragma mark Private Methods
69 | #pragma mark --
70 |
71 | - (IBAction)investButtonAction:(id)sender
72 | {
73 | [DNTutorial completedStepForKey:@"secondStep"];
74 | }
75 |
76 | - (IBAction)dismissTutorial:(id)sender
77 | {
78 | [DNTutorial hideTutorial];
79 | }
80 |
81 | - (IBAction)showTutorial:(id)sender
82 | {
83 | [DNTutorial showTutorial];
84 | }
85 |
86 | - (IBAction)resetAction:(id)sender
87 | {
88 | [DNTutorial resetProgress];
89 | }
90 |
91 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
92 | {
93 | CGPoint touchPoint = [(UITouch *)[touches anyObject] locationInView:self.view];
94 | [DNTutorial touchesBegan:touchPoint inView:self.view];
95 | }
96 |
97 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
98 | {
99 | CGPoint touchPoint = [(UITouch *)[touches anyObject] locationInView:self.view];
100 | [DNTutorial touchesMoved:touchPoint destinationSize:CGSizeMake(0, -100)];
101 | self.square.center = touchPoint;
102 | }
103 |
104 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
105 | {
106 | CGPoint touchPoint = [(UITouch *)[touches anyObject] locationInView:self.view];
107 | [DNTutorial touchesEnded:touchPoint destinationSize:CGSizeMake(0, -100)];
108 | }
109 |
110 | - (void)outputAccelerometerData:(CMAcceleration)acceleration;
111 | {
112 |
113 | }
114 |
115 | - (void)outputRotationData:(CMRotationRate)rotation;
116 | {
117 | CGFloat centerX = self.view.center.x + 50*rotation.y;
118 | CGFloat centerY = self.view.center.y + 50*rotation.x;
119 |
120 | CGPoint center = CGPointMake(centerX, centerY);
121 |
122 | [UIView animateWithDuration:0.24f animations:^{
123 | self.imageView.center = center;
124 | }];
125 | }
126 |
127 | @end
128 |
--------------------------------------------------------------------------------
/Example/DNTutorial/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Example/DNTutorial/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "iphone",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "8.0",
8 | "subtype" : "736h",
9 | "scale" : "3x"
10 | },
11 | {
12 | "orientation" : "portrait",
13 | "idiom" : "iphone",
14 | "extent" : "full-screen",
15 | "minimum-system-version" : "8.0",
16 | "subtype" : "667h",
17 | "scale" : "2x"
18 | },
19 | {
20 | "orientation" : "portrait",
21 | "idiom" : "iphone",
22 | "extent" : "full-screen",
23 | "minimum-system-version" : "7.0",
24 | "scale" : "2x"
25 | },
26 | {
27 | "orientation" : "portrait",
28 | "idiom" : "iphone",
29 | "extent" : "full-screen",
30 | "minimum-system-version" : "7.0",
31 | "subtype" : "retina4",
32 | "scale" : "2x"
33 | },
34 | {
35 | "orientation" : "portrait",
36 | "idiom" : "ipad",
37 | "extent" : "full-screen",
38 | "minimum-system-version" : "7.0",
39 | "scale" : "1x"
40 | },
41 | {
42 | "orientation" : "portrait",
43 | "idiom" : "ipad",
44 | "extent" : "full-screen",
45 | "minimum-system-version" : "7.0",
46 | "scale" : "2x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/Example/DNTutorial/Images.xcassets/backgroundImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x",
10 | "filename" : "backgroundImage.png"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/DNTutorial/Images.xcassets/backgroundImage.imageset/backgroundImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Example/DNTutorial/Images.xcassets/backgroundImage.imageset/backgroundImage.png
--------------------------------------------------------------------------------
/Example/DNTutorial/Main_iPad.storyboard:
--------------------------------------------------------------------------------
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 |
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 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/Example/DNTutorial/Storyboard.storyboard:
--------------------------------------------------------------------------------
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 |
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 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
185 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
--------------------------------------------------------------------------------
/Example/DNTutorial/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/DNTutorial/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 08/01/2014.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "DNAppDelegate.h"
12 |
13 | int main(int argc, char * argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DNAppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/DNTutorialTests/DNTutorialTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/DNTutorialTests/DNTutorialTests-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #ifdef __OBJC__
8 | #import
9 | #import
10 | #endif
11 |
--------------------------------------------------------------------------------
/Example/DNTutorialTests/DNTutorialTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialTests.m
3 | // DNTutorialTests
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DNTutorial.h"
11 |
12 | NSInteger const sTutorialGestureAll = DNTutorialActionTapGesture | DNTutorialActionScroll | DNTutorialActionSwipeGesture;
13 |
14 | @interface DNAppTutorialTests : XCTestCase
15 |
16 | @property (nonatomic, strong) UIView *containerView;
17 | @property (nonatomic, assign) CGPoint center;
18 |
19 | @end
20 |
21 | @implementation DNAppTutorialTests
22 |
23 | - (void)setUp
24 | {
25 | [super setUp];
26 | // Put setup code here. This method is called before the invocation of each test method in the class.
27 | UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
28 | self.center = containerView.center;
29 | self.containerView = containerView;
30 | }
31 |
32 | - (void)tearDown
33 | {
34 | // Put teardown code here. This method is called after the invocation of each test method in the class.
35 | self.containerView = nil;
36 | [super tearDown];
37 | }
38 |
39 | - (void)testTutorialSingleton;
40 | {
41 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
42 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
43 | DNTutorialGesture *gesture2 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeTap key:@"gestureTest2"];
44 |
45 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture1] forKey:@"stepTest1"];
46 | DNTutorialStep *step2 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture2] forKey:@"stepTest2"];
47 | DNTutorialStep *step3 = [DNTutorialStep stepWithTutorialElements:@[gesture1, banner1] forKey:@"stepTest3"];
48 | DNTutorialStep *step4 = [DNTutorialStep stepWithTutorialElements:@[gesture1, gesture2] forKey:@"stepTest4"];
49 | DNTutorialStep *step5 = [DNTutorialStep stepWithTutorialElements:@[gesture2, banner1] forKey:@"stepTest5"];
50 | DNTutorialStep *step6 = [DNTutorialStep stepWithTutorialElements:@[gesture2, gesture1] forKey:@"stepTest6"];
51 |
52 | [DNTutorial presentTutorialWithSteps:@[step1, step2, step3, step4, step5, step6] inView:self.containerView delegate:self];
53 | [DNTutorial presentStepForKey:@"stepTest4"];
54 |
55 | XCTAssertEqualObjects([DNTutorial currentStep], step1, @"DNAppTutorialTests: Presenting wrong current step");
56 | XCTAssertEqualObjects([DNTutorial tutorialStepForKey:@"stepTest4"], step4, @"DNAppTutorialTests: Presenting wrong current step");
57 | XCTAssertNotEqualObjects([DNTutorial tutorialStepForKey:@"stepTest1"], step3, @"DNAppTutorialTests: Presenting wrong current step");
58 | XCTAssertEqualObjects([DNTutorial tutorialElementForKey:@"gestureTest2"], gesture2, @"DNAppTutorialTests: Returning the wrong tutorial element");
59 | XCTAssertNotEqualObjects([DNTutorial tutorialElementForKey:@"gestureTest1"], banner1, @"DNAppTutorialTests: Returning the wrong tutorial element");
60 | XCTAssertNil([DNTutorial tutorialElementForKey:@"gestureTest4"], @"DNAppTutorialTests: Returning the wrong tutorial element");
61 | }
62 |
63 | - (void)testResetProgress;
64 | {
65 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
66 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
67 | DNTutorialGesture *gesture2 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeTap key:@"gestureTest2"];
68 |
69 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture1] forKey:@"stepTest1"];
70 | DNTutorialStep *step2 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture2] forKey:@"stepTest2"];
71 | DNTutorialStep *step3 = [DNTutorialStep stepWithTutorialElements:@[gesture1, banner1] forKey:@"stepTest3"];
72 | DNTutorialStep *step4 = [DNTutorialStep stepWithTutorialElements:@[gesture1, gesture2] forKey:@"stepTest4"];
73 | DNTutorialStep *step5 = [DNTutorialStep stepWithTutorialElements:@[gesture2, banner1] forKey:@"stepTest5"];
74 | DNTutorialStep *step6 = [DNTutorialStep stepWithTutorialElements:@[gesture2, gesture1] forKey:@"stepTest6"];
75 |
76 | [DNTutorial presentTutorialWithSteps:@[step1, step2, step3, step4, step5, step6] inView:self.containerView delegate:self];
77 |
78 | [step1 setCompleted:YES];
79 | [step4 setCompleted:YES];
80 | [step3 setCompleted:NO];
81 | [DNTutorial completedStepForKey:@"stepTest2"];
82 |
83 | XCTAssertTrue(step2.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
84 |
85 | [DNTutorial resetProgress];
86 |
87 | XCTAssertFalse(step2.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
88 | }
89 |
90 | - (void)testTutorialBanners;
91 | {
92 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
93 | DNTutorialBanner *banner2 = [DNTutorialBanner bannerWithMessage:@"Test 2" completionMessage:@"Test 2 Passed!" key:@"bannerTest2"];
94 |
95 | XCTAssertNotNil(banner1, @"DNAppTutorialTests: Cannot present a nil banner");
96 | XCTAssertEqual(banner1.tutorialActions, DNTutorialActionBanner, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
97 | XCTAssertNotEqual(banner1.tutorialActions, DNTutorialActionNone, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
98 | XCTAssertNotEqual(banner2.tutorialActions, sTutorialGestureAll, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
99 | }
100 |
101 | - (void)testTutorialGestures;
102 | {
103 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
104 | DNTutorialGesture *gesture2 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeSwipeRight key:@"gestureTest2"];
105 | DNTutorialGesture *gesture3 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeTap key:@"gestureTest3"];
106 | DNTutorialGesture *gesture4 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeDoubleTap key:@"gestureTest4"];
107 |
108 | XCTAssertNotNil(gesture1, @"DNAppTutorialTests: Cannot present a nil gesture");
109 | XCTAssertNotEqual(gesture1.gestureType, 0, @"DNAppTutorialTests: Cannot present a gesture with nil direction");
110 |
111 | XCTAssertEqual(gesture1.tutorialActions, DNTutorialActionScroll, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
112 | XCTAssertEqual(gesture2.tutorialActions, DNTutorialActionSwipeGesture, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
113 | XCTAssertEqual(gesture3.tutorialActions, sTutorialGestureAll, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
114 | XCTAssertEqual(gesture4.tutorialActions, sTutorialGestureAll, @"DNAppTutorialTests: Tutorial element returning the wrong actions");
115 | }
116 |
117 | - (void)testTutorialAudio;
118 | {
119 | NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"completionSound" ofType:@"wav"]];
120 | DNTutorialAudio *audio1 = [DNTutorialAudio audioWithURL:url key:@"audioTest1"];
121 | DNTutorialAudio *audio2 = [DNTutorialAudio audioWithPath:@"completionSound" ofType:@"wav" key:@"audioTest2"];
122 | XCTAssertNotNil(audio1, @"DNAppTutorialTests: Cannot present a nil gesture");
123 | XCTAssertNotNil(audio2, @"DNAppTutorialTests: Cannot present a nil gesture");
124 | }
125 |
126 | - (void)testPresentTutorial;
127 | {
128 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
129 | DNTutorialBanner *banner2 = [DNTutorialBanner bannerWithMessage:@"Test 2" completionMessage:@"Test 2 Passed!" key:@"bannerTest2"];
130 | DNTutorialBanner *banner3 = [DNTutorialBanner bannerWithMessage:@"Test 3" completionMessage:@"Test 3 Passed!" key:@"bannerTest3"];
131 |
132 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
133 | DNTutorialGesture *gesture2 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeTap key:@"gestureTest2"];
134 | DNTutorialGesture *gesture3 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeSwipeDown key:@"gestureTest3"];
135 |
136 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture1] forKey:@"stepTest1"];
137 | DNTutorialStep *step2 = [DNTutorialStep stepWithTutorialElements:@[banner2, gesture2] forKey:@"stepTest2"];
138 | DNTutorialStep *step3 = [DNTutorialStep stepWithTutorialElements:@[banner3, gesture3] forKey:@"stepTest3"];
139 |
140 | [DNTutorial presentTutorialWithSteps:@[step1, step2, step3] inView:self.containerView delegate:self];
141 |
142 | XCTAssertEqual([DNTutorial currentStep], step1, @"DNAppTutorialTests: Presenting the wrong tutorial step");
143 | XCTAssertNotEqual([DNTutorial currentStep], step2, @"DNAppTutorialTests: Presenting the wrong tutorial step");
144 | }
145 |
146 | - (void)testTutorialStep;
147 | {
148 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
149 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
150 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture1] forKey:@"stepTest1"];
151 |
152 | // Step testing
153 | XCTAssertEqual([step1 tutorialElementForKey:@"bannerTest1"], banner1, @"DNAppTutorialTests: Step returning the wrong tutorial element");
154 | XCTAssertEqual([step1 tutorialElementForKey:@"gestureTest1"], gesture1, @"DNAppTutorialTests: Step returning the wrong tutorial element");
155 | XCTAssertNil([step1 tutorialElementForKey:@"gestureTest2"], @"DNAppTutorialTests: Step returning the wrong tutorial element");
156 |
157 | // Action testing
158 | XCTAssertEqualObjects([step1 tutorialElementsWithAction:DNTutorialActionSwipeGesture], @[], @"DNAppTutorialTests: Step returning the wrong tutorial action");
159 | XCTAssertNotNil([step1 tutorialElementsWithAction:DNTutorialActionScroll], @"DNAppTutorialTests: Step returning the wrong tutorial action");
160 | XCTAssertEqualObjects([step1 tutorialElementsWithAction:DNTutorialActionBanner], @[banner1], @"DNAppTutorialTests: Step returning the wrong tutorial action");
161 | XCTAssertEqualObjects([step1 tutorialElementsWithAction:DNTutorialActionScroll], @[gesture1], @"DNAppTutorialTests: Step returning the wrong tutorial action");
162 |
163 | // Element actions
164 | XCTAssertFalse([step1 tutorialElement:banner1 respondsToActions:DNTutorialActionNone], @"DNAppTutorialTests: Step returning the wrong element action");
165 | XCTAssertTrue([step1 tutorialElement:banner1 respondsToActions:DNTutorialActionBanner], @"DNAppTutorialTests: Step returning the wrong element action");
166 | XCTAssertFalse([step1 tutorialElement:banner1 respondsToActions:DNTutorialActionSwipeGesture], @"DNAppTutorialTests: Step returning the wrong element action");
167 | XCTAssertTrue([step1 tutorialElement:gesture1 respondsToActions:DNTutorialActionScroll], @"DNAppTutorialTests: Step returning the wrong element action");
168 | XCTAssertFalse([step1 tutorialElement:gesture1 respondsToActions:DNTutorialActionSwipeGesture], @"DNAppTutorialTests: Step returning the wrong element action");
169 |
170 | // Completion testing
171 | [step1 setCompleted:YES];
172 | XCTAssertTrue(step1.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
173 |
174 | [step1 setCompleted:NO];
175 | XCTAssertFalse(step1.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
176 | }
177 |
178 | - (void)testPercentageCompletion;
179 | {
180 | DNTutorialBanner *banner1 = [DNTutorialBanner bannerWithMessage:@"Test 1" completionMessage:@"Test 1 Passed!" key:@"bannerTest1"];
181 | DNTutorialGesture *gesture1 = [DNTutorialGesture gestureWithPosition:self.center type:DNTutorialGestureTypeScrollLeft key:@"gestureTest1"];
182 | DNTutorialStep *step1 = [DNTutorialStep stepWithTutorialElements:@[banner1, gesture1] forKey:@"stepTest1"];
183 | DNTutorialStep *step2 = [DNTutorialStep stepWithTutorialElements:@[gesture1, banner1] forKey:@"stepTest2"];
184 | DNTutorialStep *step3 = [DNTutorialStep stepWithTutorialElements:@[banner1, banner1] forKey:@"stepTest3"];
185 |
186 | [step1 setPercentageCompleted:0.5];
187 | [step2 setPercentageCompleted:0.5];
188 | [step3 setPercentageCompleted:-0.5];
189 | XCTAssertFalse(step1.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
190 |
191 | [step1 setPercentageCompleted:1.0];
192 | XCTAssertTrue(step1.isCompleted, @"DNAppTutorialTests: Step returning the wrong completion status");
193 |
194 | // Percentage completion
195 | XCTAssertEqual(banner1.percentageCompleted, 1.0, @"DNAppTutorialTests: Step returning the wrong percentage status");
196 | XCTAssertEqual(gesture1.percentageCompleted, 1.0, @"DNAppTutorialTests: Step returning the wrong percentage status");
197 | XCTAssertEqual(step1.percentageCompleted, 1.0, @"DNAppTutorialTests: Step returning the wrong percentage status");
198 | XCTAssertEqual(step2.percentageCompleted, 0.5, @"DNAppTutorialTests: Step returning the wrong percentage status");
199 | XCTAssertEqual(step3.percentageCompleted, 0.0, @"DNAppTutorialTests: Step returning the wrong percentage status");
200 | }
201 |
202 | @end
203 |
--------------------------------------------------------------------------------
/Example/DNTutorialTests/completionSound.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Example/DNTutorialTests/completionSound.wav
--------------------------------------------------------------------------------
/Example/DNTutorialTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | target 'DNTutorial', :exclusive => true do
2 | pod "DNTutorial", :path => "../"
3 | end
4 |
5 | target 'DNTutorialTests', :exclusive => true do
6 | pod "DNTutorial", :path => "../"
7 | end
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - DNTutorial (0.1.8)
3 |
4 | DEPENDENCIES:
5 | - DNTutorial (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | DNTutorial:
9 | :path: ../
10 |
11 | SPEC CHECKSUMS:
12 | DNTutorial: 15f34fc489491af3def81ec54e40da51eb8f7a6f
13 |
14 | COCOAPODS: 0.36.3
15 |
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorial.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorial.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialAudio.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialAudio.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialBanner.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialBanner.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialElement.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialElement.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialGesture.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialGesture.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialMovement.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialMovement.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Private/DNTutorial/DNTutorialStep.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialStep.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorial.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorial.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialAudio.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialAudio.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialBanner.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialBanner.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialElement.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialElement.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialGesture.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialGesture.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialMovement.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialMovement.h
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/DNTutorial/DNTutorialStep.h:
--------------------------------------------------------------------------------
1 | ../../../../../Pod/Classes/DNTutorialStep.h
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/DNTutorial.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DNTutorial",
3 | "version": "0.1.8",
4 | "summary": "DNTutorial provides an easy to use introductory tutorial system based on Paper by Facebook.",
5 | "description": " DNTutorial provides an easy to use introductory tutorial system based on Paper by Facebook.\n Once the user completes a task, the tutorial message will never be displayed again.\n If the user interacts with a feature that could toggle a tutorial message, that message should never be displayed to the user.\n s\n",
6 | "homepage": "https://github.com/danielniemeyer/DNTutorial",
7 | "screenshots": "http://f.cl.ly/items/3o0n1K2V2z1L1e0t2X09/tutorial.gif",
8 | "license": "MIT",
9 | "authors": {
10 | "Daniel Niemeyer": "danieldn94@gmail.com"
11 | },
12 | "source": {
13 | "git": "https://github.com/danielniemeyer/DNTutorial.git",
14 | "tag": "0.1.8"
15 | },
16 | "platforms": {
17 | "ios": "7.0"
18 | },
19 | "requires_arc": true,
20 | "source_files": "Pod/Classes",
21 | "resources": "Pod/Assets/*.png"
22 | }
23 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - DNTutorial (0.1.8)
3 |
4 | DEPENDENCIES:
5 | - DNTutorial (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | DNTutorial:
9 | :path: ../
10 |
11 | SPEC CHECKSUMS:
12 | DNTutorial: 15f34fc489491af3def81ec54e40da51eb8f7a6f
13 |
14 | COCOAPODS: 0.36.3
15 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial-DNTutorial/Pods-DNTutorial-DNTutorial-Private.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods-DNTutorial-DNTutorial.xcconfig"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/DNTutorial" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = -ObjC
5 | PODS_ROOT = ${SRCROOT}
6 | SKIP_INSTALL = YES
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial-DNTutorial/Pods-DNTutorial-DNTutorial-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_DNTutorial_DNTutorial : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_DNTutorial_DNTutorial
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial-DNTutorial/Pods-DNTutorial-DNTutorial-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #endif
4 |
5 | #import "Pods-DNTutorial-environment.h"
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial-DNTutorial/Pods-DNTutorial-DNTutorial.xcconfig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Example/Pods/Target Support Files/Pods-DNTutorial-DNTutorial/Pods-DNTutorial-DNTutorial.xcconfig
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## DNTutorial
5 |
6 | Copyright (c) 2014 Daniel Niemeyer
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | Generated by CocoaPods - http://cocoapods.org
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Copyright (c) 2014 Daniel Niemeyer <danieldn94@gmail.com>
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 |
37 | Title
38 | DNTutorial
39 | Type
40 | PSGroupSpecifier
41 |
42 |
43 | FooterText
44 | Generated by CocoaPods - http://cocoapods.org
45 | Title
46 |
47 | Type
48 | PSGroupSpecifier
49 |
50 |
51 | StringsTable
52 | Acknowledgements
53 | Title
54 | Acknowledgements
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_DNTutorial : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_DNTutorial
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial-environment.h:
--------------------------------------------------------------------------------
1 |
2 | // To check if a library is compiled with CocoaPods you
3 | // can use the `COCOAPODS` macro definition which is
4 | // defined in the xcconfigs so it is available in
5 | // headers also when they are imported in the client
6 | // project.
7 |
8 |
9 | // DNTutorial
10 | #define COCOAPODS_POD_AVAILABLE_DNTutorial
11 | #define COCOAPODS_VERSION_MAJOR_DNTutorial 0
12 | #define COCOAPODS_VERSION_MINOR_DNTutorial 1
13 | #define COCOAPODS_VERSION_PATCH_DNTutorial 8
14 |
15 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=""
10 |
11 | install_resource()
12 | {
13 | case $1 in
14 | *.storyboard)
15 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}"
16 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}"
17 | ;;
18 | *.xib)
19 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}"
20 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}"
21 | ;;
22 | *.framework)
23 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
24 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
25 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
26 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
27 | ;;
28 | *.xcdatamodel)
29 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\""
30 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom"
31 | ;;
32 | *.xcdatamodeld)
33 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\""
34 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd"
35 | ;;
36 | *.xcmappingmodel)
37 | echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\""
38 | xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm"
39 | ;;
40 | *.xcassets)
41 | XCASSET_FILES="$XCASSET_FILES '${PODS_ROOT}/$1'"
42 | ;;
43 | /*)
44 | echo "$1"
45 | echo "$1" >> "$RESOURCES_TO_COPY"
46 | ;;
47 | *)
48 | echo "${PODS_ROOT}/$1"
49 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY"
50 | ;;
51 | esac
52 | }
53 | if [[ "$CONFIGURATION" == "Debug" ]]; then
54 | install_resource "../../Pod/Assets/DNTutorialCheck.png"
55 | install_resource "../../Pod/Assets/DNTutorialCheck@2x.png"
56 | install_resource "../../Pod/Assets/DNTutorialCheck@3x.png"
57 | install_resource "../../Pod/Assets/DNTutorialClose.png"
58 | install_resource "../../Pod/Assets/DNTutorialClose@2x.png"
59 | install_resource "../../Pod/Assets/DNTutorialClose@3x.png"
60 | fi
61 | if [[ "$CONFIGURATION" == "Release" ]]; then
62 | install_resource "../../Pod/Assets/DNTutorialCheck.png"
63 | install_resource "../../Pod/Assets/DNTutorialCheck@2x.png"
64 | install_resource "../../Pod/Assets/DNTutorialCheck@3x.png"
65 | install_resource "../../Pod/Assets/DNTutorialClose.png"
66 | install_resource "../../Pod/Assets/DNTutorialClose@2x.png"
67 | install_resource "../../Pod/Assets/DNTutorialClose@3x.png"
68 | fi
69 |
70 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
71 | if [[ "${ACTION}" == "install" ]]; then
72 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
73 | fi
74 | rm -f "$RESOURCES_TO_COPY"
75 |
76 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
77 | then
78 | case "${TARGETED_DEVICE_FAMILY}" in
79 | 1,2)
80 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
81 | ;;
82 | 1)
83 | TARGET_DEVICE_ARGS="--target-device iphone"
84 | ;;
85 | 2)
86 | TARGET_DEVICE_ARGS="--target-device ipad"
87 | ;;
88 | *)
89 | TARGET_DEVICE_ARGS="--target-device mac"
90 | ;;
91 | esac
92 | while read line; do XCASSET_FILES="$XCASSET_FILES '$line'"; done <<<$(find "$PWD" -name "*.xcassets" | egrep -v "^$PODS_ROOT")
93 | echo $XCASSET_FILES | xargs actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
94 | fi
95 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Pods-DNTutorial-DNTutorial"
5 | OTHER_LIBTOOLFLAGS = $(OTHER_LDFLAGS)
6 | PODS_ROOT = ${SRCROOT}/Pods
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorial/Pods-DNTutorial.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Pods-DNTutorial-DNTutorial"
5 | OTHER_LIBTOOLFLAGS = $(OTHER_LDFLAGS)
6 | PODS_ROOT = ${SRCROOT}/Pods
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests-DNTutorial/Pods-DNTutorialTests-DNTutorial-Private.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods-DNTutorialTests-DNTutorial.xcconfig"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/DNTutorial" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = -ObjC
5 | PODS_ROOT = ${SRCROOT}
6 | SKIP_INSTALL = YES
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests-DNTutorial/Pods-DNTutorialTests-DNTutorial-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_DNTutorialTests_DNTutorial : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_DNTutorialTests_DNTutorial
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests-DNTutorial/Pods-DNTutorialTests-DNTutorial-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #endif
4 |
5 | #import "Pods-DNTutorialTests-environment.h"
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests-DNTutorial/Pods-DNTutorialTests-DNTutorial.xcconfig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Example/Pods/Target Support Files/Pods-DNTutorialTests-DNTutorial/Pods-DNTutorialTests-DNTutorial.xcconfig
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## DNTutorial
5 |
6 | Copyright (c) 2014 Daniel Niemeyer
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | Generated by CocoaPods - http://cocoapods.org
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Copyright (c) 2014 Daniel Niemeyer <danieldn94@gmail.com>
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 |
37 | Title
38 | DNTutorial
39 | Type
40 | PSGroupSpecifier
41 |
42 |
43 | FooterText
44 | Generated by CocoaPods - http://cocoapods.org
45 | Title
46 |
47 | Type
48 | PSGroupSpecifier
49 |
50 |
51 | StringsTable
52 | Acknowledgements
53 | Title
54 | Acknowledgements
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_DNTutorialTests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_DNTutorialTests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests-environment.h:
--------------------------------------------------------------------------------
1 |
2 | // To check if a library is compiled with CocoaPods you
3 | // can use the `COCOAPODS` macro definition which is
4 | // defined in the xcconfigs so it is available in
5 | // headers also when they are imported in the client
6 | // project.
7 |
8 |
9 | // DNTutorial
10 | #define COCOAPODS_POD_AVAILABLE_DNTutorial
11 | #define COCOAPODS_VERSION_MAJOR_DNTutorial 0
12 | #define COCOAPODS_VERSION_MINOR_DNTutorial 1
13 | #define COCOAPODS_VERSION_PATCH_DNTutorial 8
14 |
15 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=""
10 |
11 | install_resource()
12 | {
13 | case $1 in
14 | *.storyboard)
15 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}"
16 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}"
17 | ;;
18 | *.xib)
19 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}"
20 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}"
21 | ;;
22 | *.framework)
23 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
24 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
25 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
26 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
27 | ;;
28 | *.xcdatamodel)
29 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\""
30 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom"
31 | ;;
32 | *.xcdatamodeld)
33 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\""
34 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd"
35 | ;;
36 | *.xcmappingmodel)
37 | echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\""
38 | xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm"
39 | ;;
40 | *.xcassets)
41 | XCASSET_FILES="$XCASSET_FILES '${PODS_ROOT}/$1'"
42 | ;;
43 | /*)
44 | echo "$1"
45 | echo "$1" >> "$RESOURCES_TO_COPY"
46 | ;;
47 | *)
48 | echo "${PODS_ROOT}/$1"
49 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY"
50 | ;;
51 | esac
52 | }
53 | if [[ "$CONFIGURATION" == "Debug" ]]; then
54 | install_resource "../../Pod/Assets/DNTutorialCheck.png"
55 | install_resource "../../Pod/Assets/DNTutorialCheck@2x.png"
56 | install_resource "../../Pod/Assets/DNTutorialCheck@3x.png"
57 | install_resource "../../Pod/Assets/DNTutorialClose.png"
58 | install_resource "../../Pod/Assets/DNTutorialClose@2x.png"
59 | install_resource "../../Pod/Assets/DNTutorialClose@3x.png"
60 | fi
61 | if [[ "$CONFIGURATION" == "Release" ]]; then
62 | install_resource "../../Pod/Assets/DNTutorialCheck.png"
63 | install_resource "../../Pod/Assets/DNTutorialCheck@2x.png"
64 | install_resource "../../Pod/Assets/DNTutorialCheck@3x.png"
65 | install_resource "../../Pod/Assets/DNTutorialClose.png"
66 | install_resource "../../Pod/Assets/DNTutorialClose@2x.png"
67 | install_resource "../../Pod/Assets/DNTutorialClose@3x.png"
68 | fi
69 |
70 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
71 | if [[ "${ACTION}" == "install" ]]; then
72 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
73 | fi
74 | rm -f "$RESOURCES_TO_COPY"
75 |
76 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
77 | then
78 | case "${TARGETED_DEVICE_FAMILY}" in
79 | 1,2)
80 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
81 | ;;
82 | 1)
83 | TARGET_DEVICE_ARGS="--target-device iphone"
84 | ;;
85 | 2)
86 | TARGET_DEVICE_ARGS="--target-device ipad"
87 | ;;
88 | *)
89 | TARGET_DEVICE_ARGS="--target-device mac"
90 | ;;
91 | esac
92 | while read line; do XCASSET_FILES="$XCASSET_FILES '$line'"; done <<<$(find "$PWD" -name "*.xcassets" | egrep -v "^$PODS_ROOT")
93 | echo $XCASSET_FILES | xargs actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
94 | fi
95 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Pods-DNTutorialTests-DNTutorial"
5 | OTHER_LIBTOOLFLAGS = $(OTHER_LDFLAGS)
6 | PODS_ROOT = ${SRCROOT}/Pods
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-DNTutorialTests/Pods-DNTutorialTests.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DNTutorial"
3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DNTutorial"
4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Pods-DNTutorialTests-DNTutorial"
5 | OTHER_LIBTOOLFLAGS = $(OTHER_LDFLAGS)
6 | PODS_ROOT = ${SRCROOT}/Pods
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Daniel Niemeyer
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Pod/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/.gitkeep
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialCheck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialCheck.png
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialCheck@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialCheck@2x.png
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialCheck@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialCheck@3x.png
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialClose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialClose.png
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialClose@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialClose@2x.png
--------------------------------------------------------------------------------
/Pod/Assets/DNTutorialClose@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Assets/DNTutorialClose@3x.png
--------------------------------------------------------------------------------
/Pod/Classes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danielniemeyer/DNTutorial/5675796788fdf6eadf99856ec79a8f9ed6e716ee/Pod/Classes/.gitkeep
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorial.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorial.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | // App tutorial manages a set of tutorial messages that interact with the user.
10 | // Once the user completes a task, the tutotial message will never be displayed again.
11 | // If the user interacts with a feature that could toggle a tutorial message, that message
12 | // should never be displayed to the user.
13 | // App tutorial provides incentives for users to complete tutorials by displaying their progress overall
14 |
15 | // Tutorials consist of multiple types.
16 | // DNTutorialBanner displays a banner with an appropriate message and an action that triggers its dismissal.
17 | // DNTutorialGesture displays a gesture motion with a starting location and direction.
18 |
19 | //- https://github.com/lostinthepines/TutorialKit
20 | //- https://github.com/kronik/UIViewController-Tutorial
21 |
22 | #import
23 |
24 | #import "DNTutorialStep.h"
25 |
26 | #import "DNTutorialBanner.h"
27 | #import "DNTutorialGesture.h"
28 | #import "DNTutorialAudio.h"
29 | #import "DNTutorialMovement.h"
30 |
31 | typedef BOOL (^shouldPresent)();
32 |
33 | @class DNTutorial;
34 |
35 | @protocol DNTutorialDelegate;
36 |
37 | @interface DNTutorial : NSObject
38 |
39 | // Tells DNTutorial to load the given elements and check if should present them
40 | + (void)presentTutorialWithSteps:(NSArray *)tutorialSteps
41 | inView:(UIView *)aView
42 | delegate:(id)delegate;
43 |
44 |
45 | // Pauses the tutorial, saves the current state and hides all elements
46 | + (void)showTutorial;
47 | + (void)hideTutorial;
48 |
49 |
50 | // Presents step for key
51 | + (void)presentStepForKey:(NSString *)aKey;
52 |
53 |
54 | // Hode step for key
55 | + (void)hideStepForKey:(NSString *)aKey;
56 |
57 |
58 | // Triggers a user action as completed
59 | + (void)completedStepForKey:(NSString *)aKey;
60 |
61 |
62 | // Reset tutorial to factory settings
63 | + (void)resetProgress;
64 |
65 |
66 | // Set debug mode so a step is always displayed
67 | + (void)setDebug;
68 |
69 |
70 | // Set hidden mode, prevents all tutorial steps from displaying
71 | + (void)setHidden:(BOOL)hidden;
72 |
73 |
74 | // Set tutorial elements presentation delay, defaults to 0
75 | + (void)setPresentationDelay:(NSUInteger)delay;
76 |
77 |
78 | // Sets the universal should present tutorial elements block
79 | + (void)shouldPresentElementsWithBlock:(shouldPresent)block;
80 |
81 |
82 | // Returns the tutorial step corresponding the given key. If no object is found for the given key,
83 | // nil is returned instead.
84 | + (id)tutorialStepForKey:(NSString *)aKey;
85 |
86 |
87 | // Returns the tutorial element corresponding the given key. If no object is found for the given key,
88 | // nil is returned instead.
89 | + (id)tutorialElementForKey:(NSString *)aKey;
90 |
91 |
92 | // Returns the current tutorial step or nil if not found
93 | + (DNTutorialStep *)currentStep;
94 |
95 | // Used for screen rotation
96 | + (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
97 |
98 | // Used for tutorial gestures
99 | + (void)touchesBegan:(CGPoint)touchPoint inView:(UIView *)view;
100 | + (void)touchesMoved:(CGPoint)touchPoint destinationSize:(CGSize)touchSize;
101 | + (void)touchesEnded:(CGPoint)touchPoint destinationSize:(CGSize)touchSize;
102 | + (void)touchesCancelled:(CGPoint)touchPoint inView:(UIView *)view;
103 |
104 |
105 | + (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
106 | + (void)scrollViewDidScroll:(UIScrollView *)scrollView;
107 | + (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
108 |
109 | @end
110 |
111 | @protocol DNTutorialDelegate
112 |
113 | @optional
114 | - (void)willDismissView:(DNTutorialElement *)view;
115 | - (void)didDismissView:(DNTutorialElement *)view;
116 |
117 | - (BOOL)shouldPresentStep:(DNTutorialStep *)step forKey:(NSString *)aKey;
118 | - (BOOL)shouldDismissStep:(DNTutorialStep *)step forKey:(NSString *)aKey;
119 |
120 | - (BOOL)shouldAnimateStep:(DNTutorialStep *)step forKey:(NSString *)aKey;
121 |
122 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
123 |
124 | @end
125 |
126 | @interface DNTutorialDictionary : NSObject
127 |
128 | @property (nonatomic, strong) NSMutableDictionary *dictionary;
129 |
130 | + (instancetype)dictionary;
131 | - (instancetype)initWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)count;
132 |
133 | - (NSUInteger)count;
134 | - (id)objectForKey:(id)aKey;
135 | - (NSEnumerator *)keyEnumerator;
136 | - (void)setObject:(id)anObject forKey:(id < NSCopying >)aKey;
137 | - (void)removeObjectForKey:(id)aKey;
138 |
139 | - (void)controller:(NSString *)aController setObject:(id)anObject forKey:(id)aKey;
140 | - (id)controller:(NSString *)aController getObjectforKey:(id)aKey;
141 | - (void)controller:(NSString *)aController setCompletion:(BOOL)completion forElement:(id)aKey;
142 |
143 | - (void)removeAllObjects;
144 |
145 | @end
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorial.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorial.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNTutorial.h"
10 |
11 | NSString* const sUserDefaultsKey = @"DNTutorialDefaults";
12 |
13 | NSString* const sTutorialObjectsCountKey = @"tutorialObjectCount";
14 | NSString* const sTutorialRemainingCountKey = @"tutorialRemainingCount";
15 | NSString* const sTutorialElementsKey = @"tutorialSteps";
16 |
17 | NSInteger const sTutorialTrackingDistance = 100;
18 |
19 | @interface DNTutorial()
20 |
21 | @property (nonatomic, copy) shouldPresent shouldPresentBlock;
22 | @property (nonatomic, assign) NSUInteger presentationDelay;
23 | @property (nonatomic, weak) id delegate;
24 | @property (nonatomic, strong) UIView *parentView;
25 |
26 | @property (nonatomic, strong) NSMutableArray *tutorialSteps;
27 | @property (nonatomic, strong) DNTutorialStep *currentStep;
28 | @property (nonatomic, assign) BOOL hidden;
29 | @property (nonatomic) NSValue *initialGesturePoint;
30 |
31 | @property (nonatomic, strong) DNTutorialDictionary *userDefaults;
32 |
33 | @end
34 |
35 | @implementation DNTutorial
36 |
37 | #pragma mark --
38 | #pragma mark - Initiation
39 | #pragma mark --
40 |
41 | + (DNTutorial *)sharedInstance;
42 | {
43 | static DNTutorial *appTutorial = nil;
44 | if (appTutorial == nil)
45 | {
46 | static dispatch_once_t onceToken;
47 | dispatch_once(&onceToken, ^{
48 | appTutorial = [[DNTutorial alloc] init];
49 | appTutorial.tutorialSteps = [NSMutableArray array];
50 | appTutorial.userDefaults = [DNTutorialDictionary dictionary];
51 | appTutorial.presentationDelay = 0;
52 | });
53 | }
54 |
55 | return appTutorial;
56 | }
57 |
58 | #pragma mark --
59 | #pragma mark Public Methods
60 | #pragma mark --
61 |
62 | + (void)presentTutorialWithSteps:(NSArray *)tutorialSteps
63 | inView:(UIView *)aView
64 | delegate:(id)delegate;
65 | {
66 | // Cannot init a null tutorial
67 | NSAssert(tutorialSteps != nil, @"AppTutorial: Cannot presnet tutorial with nil objetcs");
68 | NSAssert(aView != nil, @"AppTutorial: Cannot presnet tutorial with nil view");
69 | NSAssert(delegate != nil, @"AppTutorial: Cannot presnet tutorial with nil delegate");
70 |
71 | // Retrive DNTutorial instance
72 | DNTutorial *tutorial = [DNTutorial sharedInstance];
73 |
74 | // Check if should present
75 | if (tutorial.hidden)
76 | {
77 | return;
78 | }
79 |
80 | // Remember tutorial objects
81 | tutorial.tutorialSteps = [tutorialSteps mutableCopy];
82 | tutorial.delegate = delegate;
83 | tutorial.parentView = aView;
84 |
85 | // Restore state
86 | [tutorial loadData];
87 |
88 | if ([tutorial.tutorialSteps count] == 0)
89 | {
90 | // Nothing to present dismiss
91 | return;
92 | }
93 |
94 | [tutorial presentTutorialStep:tutorial.tutorialSteps[0] inView:aView];
95 | }
96 |
97 | + (void)showTutorial;
98 | {
99 | // Retrive DNTutorial instance
100 | DNTutorial *tutorial = [DNTutorial sharedInstance];
101 |
102 | if ([tutorial.tutorialSteps count] == 0 || tutorial.currentStep != nil)
103 | return;
104 |
105 | [tutorial presentTutorialStep:tutorial.tutorialSteps[0] inView:tutorial.parentView];
106 | }
107 |
108 | + (void)hideTutorial;
109 | {
110 | // Retrive DNTutorial instance
111 | DNTutorial *tutorial = [DNTutorial sharedInstance];
112 |
113 | // Dequeue current step
114 | if (tutorial.currentStep == nil)
115 | return;
116 |
117 | // Enqueue current step
118 | [tutorial.tutorialSteps insertObject:tutorial.currentStep atIndex:0];
119 |
120 | // Hide it
121 | [tutorial.currentStep hideElements];
122 | tutorial.currentStep = nil;
123 | }
124 |
125 | + (void)presentStepForKey:(NSString *)aKey;
126 | {
127 | NSAssert(aKey != nil, @"AppTutorial: Cannot present tutorial with nil key");
128 |
129 | // Retrive DNTutorial instance
130 | DNTutorial *tutorial = [DNTutorial sharedInstance];
131 |
132 | DNTutorialStep *step = [tutorial tutorialStepForKey:aKey];
133 |
134 | if (step == nil || tutorial.currentStep != nil) {
135 | return;
136 | }
137 |
138 | [tutorial presentTutorialStep:step inView:tutorial.parentView];
139 | }
140 |
141 | + (void)hideStepForKey:(NSString *)aKey;
142 | {
143 | NSAssert(aKey != nil, @"AppTutorial: Cannot hide tutorial with nil key");
144 |
145 | // Retrive DNTutorial instance
146 | DNTutorial *tutorial = [DNTutorial sharedInstance];
147 |
148 | DNTutorialStep *step = [tutorial tutorialStepForKey:aKey];
149 |
150 | if (step == nil) {
151 | return;
152 | }
153 |
154 | // Hide it
155 | [step hideElements];
156 | }
157 |
158 | + (void)completedStepForKey:(NSString *)aKey;
159 | {
160 | NSAssert(aKey != nil, @"AppTutorial: Cannot complete step with nil key");
161 |
162 | // Retrive DNTutorial instance
163 | DNTutorial *tutorial = [DNTutorial sharedInstance];
164 |
165 | // Complete approprate step
166 | DNTutorialStep *toComplete = nil;
167 |
168 | for (DNTutorialStep *step in tutorial.tutorialSteps)
169 | {
170 | if ([step.key isEqualToString:aKey])
171 | {
172 | toComplete = step;
173 | break;
174 | }
175 | }
176 |
177 | if (toComplete == nil)
178 | {
179 | toComplete = tutorial.currentStep;
180 | }
181 |
182 | if (toComplete != nil && [toComplete.key isEqualToString:aKey])
183 | {
184 | [toComplete setCompleted:YES];
185 |
186 | // Save state
187 | if (tutorial.delegate != nil)
188 | {
189 | [tutorial.userDefaults controller:[tutorial currentController] setCompletion:YES forElement:toComplete.key];
190 | }
191 | }
192 | }
193 |
194 | + (void)resetProgress;
195 | {
196 | // Retrive DNTutorial instance
197 | DNTutorial *tutorial = [DNTutorial sharedInstance];
198 | [tutorial.userDefaults removeAllObjects];
199 |
200 | // Reset progress of current objects
201 | for (DNTutorialStep *step in tutorial.tutorialSteps)
202 | {
203 | [step setPercentageCompleted:0.0f];
204 | [step setCompleted:NO];
205 | }
206 | }
207 |
208 | + (void)setDebug;
209 | {
210 | [DNTutorial resetProgress];
211 | }
212 |
213 | + (void)setHidden:(BOOL)hidden;
214 | {
215 | // Retrive DNTutorial instance
216 | DNTutorial *tutorial = [DNTutorial sharedInstance];
217 |
218 | tutorial.hidden = hidden;
219 | }
220 |
221 | + (void)setPresentationDelay:(NSUInteger)delay;
222 | {
223 | // Retrive DNTutorial instance
224 | DNTutorial *tutorial = [DNTutorial sharedInstance];
225 |
226 | tutorial.presentationDelay = delay;
227 | }
228 |
229 | + (void)shouldPresentElementsWithBlock:(shouldPresent)block;
230 | {
231 | // Retrive DNTutorial instance
232 | DNTutorial *tutorial = [DNTutorial sharedInstance];
233 |
234 | tutorial.shouldPresentBlock = block;
235 | }
236 |
237 | + (void)touchesBegan:(CGPoint)touchPoint inView:(UIView *)view;
238 | {
239 | // Retrive DNTutorial instance
240 | DNTutorial *tutorial = [DNTutorial sharedInstance];
241 | [tutorial swipeBegan:DNTutorialActionSwipeGesture withPoint:touchPoint];
242 | }
243 |
244 | + (void)touchesMoved:(CGPoint)touchPoint destinationSize:(CGSize)touchSize;
245 | {
246 | // Retrive DNTutorial instance
247 | DNTutorial *tutorial = [DNTutorial sharedInstance];
248 | [tutorial swipeMoved:DNTutorialActionSwipeGesture withPoint:touchPoint size:touchSize];
249 | }
250 |
251 | + (void)touchesEnded:(CGPoint)touchPoint destinationSize:(CGSize)touchSize;
252 | {
253 | // Retrive DNTutorial instance
254 | DNTutorial *tutorial = [DNTutorial sharedInstance];
255 | [tutorial swipeEnded:DNTutorialActionSwipeGesture withPoint:touchPoint size:touchSize];
256 | }
257 |
258 | + (void)touchesCancelled:(CGPoint)touchPoint inView:(UIView *)view;
259 | {
260 | // Retrive DNTutorial instance
261 | DNTutorial *tutorial = [DNTutorial sharedInstance];
262 | [tutorial swipeEnded:DNTutorialActionSwipeGesture withPoint:touchPoint size:view.bounds.size];
263 | }
264 |
265 | + (id)tutorialStepForKey:(NSString *)aKey;
266 | {
267 | // Retrive DNTutorial instance
268 | DNTutorial *tutorial = [DNTutorial sharedInstance];
269 |
270 | for (DNTutorialStep *step in tutorial.tutorialSteps)
271 | {
272 | if ([step.key isEqualToString:aKey])
273 | {
274 | return step;
275 | }
276 | }
277 |
278 | return nil;
279 | }
280 |
281 | + (id)tutorialElementForKey:(NSString *)aKey;
282 | {
283 | NSAssert(aKey != nil, @"AppTutorial: Cannot get tutorial element with nil key");
284 |
285 | // Retrive DNTutorial instance
286 | DNTutorial *tutorial = [DNTutorial sharedInstance];
287 |
288 | DNTutorialElement *element = nil;
289 |
290 | for (DNTutorialStep *step in tutorial.tutorialSteps)
291 | {
292 | element = [step tutorialElementForKey:aKey];
293 |
294 | if (element)
295 | {
296 | return element;
297 | }
298 | }
299 |
300 | return nil;
301 | }
302 |
303 | + (DNTutorialStep *)currentStep;
304 | {
305 | // Retrive DNTutorial instance
306 | DNTutorial *tutorial = [DNTutorial sharedInstance];
307 |
308 | return tutorial.currentStep;
309 | }
310 |
311 | #pragma mark --
312 | #pragma mark Screen rotation
313 | #pragma mark --
314 |
315 | + (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
316 | {
317 | // Retrive DNTutorial instance
318 | DNTutorial *tutorial = [DNTutorial sharedInstance];
319 | [tutorial.currentStep willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
320 | }
321 |
322 | #pragma mark --
323 | #pragma mark Gesture recognizers
324 | #pragma mark --
325 |
326 | - (void)swipeBegan:(DNTutorialAction)action withPoint:(CGPoint)point;
327 | {
328 | if (self.currentStep == nil || self.initialGesturePoint != nil)
329 | return;
330 |
331 | // Stop Animating
332 | [self.currentStep stopAnimating];
333 |
334 | // Register initial position
335 | NSArray *tutorialElements = [self.currentStep tutorialElementsWithAction:action | DNTutorialActionTapGesture];
336 |
337 | if ([tutorialElements count] == 0)
338 | {
339 | // Ignore
340 | return;
341 | }
342 |
343 | // Register initial point
344 | self.initialGesturePoint = [NSValue valueWithCGPoint:point];
345 | }
346 |
347 | - (void)swipeMoved:(DNTutorialAction)action withPoint:(CGPoint)point size:(CGSize)size;
348 | {
349 | if (self.currentStep == nil)
350 | return;
351 |
352 | // Should base on direction of current presenting gesture action
353 | NSArray *tutorialElements = [self.currentStep tutorialElementsWithAction:(action)];
354 |
355 | if ([tutorialElements count] == 0)
356 | {
357 | // Ignore
358 | return;
359 | }
360 |
361 | CGFloat delta = 0.0f;
362 |
363 | // Calculate delta based on target direction
364 | for (DNTutorialElement *tutorialElement in tutorialElements)
365 | {
366 | if ([self.currentStep tutorialElement:tutorialElement respondsToActions:action])
367 | {
368 | delta = [self point:point deltaPositionForElement:tutorialElement withSize:size];
369 | break;
370 | }
371 | }
372 |
373 | // Track current position in relation to initial point
374 | [self.currentStep setPercentageCompleted:delta];
375 |
376 | if (delta >= 1.0) {
377 | self.initialGesturePoint = nil;
378 | }
379 | }
380 |
381 | - (void)swipeEnded:(DNTutorialAction)action withPoint:(CGPoint)point size:(CGSize)size;
382 | {
383 | if (self.currentStep == nil)
384 | return;
385 |
386 | // Check if currently presenting a gesture animation
387 | NSArray *tutorialElements = [self.currentStep tutorialElementsWithAction:action | DNTutorialActionTapGesture];
388 |
389 | CGFloat delta = 0.0f;
390 |
391 | for (DNTutorialElement *tutorialElement in tutorialElements)
392 | {
393 | if ([self.currentStep tutorialElement:tutorialElement respondsToActions:action])
394 | {
395 | delta = [self point:point deltaPositionForElement:tutorialElement withSize:size];
396 | break;
397 | }
398 | }
399 |
400 | // Start animating
401 | if (delta < 1.0)
402 | {
403 | self.initialGesturePoint = nil;
404 |
405 | [self.currentStep startAnimating];
406 |
407 | [self.currentStep setPercentageCompleted:0];
408 | }
409 | }
410 |
411 | #pragma mark --
412 | #pragma mark UIScrollView
413 | #pragma mark --
414 |
415 | + (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
416 | {
417 | // Retrive DNTutorial instance
418 | DNTutorial *tutorial = [DNTutorial sharedInstance];
419 | [tutorial swipeBegan:DNTutorialActionScroll withPoint:scrollView.contentOffset];
420 | }
421 |
422 | + (void)scrollViewDidScroll:(UIScrollView *)scrollView;
423 | {
424 | // Retrive DNTutorial instance
425 | DNTutorial *tutorial = [DNTutorial sharedInstance];
426 | [tutorial swipeMoved:DNTutorialActionScroll withPoint:scrollView.contentOffset size:scrollView.bounds.size];
427 | }
428 |
429 | + (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
430 | {
431 | // Retrive DNTutorial instance
432 | DNTutorial *tutorial = [DNTutorial sharedInstance];
433 | [tutorial swipeEnded:DNTutorialActionScroll withPoint:scrollView.contentOffset size:scrollView.bounds.size];
434 | }
435 |
436 | - (CGFloat)point:(CGPoint)point deltaPositionForElement:(DNTutorialElement *)tutorialElement withSize:(CGSize)size;
437 | {
438 | CGFloat delta = 0.0f;
439 | CGPoint initialPoint = [self.initialGesturePoint CGPointValue];
440 |
441 | DNTutorialGestureType direction = [(DNTutorialGesture *)tutorialElement gestureType];
442 |
443 | switch (direction) {
444 | case DNTutorialGestureTypeSwipeUp:
445 | delta = (point.y - initialPoint.y)/size.height;
446 | break;
447 | case DNTutorialGestureTypeSwipeRight:
448 | delta = (point.x - initialPoint.x)/size.width;
449 | break;
450 | case DNTutorialGestureTypeSwipeDown:
451 | delta = (initialPoint.y - point.y)/size.height;
452 | break;
453 | case DNTutorialGestureTypeSwipeLeft:
454 | delta = (initialPoint.x - point.x)/size.width;
455 | break;
456 | case DNTutorialGestureTypeScrollUp:
457 | delta = (point.y - initialPoint.y)/size.height;
458 | break;
459 | case DNTutorialGestureTypeScrollRight:
460 | delta = (initialPoint.x - point.x)/size.width;
461 | break;
462 | case DNTutorialGestureTypeScrollDown:
463 | delta = (initialPoint.y - point.y)/size.height;
464 | break;
465 | case DNTutorialGestureTypeScrollLeft:
466 | delta = (point.x - initialPoint.x)/size.width;
467 | break;
468 |
469 | default:
470 | break;
471 | }
472 |
473 | return delta;
474 | }
475 |
476 | #pragma mark --
477 | #pragma mark Private Methods
478 | #pragma mark --
479 |
480 | - (void)loadData;
481 | {
482 | // Load data from user defaults
483 | NSUInteger objectCount = [[self.userDefaults controller:[self currentController] getObjectforKey:sTutorialObjectsCountKey] integerValue];
484 |
485 | // If no data found, save it
486 | if (objectCount == 0)
487 | {
488 | // Save current
489 | objectCount = [self.tutorialSteps count];
490 | [self.userDefaults controller:[self currentController] setObject:@(objectCount) forKey:sTutorialObjectsCountKey];
491 | [self.userDefaults controller:[self currentController] setObject:@(objectCount) forKey:sTutorialRemainingCountKey];
492 | }
493 |
494 | // Advance tutorial
495 | if (objectCount != [_tutorialSteps count])
496 | {
497 | NSLog(@"DNTutorial: Detected different number of banners being presented than those previously saved.");
498 | }
499 |
500 | // Advance tutorial
501 | NSDictionary *elementsDict = [self.userDefaults controller:[self currentController] getObjectforKey:sTutorialElementsKey];
502 |
503 | [elementsDict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL* stop)
504 | {
505 | for (DNTutorialStep *step in [self.tutorialSteps copy])
506 | {
507 | if ([key isEqualToString:step.key] && [value boolValue])
508 | {
509 | [self.tutorialSteps removeObject:step];
510 | }
511 | }
512 | }];
513 | }
514 |
515 | - (void)saveData;
516 | {
517 | NSUInteger remainingCount = [self.tutorialSteps count];
518 |
519 | if (!self.currentStep.isCompleted)
520 | {
521 | remainingCount++;
522 | }
523 |
524 | // Amount remaining
525 | [self.userDefaults controller:[self currentController] setObject:@(remainingCount) forKey:sTutorialRemainingCountKey];
526 | }
527 |
528 | - (void)presentTutorialStep:(DNTutorialStep *)step inView:(UIView *)aView;
529 | {
530 | // Check if can present step
531 | if (self.currentStep == step)
532 | {
533 | return;
534 | }
535 |
536 | // Check for global variable
537 | if (self.shouldPresentBlock && !self.shouldPresentBlock())
538 | {
539 | [self skipTutorialStep:step];
540 | return;
541 | }
542 |
543 | BOOL shouldPresent = YES;
544 |
545 | if ([_delegate respondsToSelector:@selector(shouldPresentStep:forKey:)])
546 | {
547 | shouldPresent = [_delegate shouldPresentStep:step forKey:step.key];
548 | }
549 |
550 | if (step.isCompleted)
551 | {
552 | shouldPresent = NO;
553 | }
554 |
555 | if (!shouldPresent)
556 | {
557 | // Skip current step
558 | [self skipTutorialStep:step];
559 | return;
560 | }
561 |
562 | // Present step
563 | [step setDelegate:self];
564 |
565 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.presentationDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
566 | // Present
567 | [step showInView:aView];
568 | });
569 |
570 | // Current object
571 | self.currentStep = step;
572 |
573 | // Dequeue
574 | [self.tutorialSteps removeObjectAtIndex:0];
575 | }
576 |
577 | - (void)hideTutorialStep:(DNTutorialStep *)step;
578 | {
579 | [self skipTutorialStep:step];
580 | }
581 |
582 | - (void)skipTutorialStep:(DNTutorialStep *)step;
583 | {
584 | // Save step for later
585 | if (_delegate)
586 | {
587 | [self.userDefaults controller:[self currentController] setCompletion:NO forElement:step.key];
588 | }
589 |
590 | // Dequeue
591 | [self.tutorialSteps removeObjectAtIndex:0];
592 |
593 | // Advance sequence
594 | if ([self.tutorialSteps count] > 0)
595 | {
596 | // Next
597 | [self presentTutorialStep:self.tutorialSteps[0] inView:self.parentView];
598 | }
599 |
600 | // Add it to the end of queue
601 | [self.tutorialSteps addObject:step];
602 | }
603 |
604 | - (void)setDelegate:(id)delegate
605 | {
606 | if (_delegate == delegate)
607 | return;
608 |
609 | // Reset current step
610 | _delegate = delegate;
611 | _currentStep = nil;
612 | }
613 |
614 | - (NSString *)currentController
615 | {
616 |
617 | return NSStringFromClass([self.delegate class]);
618 | }
619 |
620 | - (BOOL)containsStepForKey:(NSString *)key
621 | {
622 | // Check for key present in presenting queue
623 | return [self tutorialStepForKey:key] != nil;
624 | }
625 |
626 | - (id)tutorialStepForKey:(NSString *)key
627 | {
628 | // Iterate objects and check for present key
629 | for (DNTutorialStep *tutorialStep in self.tutorialSteps)
630 | {
631 | if ([tutorialStep.key isEqualToString:key])
632 | {
633 | return tutorialStep;
634 | }
635 | }
636 |
637 | // Nothing found
638 | return nil;
639 | }
640 |
641 | #pragma mark --
642 | #pragma mark Step Delegate Methods
643 | #pragma mark --
644 |
645 | - (void)willDismissStep:(DNTutorialStep *)tutorialStep;
646 | {
647 | // Save state
648 | if (_delegate != nil)
649 | {
650 | [self.userDefaults controller:[self currentController] setCompletion:tutorialStep.isCompleted forElement:tutorialStep.key];
651 |
652 | [self saveData];
653 | }
654 | }
655 |
656 | - (void)didDismissStep:(DNTutorialStep *)tutorialStep;
657 | {
658 | // Check if there are more steps to present and if so show it.
659 | self.currentStep = nil;
660 |
661 | if ([self.tutorialSteps count] == 0)
662 | {
663 | return;
664 | }
665 |
666 | // Present next step
667 | [self presentTutorialStep:self.tutorialSteps[0] inView:self.parentView];
668 | }
669 |
670 | - (BOOL)shouldDismissStep:(DNTutorialStep *)step;
671 | {
672 | BOOL toReturn = YES;
673 |
674 | if ([_delegate respondsToSelector:@selector(shouldDismissStep:forKey:)])
675 | {
676 | toReturn = [_delegate shouldDismissStep:step forKey:step.key];
677 | }
678 |
679 | return toReturn;
680 | }
681 |
682 | - (BOOL)shouldAnimateStep:(DNTutorialStep *)step;
683 | {
684 | BOOL toReturn = YES;
685 |
686 | if ([_delegate respondsToSelector:@selector(shouldAnimateStep:forKey:)])
687 | {
688 | toReturn = [_delegate shouldAnimateStep:step forKey:step.key];
689 | }
690 |
691 | return toReturn;
692 | }
693 |
694 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
695 | {
696 | if ([_delegate respondsToSelector:@selector(willAnimateElement:toInterfaceOrientation:duration:)])
697 | {
698 | [_delegate willAnimateElement:element toInterfaceOrientation:toInterfaceOrientation duration:duration];
699 | }
700 | }
701 |
702 | @end
703 |
704 | #pragma mark --
705 | #pragma mark DNTutorialDictionary Subclass
706 | #pragma mark --
707 |
708 | @implementation DNTutorialDictionary
709 |
710 | + (instancetype)dictionary;
711 | {
712 | DNTutorialDictionary *tutorialDictionary = [DNTutorialDictionary new];
713 | [tutorialDictionary setupWithObjects:nil forKeys:nil count:0];
714 | return tutorialDictionary;
715 | }
716 |
717 | - (instancetype)initWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)count;
718 | {
719 | DNTutorialDictionary *tutorialDictionary = [DNTutorialDictionary new];
720 | [tutorialDictionary setupWithObjects:objects forKeys:keys count:count];
721 | return tutorialDictionary;
722 | }
723 |
724 | - (void)setupWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)count;
725 | {
726 | self.dictionary = [NSMutableDictionary dictionaryWithObjects:objects forKeys:keys count:count];
727 |
728 | // Populate data from user defaults
729 | if ([[NSUserDefaults standardUserDefaults] dictionaryForKey:sUserDefaultsKey] != nil)
730 | {
731 | self.dictionary = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:sUserDefaultsKey] mutableCopy];
732 | }
733 | }
734 |
735 | - (NSUInteger)count;
736 | {
737 | return [_dictionary count];
738 | }
739 |
740 | - (id)objectForKey:(id)aKey;
741 | {
742 | return [_dictionary objectForKey:aKey];
743 | }
744 |
745 | - (NSEnumerator *)keyEnumerator;
746 | {
747 | return [_dictionary keyEnumerator];
748 | }
749 |
750 | - (void)setObject:(id)anObject forKey:(id < NSCopying >)aKey;
751 | {
752 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot get dictionary for a nil key");
753 |
754 | [_dictionary setObject:anObject forKey:aKey];
755 |
756 | [[NSUserDefaults standardUserDefaults] setObject:_dictionary forKey:sUserDefaultsKey];
757 | [[NSUserDefaults standardUserDefaults] synchronize];
758 | }
759 |
760 | - (void)removeObjectForKey:(id)aKey;
761 | {
762 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot get dictionary for a nil key");
763 |
764 | [_dictionary removeObjectForKey:aKey];
765 |
766 | [[NSUserDefaults standardUserDefaults] setObject:_dictionary forKey:sUserDefaultsKey];
767 | [[NSUserDefaults standardUserDefaults] synchronize];
768 | }
769 |
770 | - (void)controller:(NSString *)aController setObject:(id)anObject forKey:(id)aKey;
771 | {
772 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot get dictionary for a nil key");
773 |
774 | NSMutableDictionary *controllerDictionary = [self dictionaryForController:aController];
775 | [controllerDictionary setObject:anObject forKey:aKey];
776 | [self setObject:controllerDictionary forKey:aController];
777 | }
778 |
779 | - (id)controller:(NSString *)aController getObjectforKey:(id)aKey;
780 | {
781 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot get dictionary for a nil key");
782 |
783 | NSMutableDictionary *controllerDictionary = [self dictionaryForController:aController];
784 | return [controllerDictionary objectForKey:aKey];
785 | }
786 |
787 | - (void)controller:(NSString *)aController setCompletion:(BOOL)completion forElement:(id)aKey;
788 | {
789 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot set dictionary for a nil element.");
790 | NSAssert(aController != nil, @"DNTutorialTutorial: Cannot set dictionary for a nil controller, this generaly occurs when the tutorial delegate is nil.");
791 |
792 | NSMutableDictionary *controllerDictionary = [self dictionaryForController:aController];
793 | NSMutableDictionary *elementsDictionary = [controllerDictionary[sTutorialElementsKey] mutableCopy];
794 | [elementsDictionary setObject:@(completion) forKey:aKey];
795 | [controllerDictionary setObject:elementsDictionary forKey:sTutorialElementsKey];
796 |
797 | [_dictionary setObject:controllerDictionary forKey:aController];
798 | [[NSUserDefaults standardUserDefaults] setObject:_dictionary forKey:sUserDefaultsKey];
799 | }
800 |
801 | - (BOOL)controller:(NSString *)aController getCompletionforElement:(id)aKey;
802 | {
803 | NSAssert(aKey != nil, @"DNTutorialTutorial: Cannot set dictionary for a nil element.");
804 | NSAssert(aController != nil, @"DNTutorialTutorial: Cannot set dictionary for a nil controller, this generaly occurs when the tutorial delegate is nil.");
805 |
806 | NSMutableDictionary *controllerDictionary = [self dictionaryForController:aController];
807 | return [[controllerDictionary objectForKey:controllerDictionary[sTutorialElementsKey]] boolValue];
808 | }
809 |
810 | - (NSMutableDictionary *)dictionaryForController:(NSString *)aController;
811 | {
812 | // Check for existing entry in user defaults dictionary, create one if needed
813 | NSMutableDictionary *controllerDictionary = nil;
814 |
815 | if (_dictionary[aController] == nil)
816 | {
817 | controllerDictionary = [NSMutableDictionary dictionary];
818 | [controllerDictionary setObject:[NSDictionary dictionary] forKey:sTutorialElementsKey];
819 | [_dictionary setObject:controllerDictionary forKey:aController];
820 | }
821 | else
822 | {
823 | controllerDictionary = [_dictionary[aController] mutableCopy];
824 | }
825 |
826 | return controllerDictionary;
827 | }
828 |
829 | - (void)removeAllObjects;
830 | {
831 | // Remove all objects
832 | [_dictionary removeAllObjects];
833 | [[NSUserDefaults standardUserDefaults] setObject:_dictionary forKey:sUserDefaultsKey];
834 | [[NSUserDefaults standardUserDefaults] synchronize];
835 | }
836 |
837 | @end
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialAudio.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialAudio.h
3 | // Pods
4 | //
5 | // Created by Daniel Niemeyer on 3/31/15.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 |
12 | #import "DNTutorialElement.h"
13 |
14 | @interface DNTutorialAudio : DNTutorialElement
15 |
16 | // Instantiate a new audio tutorial with the given audio URL
17 | + (id)audioWithURL:(NSURL *)URL
18 | key:(NSString *)key;
19 |
20 | // Instantiate a new audio tutorial with the given audio file
21 | + (id)audioWithPath:(NSString *)path
22 | ofType:(NSString *)type
23 | key:(NSString *)key;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialAudio.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialAudio.m
3 | // Pods
4 | //
5 | // Created by Daniel Niemeyer on 3/31/15.
6 | //
7 | //
8 |
9 | #import "DNTutorialAudio.h"
10 |
11 | @interface DNTutorialAudio()
12 |
13 | @property (nonatomic, strong) NSURL *URL;
14 | @property (nonatomic, strong) AVAudioPlayer *audioPlayer;
15 |
16 | @end
17 |
18 | @implementation DNTutorialAudio
19 |
20 | #pragma mark --
21 | #pragma mark - Initialization
22 | #pragma mark --
23 |
24 | + (id)audioWithURL:(NSURL *)URL
25 | key:(NSString *)key;
26 | {
27 | // Proper initialization
28 | NSAssert(URL != nil, @"DNTutorialAudio: Cannot initialize action with no URL");
29 | NSAssert(key != nil, @"DNTutorialAudio: Cannot initialize action with invalid key");
30 |
31 | DNTutorialAudio *audio = [DNTutorialAudio new];
32 | audio.key = key;
33 | audio.URL = URL;
34 | return audio;
35 | }
36 |
37 | + (id)audioWithPath:(NSString *)path
38 | ofType:(NSString *)type
39 | key:(NSString *)key;
40 | {
41 | // Proper initialization
42 | NSAssert(path != nil, @"DNTutorialAudio: Cannot initialize action with no path");
43 | NSAssert(type != nil, @"DNTutorialAudio: Cannot initialize action with no type");
44 | NSAssert(key != nil, @"DNTutorialAudio: Cannot initialize action with invalid key");
45 |
46 | DNTutorialAudio *audio = [DNTutorialAudio new];
47 |
48 | @try {
49 | NSURL *URL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:path ofType:type]];
50 | audio.URL = URL;
51 | }
52 | @catch (NSException *exception) {
53 | NSLog(@"DNTutorialAudio: Cannot locate audio file!");
54 | }
55 |
56 | audio.key = key;
57 | return audio;
58 | }
59 |
60 | #pragma mark --
61 | #pragma mark - Polimorphic Methods
62 | #pragma mark --
63 |
64 | - (void)setUpInView:(UIView *)aView;
65 | {
66 | // Initialize audio player
67 | NSError *error;
68 | AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.URL error:&error];
69 | self.audioPlayer = audioPlayer;
70 |
71 | // Check for errors
72 | if (error)
73 | {
74 | NSLog(@"DNTutorialAudio: Error in audioPlayer: %@", [error localizedDescription]);
75 | return;
76 | }
77 |
78 | // Prepare to play
79 | audioPlayer.delegate = self;
80 | [audioPlayer prepareToPlay];
81 | }
82 |
83 | - (void)tearDown;
84 | {
85 | [self.audioPlayer stop];
86 | }
87 |
88 | - (void)show;
89 | {
90 | _actionCompleted = NO;
91 |
92 | [self.audioPlayer play];
93 | }
94 |
95 | - (void)dismiss;
96 | {
97 | // Will dismiss element
98 | [_delegate willDismissElement:self];
99 |
100 | [self.audioPlayer pause];
101 |
102 | [_delegate didDismissElement:self];
103 | }
104 |
105 | - (void)startAnimating;
106 | {
107 | return;
108 | }
109 |
110 | - (void)stopAnimating;
111 | {
112 | return;
113 | }
114 |
115 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
116 | {
117 | if (_actionCompleted && completed) {
118 | return;
119 | }
120 |
121 | _actionCompleted = completed;
122 |
123 | if (completed)
124 | {
125 | // Should dismiss
126 | [self dismiss];
127 | }
128 | }
129 |
130 | - (void)setPercentageCompleted:(CGFloat)percentage;
131 | {
132 | // percentage alpha and position based on position
133 | if (percentage < 0 || _actionCompleted)
134 | {
135 | return;
136 | }
137 |
138 | _percentageCompleted = percentage;
139 |
140 | if (percentage >= 1.0)
141 | {
142 | // User action completed
143 | [self setCompleted:YES animated:NO];
144 | }
145 | }
146 |
147 | #pragma mark --
148 | #pragma mark - Private Methods
149 | #pragma mark --
150 |
151 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
152 | {
153 |
154 | }
155 |
156 | - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error;
157 | {
158 |
159 | }
160 |
161 | - (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player;
162 | {
163 |
164 | }
165 |
166 | - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player;
167 | {
168 |
169 | }
170 |
171 | @end
172 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialBanner.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialBanner.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | #import "DNTutorialElement.h"
13 |
14 | @interface DNTutorialBanner : DNTutorialElement
15 |
16 | // Public method for instantiating a new DNTutorialBanner with an appropriate message
17 | + (id)bannerWithMessage:(NSString *)message
18 | completionMessage:(NSString *)completionMessage
19 | key:(NSString *)key;
20 |
21 |
22 | // Style banner
23 | - (void)styleWithColor:(UIColor *)color
24 | completedColor:(UIColor *)completedColor
25 | opacity:(CGFloat)opacity
26 | font:(UIFont *)font;
27 |
28 | // Banner background color, defaults to light gray
29 | - (void)setBannerColor:(UIColor *)bannerColor;
30 |
31 | // Banner completed color
32 | - (void)setCompletedColor:(UIColor *)completedColor;
33 |
34 | // Banner alpha, defaults to 0.8
35 | - (void)setBannerOpacity:(CGFloat)opacity;
36 |
37 | // Banner font, defaults to system font of size 17
38 | - (void)setBannerFont:(UIFont *)font;
39 |
40 | // Banner completion delay in seconds, defaults to 2 seconds.
41 | - (void)setCompletedDelay:(NSUInteger)completedDelay;
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialBanner.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialBanner.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNTutorialBanner.h"
10 |
11 | NSInteger const sBannerVisibleHeight = 80;
12 |
13 | @interface DNTutorialBanner()
14 |
15 | @property (nonatomic, weak) CAShapeLayer *circleLayer;
16 | @property (nonatomic, weak) UILabel *messagelabel;
17 | @property (nonatomic, weak) UIButton *closeButton;
18 | @property (nonatomic, strong) UIView *containerView;
19 |
20 | @property (nonatomic, strong) NSString *message;
21 | @property (nonatomic, strong) NSString *completedMessage;
22 | @property (nonatomic, strong,setter = setBannerFont:)UIFont *messageFont;
23 | @property (nonatomic, setter = setBannerColor:) UIColor *backgroundColor;
24 | @property (nonatomic, strong) UIColor *completedColor;
25 | @property (nonatomic, setter = setBannerOpacity:) CGFloat opacity;
26 | @property (nonatomic, assign) NSUInteger completedDelay;
27 |
28 | @end
29 |
30 | @implementation DNTutorialBanner
31 |
32 | #pragma mark --
33 | #pragma mark - Initialization
34 | #pragma mark --
35 |
36 | + (id)bannerWithMessage:(NSString *)message
37 | completionMessage:(NSString *)completionMessage
38 | key:(NSString *)key;
39 | {
40 | // Proper initialization
41 | NSAssert(key != nil, @"DNTutorialGesture: Cannot initialize action with invalid key");
42 |
43 | // Init view
44 | DNTutorialBanner *banner = [DNTutorialBanner new];
45 | banner.opacity = 0.8;
46 | banner.backgroundColor = [UIColor blackColor];
47 | banner.completedColor = [UIColor blueColor];
48 | banner.key = key;
49 | banner.message = message;
50 | banner.completedMessage = completionMessage;
51 | banner.completedDelay = 2.0;
52 | banner.messageFont = [UIFont systemFontOfSize:15];
53 |
54 | return banner;
55 | }
56 |
57 | #pragma mark --
58 | #pragma mark - Polimorphic Methods
59 | #pragma mark --
60 |
61 | - (void)setUpInView:(UIView *)aView;
62 | {
63 | // Initialize container view
64 | UIView *view = [UIView new];
65 |
66 | CGFloat viewHeight = CGRectGetHeight(aView.bounds);
67 | CGFloat viewWidth = CGRectGetWidth(aView.bounds);
68 | CGRect frame = CGRectMake(0, viewHeight, viewWidth, 100);
69 |
70 | // Make sure the new frame doesn't put the view outside the window's bounds
71 | CGRect windowFrame = [[UIScreen mainScreen] bounds];
72 |
73 | if (frame.size.height > CGRectGetHeight(windowFrame))
74 | {
75 | frame.origin.y = CGRectGetHeight(windowFrame);
76 | }
77 |
78 | view.frame = frame;
79 | view.alpha = _opacity;
80 | view.backgroundColor = _backgroundColor;
81 | view.layer.masksToBounds = YES;
82 |
83 | // Shape layer
84 | CAShapeLayer *circleLayer = [CAShapeLayer layer];
85 | circleLayer.fillColor = [self.completedColor CGColor];
86 | circleLayer.opacity = 0.0;
87 | [view.layer addSublayer:circleLayer];
88 | self.circleLayer = circleLayer;
89 |
90 | // Close button
91 | UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
92 | closeButton.frame = CGRectMake(CGRectGetWidth(frame) - 40, 0, 40, sBannerVisibleHeight);
93 | [closeButton setImage:[UIImage imageNamed:@"DNTutorialClose"] forState:UIControlStateNormal];
94 | [closeButton addTarget:self action:@selector(closeAction:) forControlEvents:UIControlEventTouchUpInside];
95 | [view addSubview:closeButton];
96 | self.closeButton = closeButton;
97 |
98 | // Message label
99 | UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, CGRectGetWidth(frame) - CGRectGetWidth(closeButton.bounds) - 10, sBannerVisibleHeight)];
100 | messageLabel.text = self.message;
101 | messageLabel.font = self.messageFont;
102 | messageLabel.textColor = [UIColor whiteColor];
103 | messageLabel.numberOfLines = 0;
104 | messageLabel.lineBreakMode = NSLineBreakByWordWrapping;
105 | [view addSubview:messageLabel];
106 | self.messagelabel = messageLabel;
107 |
108 | // Progress indicator
109 | // CAShapeLayer *progressLayer = [CAShapeLayer layer];
110 | // progressLayer.path = [UIBezierPath bezierPathWithArcCenter:closeButton.center radius:18 startAngle:0 endAngle:0 clockwise:YES].CGPath;
111 | // progressLayer.fillColor = [UIColor clearColor].CGColor;
112 | // progressLayer.strokeColor = [UIColor whiteColor].CGColor;
113 | // progressLayer.lineWidth = 1;
114 | // [view.layer addSublayer:progressLayer];
115 | // self.progressLayer = progressLayer;
116 |
117 | // Add subview
118 | [aView addSubview:view];
119 | _containerView = view;
120 |
121 | // Check completed
122 | if (_actionCompleted)
123 | {
124 | [self setCompleted:YES animated:NO];
125 | }
126 | }
127 |
128 | - (void)tearDown;
129 | {
130 | [_containerView removeFromSuperview];
131 |
132 | _messagelabel = nil;
133 | _closeButton = nil;
134 | _circleLayer = nil;
135 | _containerView = nil;
136 | }
137 |
138 | - (void)show;
139 | {
140 | _actionCompleted = NO;
141 |
142 | // Animate entrance
143 | [UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0.7 options:UIViewAnimationOptionCurveEaseInOut
144 | animations:^{
145 | _containerView.frame = CGRectOffset(_containerView.frame, 0, -sBannerVisibleHeight);
146 | }
147 | completion:^(BOOL finished) {
148 |
149 | }];
150 | }
151 |
152 | - (void)dismiss;
153 | {
154 | // Check if already dismissed
155 | if (!_containerView.superview) {
156 | return;
157 | }
158 |
159 | // Notify delegate of dismissal
160 | [_delegate willDismissElement:self];
161 |
162 | // Animate removal
163 | [UIView animateWithDuration:0.2 animations:^{
164 | _containerView.frame = CGRectOffset(_containerView.frame, 0, CGRectGetHeight(_containerView.frame));
165 | } completion:^(BOOL finished)
166 | {
167 | [_delegate didDismissElement:self];
168 | }];
169 |
170 | }
171 |
172 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
173 | {
174 | // Animate to completed state
175 | if (_actionCompleted && completed && animated) {
176 | return;
177 | }
178 |
179 | _actionCompleted = completed;
180 |
181 | if (completed)
182 | {
183 | if (animated) {
184 | [self animateToCompletedState];
185 | }
186 | else
187 | {
188 | _containerView.backgroundColor = _completedColor;
189 | self.circleLayer.opacity = 0.0;
190 | }
191 |
192 | if (_completedMessage != nil && _completedMessage.length > 0)
193 | {
194 | [self.messagelabel setText:_completedMessage];
195 | }
196 | [self.closeButton setImage:[UIImage imageNamed:@"DNTutorialCheck"] forState:UIControlStateNormal];
197 |
198 | // Should dismiss
199 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, self.completedDelay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
200 | if ([_delegate shouldDismissElement:self])
201 | {
202 | [self dismiss];
203 | }
204 | });
205 | }
206 | else
207 | {
208 | _containerView.backgroundColor = _backgroundColor;
209 | self.circleLayer.opacity = 0.0f;
210 | }
211 | }
212 |
213 | - (void)setPercentageCompleted:(CGFloat)percentage;
214 | {
215 | if (percentage < 0 || _actionCompleted)
216 | {
217 | return;
218 | }
219 |
220 | // Load background indicator
221 | _percentageCompleted = percentage;
222 | CGRect frame = CGRectZero;
223 | frame.origin.x = -10.0;
224 | frame.origin.y = CGRectGetHeight(_containerView.bounds) / 2.0;
225 | frame.size.height = 600;
226 | frame.size.width = CGRectGetWidth(_containerView.bounds) * (percentage + 0.1);
227 | frame.origin.y -= CGRectGetHeight(frame)/2.0;
228 |
229 | self.circleLayer.opacity = (percentage*1);
230 | self.circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:frame].CGPath;
231 |
232 | // User action completed
233 | if (percentage >= 1.0)
234 | {
235 | [self setCompleted:YES animated:NO];
236 | }
237 | }
238 |
239 | - (DNTutorialAction)tutorialActions;
240 | {
241 | return DNTutorialActionBanner;
242 | }
243 |
244 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
245 | {
246 | // Redraw
247 | UIView *aView = _containerView.superview;
248 |
249 | CGFloat viewHeight = CGRectGetHeight(aView.bounds) - sBannerVisibleHeight;
250 | CGFloat viewWidth = CGRectGetWidth(aView.bounds);
251 | CGRect frame = CGRectMake(0, viewHeight, viewWidth, 100);
252 |
253 | [UIView animateWithDuration:duration animations:^(void){
254 | _containerView.frame = frame;
255 | _closeButton.frame = CGRectMake(CGRectGetWidth(frame) - 40, 0, 40, sBannerVisibleHeight);
256 | _messagelabel.frame = CGRectMake(10, 0, CGRectGetWidth(frame) - CGRectGetWidth(_closeButton.bounds) - 10, sBannerVisibleHeight);
257 | }];
258 | }
259 |
260 | #pragma mark --
261 | #pragma mark Public Methods
262 | #pragma mark --
263 |
264 | - (void)styleWithColor:(UIColor *)color
265 | completedColor:(UIColor *)completedColor
266 | opacity:(CGFloat)opacity
267 | font:(UIFont *)font;
268 | {
269 | [self setBannerColor:color];
270 | [self setCompletedColor:completedColor];
271 | [self setBannerOpacity:opacity];
272 | [self setBannerFont:font];
273 | }
274 |
275 | - (void)setBannerColor:(UIColor *)bannerColor;
276 | {
277 | _backgroundColor = bannerColor;
278 | _containerView.backgroundColor = bannerColor;
279 | }
280 |
281 | - (void)setCompletedColor:(UIColor *)completedColor
282 | {
283 | _completedColor = completedColor;
284 | _circleLayer.fillColor = [_completedColor CGColor];
285 | }
286 |
287 | - (void)setBannerOpacity:(CGFloat)opacity;
288 | {
289 | _opacity = opacity;
290 | _containerView.alpha = _opacity;
291 | }
292 |
293 | - (void)setBannerFont:(UIFont *)font;
294 | {
295 | _messageFont = font;
296 | _messagelabel.font = font;
297 | }
298 |
299 | - (void)setCompletedDelay:(NSUInteger)completedDelay;
300 | {
301 | _completedDelay = completedDelay;
302 | }
303 |
304 | #pragma mark --
305 | #pragma mark Private Methods
306 | #pragma mark --
307 |
308 | - (void)animateToCompletedState;
309 | {
310 | // Expand circle
311 | [self expandCircleInView:_containerView];
312 |
313 | CGRect endRect = CGRectZero;
314 | endRect.size.height = 400;
315 | endRect.size.width = CGRectGetWidth(_containerView.bounds) * 1.1f;
316 | endRect.origin.x = -10;
317 | endRect.origin.y = -CGRectGetHeight(endRect)/2.0 + CGRectGetMidY(_containerView.bounds);
318 |
319 | self.circleLayer.opacity = 0.8;
320 | self.circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:endRect].CGPath;
321 | }
322 |
323 | - (void)expandCircleInView:(UIView *)view
324 | {
325 |
326 | CGFloat duration = 0.8;
327 | CGRect startRect = self.closeButton.frame;
328 |
329 | CGRect endRect = CGRectZero;
330 | endRect.size.height = 400;
331 | endRect.size.width = CGRectGetWidth(view.bounds) * 1.1f;
332 | endRect.origin.x = -10;
333 | endRect.origin.y = -CGRectGetHeight(endRect)/2.0 + CGRectGetMidY(view.bounds);
334 | [CATransaction begin];
335 |
336 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
337 | pathAnimation.duration = duration;
338 | pathAnimation.fromValue = (id)[[UIBezierPath bezierPathWithOvalInRect:startRect] CGPath];
339 | pathAnimation.toValue = (id)[[UIBezierPath bezierPathWithOvalInRect:endRect] CGPath];
340 | pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
341 | [self.circleLayer addAnimation:pathAnimation forKey:@"path"];
342 |
343 | CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
344 | fadeAnimation.duration = duration/2;
345 | fadeAnimation.autoreverses = YES;
346 | fadeAnimation.fromValue = @(0.0);
347 | fadeAnimation.toValue = @(0.8);
348 | fadeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
349 | [self.circleLayer addAnimation:fadeAnimation forKey:@"opacity"];
350 |
351 | [CATransaction commit];
352 | }
353 |
354 | - (void)setMessage:(NSString *)message;
355 | {
356 | _message = message;
357 | self.messagelabel.text = message;
358 | }
359 |
360 | - (void)closeAction:(id)sender;
361 | {
362 | // Should never show banner again
363 | if (_delegate && [_delegate respondsToSelector:@selector(userDismissedElement:)])
364 | {
365 | [_delegate userDismissedElement:self];
366 | }
367 |
368 | [self dismiss];
369 | }
370 |
371 | @end
372 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialElement.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialElement.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/31/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | typedef NS_OPTIONS (NSUInteger, DNTutorialAction)
13 | {
14 | DNTutorialActionBanner = 1 << 0,
15 | DNTutorialActionSwipeGesture = 1 << 1,
16 | DNTutorialActionTapGesture = 1 << 2,
17 | DNTutorialActionScroll = 1 << 3,
18 | DNTutorialActionAny = ~0UL,
19 | DNTutorialActionNone = 1 << 4
20 | };
21 |
22 | @protocol DNTutorialElementDelegate;
23 |
24 | @interface DNTutorialElement : NSObject
25 | {
26 | @protected
27 | BOOL _actionCompleted;
28 | CGFloat _percentageCompleted;
29 | id _delegate;
30 | }
31 |
32 | @property (nonatomic, strong) NSString *key;
33 |
34 |
35 | // Called when a new tutorial object is instantiated
36 | - (void)setUpInView:(UIView *)aView;
37 |
38 |
39 | // Called when tutorial object is destroyed
40 | - (void)tearDown;
41 |
42 |
43 | // Override this method to customize presentation of tutorial view
44 | - (void)show;
45 |
46 |
47 | // Override this method to dimiss view
48 | - (void)dismiss;
49 |
50 |
51 | // App Tutorial manager delegate
52 | - (void)setDelegate:(id)aDelegate;
53 |
54 |
55 | // User completed action indicated by tutorial object
56 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
57 |
58 |
59 | // Set percentage completed
60 | - (void)setPercentageCompleted:(CGFloat)percentage;
61 |
62 | // Getter for percentage completion
63 | - (CGFloat)percentageCompleted;
64 |
65 | // Getter for tutorial actions
66 | - (DNTutorialAction)tutorialActions;
67 |
68 | // Called when element should animate
69 | - (void)startAnimating;
70 |
71 | // Called when user pauses animations
72 | - (void)stopAnimating;
73 |
74 | // Interface orientation
75 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
76 |
77 | @end
78 |
79 | @protocol DNTutorialElementDelegate
80 | @required
81 |
82 | - (void)willDismissElement:(DNTutorialElement *)element;
83 | - (void)didDismissElement:(DNTutorialElement *)element;
84 |
85 | - (BOOL)shouldDismissElement:(DNTutorialElement *)element;
86 | - (void)userDismissedElement:(DNTutorialElement *)element;
87 |
88 | - (BOOL)shouldAnimateElement:(DNTutorialElement *)element;
89 |
90 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
91 |
92 |
93 | @end
94 |
95 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialElement.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialElement.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/31/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNTutorialElement.h"
10 |
11 |
12 | @implementation DNTutorialElement
13 |
14 | - (void)setUpInView:(UIView *)aView;
15 | {
16 | return;
17 | }
18 |
19 | - (void)tearDown;
20 | {
21 | return;
22 | }
23 |
24 | - (void)show;
25 | {
26 | return;
27 | }
28 |
29 | - (void)dismiss;
30 | {
31 | return;
32 | }
33 |
34 | - (void)setDelegate:(id)aDelegate;
35 | {
36 | _delegate = aDelegate;
37 | }
38 |
39 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
40 | {
41 | _actionCompleted = completed;
42 | }
43 |
44 | - (void)setPercentageCompleted:(CGFloat)percentage;
45 | {
46 | if (percentage < 0)
47 | return;
48 |
49 | _percentageCompleted = percentage;
50 |
51 | if (percentage >= 1.0)
52 | [self setCompleted:YES animated:NO];
53 | }
54 |
55 | - (CGFloat)percentageCompleted;
56 | {
57 | return _percentageCompleted;
58 | }
59 |
60 | - (DNTutorialAction)tutorialActions;
61 | {
62 | return DNTutorialActionNone;
63 | }
64 |
65 | - (void)startAnimating;
66 | {
67 | return;
68 | }
69 |
70 |
71 | - (void)stopAnimating;
72 | {
73 | return;
74 | }
75 |
76 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
77 | {
78 | [self willAnimateElement:self toInterfaceOrientation:toInterfaceOrientation duration:duration];
79 | }
80 |
81 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
82 | {
83 | [_delegate willAnimateElement:element toInterfaceOrientation:toInterfaceOrientation duration:duration];
84 | }
85 |
86 | @end
87 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialGesture.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialGesture.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "DNTutorialElement.h"
12 |
13 | typedef NS_ENUM (NSUInteger, DNTutorialGestureType)
14 | {
15 | DNTutorialGestureTypeSwipeUp = 0,
16 | DNTutorialGestureTypeSwipeRight = 1,
17 | DNTutorialGestureTypeSwipeDown = 2,
18 | DNTutorialGestureTypeSwipeLeft = 3,
19 | DNTutorialGestureTypeScrollUp = 4,
20 | DNTutorialGestureTypeScrollRight = 5,
21 | DNTutorialGestureTypeScrollDown = 6,
22 | DNTutorialGestureTypeScrollLeft = 7,
23 | DNTutorialGestureTypeTap = 8,
24 | DNTutorialGestureTypeDoubleTap = 9,
25 | };
26 |
27 | @interface DNTutorialGesture : DNTutorialElement
28 |
29 | @property (nonatomic) CGFloat animationDuration;
30 | @property (nonatomic, assign) DNTutorialGestureType gestureType;
31 |
32 |
33 | // Instantiate a new gesture tutorial with the given position and animation direction
34 | + (id)gestureWithPosition:(CGPoint)point
35 | type:(DNTutorialGestureType)type
36 | key:(NSString *)key;
37 |
38 |
39 | // Sets the center position
40 | - (void)setPosition:(CGPoint)point;
41 |
42 |
43 | // Sets the background image
44 | - (void)setBackgroundImage:(UIImage *)image;
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialGesture.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialGesture.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/24/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNTutorialGesture.h"
10 |
11 | NSInteger const sGesturePositionDelta = 150;
12 |
13 | @interface DNTutorialGesture()
14 |
15 | @property (nonatomic) CGPoint startPosition;
16 | @property (nonatomic, weak) CAShapeLayer *circleLayer;
17 | @property (nonatomic, weak, setter = setBackgroundImage:) UIImage *circleImage;
18 |
19 | @end
20 |
21 | @implementation DNTutorialGesture
22 |
23 | #pragma mark --
24 | #pragma mark - Initialization
25 | #pragma mark --
26 |
27 | + (id)gestureWithPosition:(CGPoint)point
28 | type:(DNTutorialGestureType)type
29 | key:(NSString *)key;
30 | {
31 | // Proper initialization
32 | NSAssert(key != nil, @"DNTutorialGesture: Cannot initialize action with invalid key");
33 |
34 | // Init view
35 | DNTutorialGesture *view = [DNTutorialGesture new];
36 | view.key = key;
37 | view.startPosition = point;
38 | view.gestureType = type;
39 | view.animationDuration = 1.5;
40 | return view;
41 | }
42 |
43 | - (void)setUpInView:(UIView *)aView;
44 | {
45 | // Initialize container view
46 | CGRect frame = CGRectMake(0, 0, 50, 50);
47 |
48 | CAShapeLayer *layer = [CAShapeLayer layer];
49 | layer.bounds = frame;
50 | layer.path = [UIBezierPath bezierPathWithOvalInRect:frame].CGPath;
51 | layer.fillColor = [UIColor whiteColor].CGColor;
52 | layer.opacity = 0.0;
53 | layer.position = self.startPosition;
54 |
55 | // Check for backgroundImage
56 | if (_circleImage == nil)
57 | {
58 | // Add shadow
59 | layer.shadowColor = [UIColor blueColor].CGColor;
60 | layer.shadowOpacity = 0.75f;
61 | layer.shadowRadius = 5;
62 | layer.shadowOffset = CGSizeMake(0, 0);
63 | layer.shadowPath = layer.path;
64 | }
65 | else
66 | {
67 | layer.fillColor = nil;
68 | layer.contents = (id)_circleImage.CGImage;
69 | }
70 |
71 | // Add layer
72 | self.circleLayer = layer;
73 | [aView.layer addSublayer:layer];
74 | }
75 |
76 | - (void)tearDown;
77 | {
78 | // Check if already dismissed
79 | if (_circleLayer.superlayer)
80 | {
81 | [_circleLayer removeFromSuperlayer];
82 | }
83 | _circleImage = nil;
84 | _circleLayer = nil;
85 | }
86 |
87 | #pragma mark --
88 | #pragma mark - Polimorphic Methods
89 | #pragma mark --
90 |
91 | - (void)show;
92 | {
93 | // Start animation
94 | _actionCompleted = NO;
95 |
96 | // Test
97 | //UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
98 | //[_delegate willAnimateElement:self toInterfaceOrientation:interfaceOrientation duration:0.0];
99 |
100 | [self startAnimating];
101 | }
102 |
103 | - (void)dismiss;
104 | {
105 | // Will dismiss element
106 | [_delegate willDismissElement:self];
107 |
108 | // Animate removal
109 | CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
110 | opacityAnimation.duration = 0.2;
111 | opacityAnimation.fromValue = @(_circleLayer.opacity);
112 | opacityAnimation.toValue = @(0.0);
113 | opacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
114 |
115 | [self stopAnimating];
116 | [_circleLayer addAnimation:opacityAnimation forKey:@"opacity"];
117 |
118 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, opacityAnimation.duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
119 | [_delegate didDismissElement:self];
120 | });
121 | }
122 |
123 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
124 | {
125 | if (_actionCompleted && completed) {
126 | return;
127 | }
128 |
129 | _actionCompleted = completed;
130 |
131 | if (completed)
132 | {
133 | // Should dismiss
134 | [self dismiss];
135 | }
136 | }
137 |
138 | - (void)setPercentageCompleted:(CGFloat)percentage;
139 | {
140 | // percentage alpha and position based on position
141 | if (percentage < 0 || _actionCompleted)
142 | {
143 | return;
144 | }
145 |
146 | _percentageCompleted = percentage;
147 |
148 | if (percentage >= 1.0)
149 | {
150 | // User action completed
151 | [self setCompleted:YES animated:NO];
152 | }
153 | }
154 |
155 | - (DNTutorialAction)tutorialActions;
156 | {
157 | if (self.gestureType >= DNTutorialGestureTypeSwipeUp && self.gestureType < DNTutorialGestureTypeScrollUp)
158 | {
159 | return DNTutorialActionSwipeGesture;
160 | }
161 |
162 | if (self.gestureType >= DNTutorialGestureTypeScrollUp && self.gestureType < DNTutorialGestureTypeTap)
163 | {
164 | return DNTutorialActionScroll;
165 | }
166 |
167 | return DNTutorialActionTapGesture | DNTutorialActionScroll | DNTutorialActionSwipeGesture;
168 | }
169 |
170 | - (void)startAnimating;
171 | {
172 | // Check if can animate
173 | if (_actionCompleted || ![_delegate shouldAnimateElement:self])
174 | {
175 | return;
176 | }
177 |
178 | // Animations
179 | NSArray *animations;
180 | CGFloat multiplier = 1.2;
181 |
182 | // Calculate end point based on origin and direction
183 | CGPoint startPoint = self.startPosition;
184 | CGPoint endPoint = [self DNPointOffSet:startPoint delta:sGesturePositionDelta];
185 |
186 | CGRect startRect = self.circleLayer.bounds;
187 | CGRect endRect = CGRectZero;
188 |
189 | // Center function
190 | endRect.size.height = endRect.size.width = startRect.size.height * multiplier;
191 | endRect.origin.x = startRect.origin.x - (endRect.size.width - startRect.size.width)/2.0;
192 | endRect.origin.y = startRect.origin.y - (endRect.size.height - startRect.size.height)/2.0;
193 |
194 | // Animate path
195 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
196 | pathAnimation.duration = self.animationDuration*0.3;
197 | pathAnimation.fromValue = (id)[[UIBezierPath bezierPathWithOvalInRect:endRect] CGPath];
198 | pathAnimation.toValue = (id)[[UIBezierPath bezierPathWithOvalInRect:startRect] CGPath];
199 | pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
200 |
201 | // Animate position
202 | CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
203 | positionAnimation.beginTime = self.animationDuration*0.4;
204 | positionAnimation.duration = self.animationDuration*0.6;
205 | positionAnimation.fromValue = [NSValue valueWithCGPoint:startPoint];
206 | positionAnimation.toValue = [NSValue valueWithCGPoint:endPoint];
207 | positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
208 |
209 | // Animate opacity
210 | CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
211 | opacityAnimation.duration = self.animationDuration * 0.4;
212 | opacityAnimation.fromValue = @(0.0);
213 | opacityAnimation.toValue = @(1.0);
214 | opacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
215 |
216 | CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
217 | fadeOut.beginTime = opacityAnimation.duration;
218 | fadeOut.duration = self.animationDuration * 0.6;
219 | fadeOut.fromValue = @(1.0);
220 | fadeOut.toValue = @(0.0);
221 | fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
222 |
223 | animations = [NSArray arrayWithObjects:pathAnimation, positionAnimation, opacityAnimation, fadeOut, nil];
224 |
225 | // Animate tap
226 | if (self.gestureType == DNTutorialGestureTypeTap)
227 | {
228 | multiplier = 1.1;
229 |
230 | // Animate size
231 | pathAnimation.duration = self.animationDuration*0.4;
232 | pathAnimation.fromValue = (id)[[UIBezierPath bezierPathWithOvalInRect:startRect] CGPath];
233 | pathAnimation.toValue = (id)[[UIBezierPath bezierPathWithOvalInRect:endRect] CGPath];
234 | pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
235 |
236 | CABasicAnimation *shadowAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
237 | shadowAnimation.duration = pathAnimation.duration;
238 | shadowAnimation.fromValue = pathAnimation.fromValue;
239 | shadowAnimation.toValue = pathAnimation.toValue;
240 | shadowAnimation.timingFunction = pathAnimation.timingFunction;
241 |
242 | CABasicAnimation *pathAnimation1 = [CABasicAnimation animationWithKeyPath:@"path"];
243 | pathAnimation1.duration = self.animationDuration*0.6;
244 | pathAnimation1.beginTime = pathAnimation.duration;
245 | pathAnimation1.fromValue = (id)[[UIBezierPath bezierPathWithOvalInRect:endRect] CGPath];
246 | pathAnimation1.toValue = (id)[[UIBezierPath bezierPathWithOvalInRect:startRect] CGPath];
247 | pathAnimation1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
248 |
249 | CABasicAnimation *shadowAnimation1 = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
250 | shadowAnimation1.beginTime = pathAnimation1.beginTime;
251 | shadowAnimation1.duration = pathAnimation1.duration;
252 | shadowAnimation1.fromValue = pathAnimation1.fromValue;
253 | shadowAnimation1.toValue = pathAnimation1.toValue;
254 | shadowAnimation1.timingFunction = pathAnimation1.timingFunction;
255 |
256 | animations = [NSArray arrayWithObjects:pathAnimation, shadowAnimation, pathAnimation1, shadowAnimation1, opacityAnimation, fadeOut, nil];
257 | }
258 |
259 | CAAnimationGroup *opacityGroup = [CAAnimationGroup animation];
260 | opacityGroup.animations = animations;
261 | opacityGroup.repeatCount = HUGE_VALF;
262 | opacityGroup.duration = self.animationDuration*multiplier;
263 | [self.circleLayer addAnimation:opacityGroup forKey:@"gestureAnimation"];
264 | }
265 |
266 | - (void)stopAnimating;
267 | {
268 | // Stop animating
269 | [_circleLayer removeAnimationForKey:@"gestureAnimation"];
270 | }
271 |
272 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
273 | {
274 |
275 | }
276 |
277 | #pragma mark --
278 | #pragma mark - Public Methods
279 | #pragma mark --
280 |
281 | - (void)setPosition:(CGPoint)point;
282 | {
283 | if (!CGPointEqualToPoint(_startPosition, point))
284 | {
285 | _startPosition = point;
286 | _circleLayer.position = point;
287 |
288 | if ([_circleLayer animationForKey:@"gestureAnimation"])
289 | {
290 | [self stopAnimating];
291 | [self startAnimating];
292 | }
293 | }
294 | }
295 |
296 | - (void)setBackgroundImage:(UIImage *)image;
297 | {
298 | _circleImage = image;
299 | _circleLayer.fillColor = nil;
300 | _circleLayer.contents = (id)image.CGImage;
301 | }
302 |
303 | #pragma mark --
304 | #pragma mark - Private Methods
305 | #pragma mark --
306 |
307 | - (CGPoint)DNPointOffSet:(CGPoint)origin delta:(CGFloat)delta;
308 | {
309 | switch (self.gestureType) {
310 | case DNTutorialGestureTypeSwipeUp:
311 | origin.y -= delta;
312 | break;
313 | case DNTutorialGestureTypeSwipeRight:
314 | origin.x += delta;
315 | break;
316 | case DNTutorialGestureTypeSwipeDown:
317 | origin.y += delta;
318 | break;
319 | case DNTutorialGestureTypeSwipeLeft:
320 | origin.x -= delta;
321 | break;
322 | case DNTutorialGestureTypeScrollUp:
323 | origin.y -= delta;
324 | break;
325 | case DNTutorialGestureTypeScrollRight:
326 | origin.x += delta;
327 | break;
328 | case DNTutorialGestureTypeScrollDown:
329 | origin.y += delta;
330 | break;
331 | case DNTutorialGestureTypeScrollLeft:
332 | origin.x -= delta;
333 | break;
334 | default:
335 | break;
336 | }
337 |
338 | return origin;
339 | }
340 |
341 | @end
342 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialMovement.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialMovement.h
3 | // Pods
4 | //
5 | // Created by Daniel Niemeyer on 3/31/15.
6 | //
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 |
13 | #import "DNTutorialElement.h"
14 |
15 | typedef NS_ENUM (NSUInteger, DNTutorialMovementDirection)
16 | {
17 | DNTutorialMovementDirectionUp = 0,
18 | DNTutorialMovementDirectionRight = 1,
19 | DNTutorialMovementDirectionDown = 2,
20 | DNTutorialMovementDirectionLeft = 3,
21 | };
22 |
23 | @interface DNTutorialMovement : DNTutorialElement
24 |
25 | @property (nonatomic, assign) DNTutorialMovementDirection movementDirection;
26 |
27 | // Public method for instantiating a new DNTutorialMovement with the appropriate direction
28 | + (id)movementWithDirection:(DNTutorialMovementDirection)direction
29 | key:(NSString *)key;
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialMovement.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialMovement.m
3 | // Pods
4 | //
5 | // Created by Daniel Niemeyer on 3/31/15.
6 | //
7 | //
8 |
9 | #import "DNTutorialMovement.h"
10 |
11 | @interface DNTutorialMovement()
12 |
13 | @property (nonatomic, strong) CMMotionManager *motionManager;
14 | @property (nonatomic, strong) NSNumberFormatter *numberFormatter;
15 |
16 | @property (nonatomic, assign) CGFloat xAcceleration;
17 | @property (nonatomic, assign) CGFloat yAcceleration;
18 | @property (nonatomic, assign) CGFloat zAcceleration;
19 |
20 | @end
21 |
22 | @implementation DNTutorialMovement
23 |
24 | #pragma mark --
25 | #pragma mark - Initialization
26 | #pragma mark --
27 |
28 | + (id)movementWithDirection:(DNTutorialMovementDirection)direction
29 | key:(NSString *)key;
30 | {
31 | // Proper initialization
32 | NSAssert(key != nil, @"DNTutorialMovement: Cannot initialize action with invalid key");
33 |
34 | DNTutorialMovement *movement = [DNTutorialMovement new];
35 | movement.key = key;
36 | movement.movementDirection = direction;
37 | return movement;
38 | }
39 |
40 | #pragma mark --
41 | #pragma mark - Polimorphic Methods
42 | #pragma mark --
43 |
44 | - (void)setUpInView:(UIView *)aView;
45 | {
46 | // Initialize motion manager
47 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
48 | [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
49 | [numberFormatter setMaximumFractionDigits:2];
50 | self.numberFormatter = numberFormatter;
51 |
52 | self.motionManager = [[CMMotionManager alloc] init];
53 | self.motionManager.accelerometerUpdateInterval = 0.2f;
54 | self.motionManager.gyroUpdateInterval = 0.2f;
55 |
56 | [self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
57 | withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
58 | [self outputAccelerometerData:accelerometerData.acceleration];
59 | if (error)
60 | {
61 | NSLog(@"DNTutorialMovement: Accelerometer queue error: %@", error);
62 | }
63 | }];
64 |
65 | [self.motionManager startGyroUpdatesToQueue:[NSOperationQueue currentQueue]
66 | withHandler:^(CMGyroData *gyroData, NSError *error) {
67 | [self outputRotationData:gyroData.rotationRate];
68 | }];
69 | }
70 |
71 | - (void)tearDown;
72 | {
73 | [self.motionManager stopAccelerometerUpdates];
74 | [self.motionManager stopGyroUpdates];
75 | self.motionManager = nil;
76 | }
77 |
78 | - (void)show;
79 | {
80 | _actionCompleted = NO;
81 | }
82 |
83 | - (void)dismiss;
84 | {
85 | // Will dismiss element
86 | [_delegate willDismissElement:self];
87 |
88 |
89 |
90 | [_delegate didDismissElement:self];
91 | }
92 |
93 | - (void)startAnimating;
94 | {
95 | return;
96 | }
97 |
98 | - (void)stopAnimating;
99 | {
100 | return;
101 | }
102 |
103 | - (void)setCompleted:(BOOL)completed animated:(BOOL)animated;
104 | {
105 | if (_actionCompleted && completed) {
106 | return;
107 | }
108 |
109 | _actionCompleted = completed;
110 |
111 | if (completed)
112 | {
113 | // Should dismiss
114 | [self dismiss];
115 | }
116 | }
117 |
118 | - (void)setPercentageCompleted:(CGFloat)percentage;
119 | {
120 | // percentage alpha and position based on position
121 | if (percentage < 0 || _actionCompleted)
122 | {
123 | return;
124 | }
125 |
126 | _percentageCompleted = percentage;
127 |
128 | if (percentage >= 1.0)
129 | {
130 | // User action completed
131 | [self setCompleted:YES animated:NO];
132 | }
133 | }
134 |
135 | #pragma mark --
136 | #pragma mark - Private Methods
137 | #pragma mark --
138 |
139 | - (void)outputAccelerometerData:(CMAcceleration)acceleration;
140 | {
141 | // Check accelerometer direction
142 | switch (self.movementDirection)
143 | {
144 | case DNTutorialMovementDirectionUp:
145 | {
146 |
147 | NSString* formattedNumber = [NSString stringWithFormat:@"%.02f", acceleration.x];
148 | CGFloat roundedAcceleration = atof([formattedNumber UTF8String]);
149 |
150 | if (self.xAcceleration != roundedAcceleration)
151 | {
152 | self.xAcceleration = roundedAcceleration;
153 | NSLog(@"Acceleration.x %f", roundedAcceleration);
154 | }
155 | }
156 | break;
157 | case DNTutorialMovementDirectionDown:
158 | {
159 |
160 | }
161 | break;
162 | case DNTutorialMovementDirectionLeft:
163 | {
164 |
165 | }
166 | break;
167 | case DNTutorialMovementDirectionRight:
168 | {
169 |
170 | }
171 | break;
172 |
173 | default:
174 | break;
175 | }
176 | }
177 |
178 | - (void)outputRotationData:(CMRotationRate)rotation;
179 | {
180 | //NSLog(@"rX: %.2fr/s",rotation.x);
181 | //NSLog(@"rY %.2fr/s",rotation.y);
182 | //NSLog(@"rZ %.2fr/s",rotation.z);
183 | }
184 |
185 | @end
186 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialStep.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialStep.h
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/31/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | #import "DNTutorialElement.h"
13 |
14 | @protocol DNTutorialStepDelegate;
15 |
16 |
17 | @interface DNTutorialStep : NSObject
18 | {
19 | @protected
20 | BOOL _actionCompleted;
21 | CGFloat _percentageCompleted;
22 | id _delegate;
23 | }
24 |
25 | @property (nonatomic, strong) NSString *key;
26 | @property (nonatomic, assign) BOOL isHidden;
27 |
28 |
29 | // Public method for instantiating a new tutorial step
30 | + (id)stepWithTutorialElements:(NSArray *)elements
31 | forKey:(NSString *)key;
32 |
33 |
34 | // App Tutorial manager delegate
35 | - (void)setDelegate:(id)aDelegate;
36 |
37 |
38 | // Present tutorial step
39 | - (void)showInView:(UIView *)aView;
40 |
41 |
42 | // Hide a step
43 | - (void)hideElements;
44 |
45 |
46 | // Set action completed
47 | - (void)setCompleted:(BOOL)completed;
48 |
49 |
50 | // Getter
51 | - (BOOL)isCompleted;
52 |
53 |
54 | // Called when element should animate
55 | - (void)startAnimating;
56 |
57 |
58 | // Called when user action pauses animations
59 | - (void)stopAnimating;
60 |
61 |
62 | // Return tutorial elements that repond to given actions
63 | - (NSArray *)tutorialElementsWithAction:(DNTutorialAction)actions;
64 |
65 |
66 | // Retuns a tutorial element with the given key
67 | - (id)tutorialElementForKey:(NSString *)aKey;
68 |
69 |
70 | // Check if a certain tutorial element responds to given actions
71 | - (BOOL)tutorialElement:(DNTutorialElement *)tutorialElement respondsToActions:(DNTutorialAction)actions;
72 |
73 |
74 | // Set percentage completed
75 | - (void)setPercentageCompleted:(CGFloat)percentage;
76 |
77 | // Getter for percentage completion
78 | - (CGFloat)percentageCompleted;
79 |
80 | // Interface rotation
81 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
82 |
83 | @end
84 |
85 | @protocol DNTutorialStepDelegate
86 | @required
87 |
88 | - (void)willDismissStep:(DNTutorialStep *)view;
89 | - (void)didDismissStep:(DNTutorialStep *)view;
90 |
91 | - (BOOL)shouldDismissStep:(DNTutorialStep *)step;
92 | - (BOOL)shouldAnimateStep:(DNTutorialStep *)step;
93 |
94 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/Pod/Classes/DNTutorialStep.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNTutorialStep.m
3 | // DNTutorial
4 | //
5 | // Created by Daniel Niemeyer on 7/31/14.
6 | // Copyright (c) 2014 Daniel Niemeyer. All rights reserved.
7 | //
8 |
9 | #import "DNTutorialStep.h"
10 |
11 | @interface DNTutorialStep()
12 | {
13 | BOOL isDismissingElement;
14 | }
15 |
16 | @property (nonatomic, strong) NSMutableArray *elements;
17 |
18 | @end
19 |
20 | @implementation DNTutorialStep
21 |
22 | #pragma mark --
23 | #pragma mark Initialization
24 | #pragma mark --
25 |
26 | + (id)stepWithTutorialElements:(NSArray *)elements
27 | forKey:(NSString *)key;
28 | {
29 | DNTutorialStep *tutorialStep = [DNTutorialStep new];
30 | tutorialStep.elements = [elements mutableCopy];
31 | tutorialStep.key = key;
32 | return tutorialStep;
33 | }
34 |
35 | #pragma mark --
36 | #pragma mark Public Methods
37 | #pragma mark --
38 |
39 | - (void)setDelegate:(id)aDelegate;
40 | {
41 | _delegate = aDelegate;
42 |
43 | for (DNTutorialElement *element in self.elements)
44 | {
45 | [element setDelegate:self];
46 | }
47 | }
48 |
49 | - (void)showInView:(UIView *)aView;
50 | {
51 | // Check if hidden
52 | self.isHidden = NO;
53 |
54 | // Present elements
55 | for (DNTutorialElement *tutorialElement in self.elements)
56 | {
57 | // Assert type conforms to tutorial type
58 | NSAssert([[tutorialElement class] isSubclassOfClass:[DNTutorialElement class]], @"AppTutorial: Presenting objects must be a subclass of DNTutorialElement");
59 |
60 | [tutorialElement setUpInView:aView];
61 | [tutorialElement setDelegate:self];
62 | [tutorialElement show];
63 | }
64 | }
65 |
66 | - (NSArray *)tutorialElementsWithAction:(DNTutorialAction)actions;
67 | {
68 | // Return first occurence of object
69 | NSMutableArray *toReturn = [NSMutableArray array];
70 |
71 | for (DNTutorialElement *tutorialElement in _elements)
72 | {
73 | if ([self tutorialElement:tutorialElement respondsToActions:actions])
74 | {
75 | [toReturn addObject:tutorialElement];
76 | }
77 | }
78 |
79 | // Mot found
80 | return [NSArray arrayWithArray:toReturn];
81 | }
82 |
83 | - (id)tutorialElementForKey:(NSString *)aKey;
84 | {
85 | for (DNTutorialElement *element in self.elements)
86 | {
87 | if ([element.key isEqualToString:aKey])
88 | {
89 | return element;
90 | }
91 | }
92 |
93 | return nil;
94 | }
95 |
96 | - (BOOL)tutorialElement:(DNTutorialElement *)tutorialElement respondsToActions:(DNTutorialAction)actions;
97 | {
98 | if (actions & [tutorialElement tutorialActions])
99 | {
100 | return YES;
101 | }
102 |
103 | return NO;
104 | }
105 |
106 | - (void)setPercentageCompleted:(CGFloat)percentage;
107 | {
108 | // Set percentage completion of child elements
109 | if (percentage < 0)
110 | return;
111 |
112 | _percentageCompleted = percentage;
113 |
114 | for (DNTutorialElement *tutorialElement in self.elements)
115 | {
116 | [tutorialElement setPercentageCompleted:percentage];
117 | }
118 |
119 | if (percentage >= 1.0)
120 | {
121 | _actionCompleted = YES;
122 | }
123 | }
124 |
125 | - (CGFloat)percentageCompleted;
126 | {
127 | return _percentageCompleted;
128 | }
129 |
130 | - (void)setCompleted:(BOOL)completed;
131 | {
132 | if (_actionCompleted && completed)
133 | {
134 | return;
135 | }
136 |
137 | _actionCompleted = completed;
138 |
139 | if (_actionCompleted)
140 | {
141 | // Execute completed block
142 | for (DNTutorialElement *element in self.elements)
143 | {
144 | [element setCompleted:YES animated:YES];
145 | }
146 | }
147 | }
148 |
149 | - (BOOL)isCompleted;
150 | {
151 | return _actionCompleted;
152 | }
153 |
154 | - (void)startAnimating;
155 | {
156 | if (self.isHidden)
157 | return;
158 |
159 | for (DNTutorialElement *tutorialElement in self.elements)
160 | {
161 | [tutorialElement startAnimating];
162 | }
163 | }
164 |
165 | - (void)stopAnimating;
166 | {
167 | for (DNTutorialElement *tutorialElement in self.elements)
168 | {
169 | [tutorialElement stopAnimating];
170 | }
171 | }
172 |
173 | - (void)hideElements;
174 | {
175 | self.isHidden = YES;
176 | [self dismiss];
177 | }
178 |
179 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
180 | {
181 | for (DNTutorialElement *tutorialElement in self.elements)
182 | {
183 | [tutorialElement willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
184 | }
185 | }
186 |
187 | #pragma mark --
188 | #pragma mark Private
189 | #pragma mark --
190 |
191 | - (void)dismiss;
192 | {
193 | if (_delegate && [_delegate respondsToSelector:@selector(willDismissStep:)])
194 | {
195 | [_delegate willDismissStep:self];
196 | }
197 |
198 | for (DNTutorialElement *element in self.elements)
199 | {
200 | [element dismiss];
201 | }
202 | }
203 |
204 | #pragma mark --
205 | #pragma mark DNTutorialElementDelegate
206 | #pragma mark --
207 |
208 |
209 | - (BOOL)shouldDismissElement:(DNTutorialElement *)element;
210 | {
211 | return [_delegate shouldDismissStep:self];
212 | }
213 |
214 | - (BOOL)shouldAnimateElement:(DNTutorialElement *)element;
215 | {
216 | return [_delegate shouldAnimateStep:self];
217 | }
218 |
219 | - (void)userDismissedElement:(DNTutorialElement *)element;
220 | {
221 | [self setCompleted:YES];
222 | }
223 |
224 | - (void)willDismissElement:(DNTutorialElement *)element;
225 | {
226 | // Called when element is about to be animated out of the parent view
227 | if (isDismissingElement || self.isHidden)
228 | {
229 | return;
230 | }
231 |
232 | // Check if there are other objects associated with this one that should be dismissed
233 | isDismissingElement = YES;
234 | }
235 |
236 | - (void)didDismissElement:(DNTutorialElement *)element;
237 | {
238 | // Dealloc
239 | [element tearDown];
240 |
241 | // Check if done hiding
242 | if (self.isHidden)
243 | {
244 | return;
245 | }
246 |
247 | // Element dismissed!
248 | [self.elements removeObject:element];
249 |
250 | // Check if step is completed
251 | if ([self.elements count] == 0)
252 | {
253 | isDismissingElement = NO;
254 |
255 | // Mark as completed
256 | [self dismiss];
257 | [_delegate didDismissStep:self];
258 | }
259 | }
260 |
261 | - (void)willAnimateElement:(DNTutorialElement *)element toInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
262 | {
263 | // Called when an element is about to animate view rotation
264 | [_delegate willAnimateElement:element toInterfaceOrientation:toInterfaceOrientation duration:duration];
265 | }
266 |
267 | @end
268 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DNTutorial
2 |
3 | [](https://travis-ci.org/danielniemeyer/DNTutorial)
4 | [](http://cocoadocs.org/docsets/DNTutorial)
5 | [](http://cocoadocs.org/docsets/DNTutorial)
6 | [](http://cocoadocs.org/docsets/DNTutorial)
7 |
8 | DNTutorial manages a set of tutorial elements that guide users on how to interact with your app.
9 |
10 | The implementation of DNTutorial is very simple and was mainly based on Paper by Facebook.
11 |
12 | 
13 |
14 | ## Usage
15 |
16 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
17 |
18 | To use DNTutorial, import the DNTutorial header file to your view controller and add it as a delegate of DNTutorial.
19 | To present a tutorial, simply create the tutorial elements you would like to present.
20 |
21 | An example of creating a tutorial sequence.
22 |
23 | ```objectivec
24 | DNTutorialBanner *banner = [DNTutorialBanner bannerWithMessage:@"A banner message" completionMessage:@"Completion message" key:@"banner"];
25 |
26 | DNTutorialGesture *scrollGesture = [DNTutorialGesture gestureWithPosition:center type:DNTutorialGestureTypeScrollLeft key:@"gesture"];
27 |
28 | DNTutorialStep *step = [DNTutorialStep stepWithTutorialElements:@[banner, scrollGesture] forKey:@"step"];
29 |
30 | [DNTutorial presentTutorialWithSteps:@[step1] inView:self.view delegate:self];
31 | ```
32 |
33 | To style the appearance of a banner simply call the style method
34 | ```objectivec
35 | [banner styleWithColor:[UIColor blackColor] completedColor:[UIColor blueColor] opacity:0.7 font:[UIFont systemFontOfSize:13]];
36 | ```
37 | ## Customization
38 |
39 | DNTutorial comes with two standard tutorial elements (DNTutorialBanner, DNTutorialGesture).
40 |
41 | Both standard classes derive from the same base class, DNTutorialElement.
42 | This polymorphic class provides an easy framework for you to come up with your own tutorial element subclasses that can
43 | work with the tutorial system right from outside the box.
44 |
45 | And if you come up with a cool class, just submit a pull request so that I can add it to the repo.
46 |
47 | ## Installation
48 |
49 | DNTutorial is available through [CocoaPods](http://cocoapods.org). To install
50 | it, simply add the following line to your Podfile:
51 |
52 | pod "DNTutorial"
53 |
54 |
55 | There are two options:
56 |
57 | 1. Adding it to your pod file.
58 | 2. Manually add the files into your Xcode project. Slightly simpler, but updates are also manual.
59 |
60 | DNTutorial requires iOS 7 or later.
61 |
62 | ## TODO
63 |
64 | - Add ability to hide and show a tutorial step and see how it syncs with skipping a tutorial step.
65 |
66 | - Dismiss objects based on user actions √
67 | - Look into NSObject as the base type for tutorialElements √
68 | - Flexible implementation with polimorphic base classes for easy customizable subclasses √
69 | - Save state on user defaults √
70 |
71 | ## Author
72 |
73 | Daniel Niemeyer
74 |
75 | ## License
76 |
77 | DNTutorial is available under the MIT license. See the LICENSE file for more info.
78 |
--------------------------------------------------------------------------------