├── .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 | [![CI Status](http://img.shields.io/travis/danielniemeyer/DNTutorial.svg?style=flat)](https://travis-ci.org/danielniemeyer/DNTutorial) 4 | [![Version](https://img.shields.io/cocoapods/v/DNTutorial.svg?style=flat)](http://cocoadocs.org/docsets/DNTutorial) 5 | [![License](https://img.shields.io/cocoapods/l/DNTutorial.svg?style=flat)](http://cocoadocs.org/docsets/DNTutorial) 6 | [![Platform](https://img.shields.io/cocoapods/p/DNTutorial.svg?style=flat)](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 | ![alt tag](http://f.cl.ly/items/3o0n1K2V2z1L1e0t2X09/tutorial.gif) 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 | --------------------------------------------------------------------------------