├── DDAutoTracker.podspec ├── DDAutoTracker ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── DDAutoTracker.h │ ├── DDAutoTrackerManager.h │ ├── DDAutoTrackerManager.m │ ├── DDAutoTrackerOperation.h │ ├── DDAutoTrackerOperation.m │ ├── NSObject+DDAutoTracker.h │ ├── NSObject+DDAutoTracker.m │ ├── UIButton+DDAutoTracker.h │ ├── UIButton+DDAutoTracker.m │ ├── UICollectionView+DDAutoTracker.h │ ├── UICollectionView+DDAutoTracker.m │ ├── UITableView+DDAutoTracker.h │ ├── UITableView+DDAutoTracker.m │ ├── UIView+DDAutoTracker.h │ └── UIView+DDAutoTracker.m ├── Example ├── DDAutoTracker.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── DDAutoTracker-Example.xcscheme │ └── xcuserdata │ │ └── wanghailiang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── DDAutoTracker.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── wanghailiang.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── DDAutoTracker │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── DDAppDelegate.h │ ├── DDAppDelegate.m │ ├── DDAutoTracker-Info.plist │ ├── DDAutoTracker-Prefix.pch │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Models │ │ ├── DDExampleModel.h │ │ ├── DDExampleModel.m │ │ ├── DDItemModel.h │ │ └── DDItemModel.m │ ├── Resources │ │ └── tracker.json │ ├── Utils │ │ ├── UIView+DDFrame.h │ │ └── UIView+DDFrame.m │ ├── ViewControllers │ │ ├── DDButtonViewController.h │ │ ├── DDButtonViewController.m │ │ ├── DDCollectionViewController.h │ │ ├── DDCollectionViewController.m │ │ ├── DDExampleViewController.h │ │ ├── DDExampleViewController.m │ │ ├── DDGestureViewController.h │ │ ├── DDGestureViewController.m │ │ ├── DDTableViewController.h │ │ └── DDTableViewController.m │ ├── Views │ │ ├── DDCollectionViewCell.h │ │ └── DDCollectionViewCell.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── Podfile ├── Podfile.lock ├── Pods │ ├── FBSnapshotTestCase │ │ ├── FBSnapshotTestCase │ │ │ ├── Categories │ │ │ │ ├── UIApplication+StrictKeyWindow.h │ │ │ │ ├── UIApplication+StrictKeyWindow.m │ │ │ │ ├── UIImage+Compare.h │ │ │ │ ├── UIImage+Compare.m │ │ │ │ ├── UIImage+Diff.h │ │ │ │ ├── UIImage+Diff.m │ │ │ │ ├── UIImage+Snapshot.h │ │ │ │ └── UIImage+Snapshot.m │ │ │ ├── FBSnapshotTestCase.h │ │ │ ├── FBSnapshotTestCase.m │ │ │ ├── FBSnapshotTestCasePlatform.h │ │ │ ├── FBSnapshotTestCasePlatform.m │ │ │ ├── FBSnapshotTestController.h │ │ │ ├── FBSnapshotTestController.m │ │ │ └── SwiftSupport.swift │ │ ├── LICENSE │ │ └── README.md │ ├── Local Podspecs │ │ └── DDAutoTracker.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ └── wanghailiang.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── DDAutoTracker.xcscheme │ │ │ ├── FBSnapshotTestCase.xcscheme │ │ │ ├── Pods-DDAutoTracker_Example.xcscheme │ │ │ ├── Pods-DDAutoTracker_Tests.xcscheme │ │ │ └── xcschememanagement.plist │ └── Target Support Files │ │ ├── DDAutoTracker │ │ ├── DDAutoTracker-dummy.m │ │ ├── DDAutoTracker-prefix.pch │ │ ├── DDAutoTracker-umbrella.h │ │ ├── DDAutoTracker.modulemap │ │ ├── DDAutoTracker.xcconfig │ │ └── Info.plist │ │ ├── FBSnapshotTestCase │ │ ├── FBSnapshotTestCase-dummy.m │ │ ├── FBSnapshotTestCase-prefix.pch │ │ ├── FBSnapshotTestCase-umbrella.h │ │ ├── FBSnapshotTestCase.modulemap │ │ ├── FBSnapshotTestCase.xcconfig │ │ └── Info.plist │ │ ├── Pods-DDAutoTracker_Example │ │ ├── Info.plist │ │ ├── Pods-DDAutoTracker_Example-acknowledgements.markdown │ │ ├── Pods-DDAutoTracker_Example-acknowledgements.plist │ │ ├── Pods-DDAutoTracker_Example-dummy.m │ │ ├── Pods-DDAutoTracker_Example-frameworks.sh │ │ ├── Pods-DDAutoTracker_Example-resources.sh │ │ ├── Pods-DDAutoTracker_Example-umbrella.h │ │ ├── Pods-DDAutoTracker_Example.debug.xcconfig │ │ ├── Pods-DDAutoTracker_Example.modulemap │ │ └── Pods-DDAutoTracker_Example.release.xcconfig │ │ └── Pods-DDAutoTracker_Tests │ │ ├── Info.plist │ │ ├── Pods-DDAutoTracker_Tests-acknowledgements.markdown │ │ ├── Pods-DDAutoTracker_Tests-acknowledgements.plist │ │ ├── Pods-DDAutoTracker_Tests-dummy.m │ │ ├── Pods-DDAutoTracker_Tests-frameworks.sh │ │ ├── Pods-DDAutoTracker_Tests-resources.sh │ │ ├── Pods-DDAutoTracker_Tests-umbrella.h │ │ ├── Pods-DDAutoTracker_Tests.debug.xcconfig │ │ ├── Pods-DDAutoTracker_Tests.modulemap │ │ └── Pods-DDAutoTracker_Tests.release.xcconfig └── Tests │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md └── _Pods.xcodeproj /DDAutoTracker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint DDAutoTracker.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'DDAutoTracker' 11 | s.version = '0.1.1' 12 | s.summary = '轻量级无痕埋点解决方案' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = '无埋点方案主要依靠AOP(Aspect Oriented Programming)面向切片编程,通过预编译方式和运行期动态代理针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。' 21 | 22 | s.homepage = 'https://github.com/luojilab/DDAutoTracker-iOS' 23 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'wanghailiang' => 'wanghailiang@luojilab.com' } 26 | s.source = { :git => 'https://github.com/luojilab/DDAutoTracker-iOS.git', :tag => 'v' + s.version.to_s } 27 | # s.social_media_url = 'https://twitter.com/' 28 | 29 | s.ios.deployment_target = '8.0' 30 | 31 | s.source_files = 'DDAutoTracker/Classes/*.{h,m}' 32 | s.public_header_files = 'DDAutoTracker/Classes/*.h' 33 | 34 | # s.resource_bundles = { 35 | # 'DDAutoTracker' => ['DDAutoTracker/Assets/*.png'] 36 | # } 37 | 38 | # s.frameworks = 'UIKit', 'MapKit' 39 | # s.dependency 'AFNetworking', '~> 2.3' 40 | end 41 | 42 | -------------------------------------------------------------------------------- /DDAutoTracker/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luojilab/DDAutoTracker-iOS/23d5960fa593ce09165bea4c823f6d6539058c7e/DDAutoTracker/Assets/.gitkeep -------------------------------------------------------------------------------- /DDAutoTracker/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luojilab/DDAutoTracker-iOS/23d5960fa593ce09165bea4c823f6d6539058c7e/DDAutoTracker/Classes/.gitkeep -------------------------------------------------------------------------------- /DDAutoTracker/Classes/DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTracker.h 3 | // Pods 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #ifndef DDAutoTracker_h 9 | #define DDAutoTracker_h 10 | 11 | #import "DDAutoTrackerManager.h" 12 | #import "NSObject+DDAutoTracker.h" 13 | 14 | #endif /* DDAutoTracker_h */ 15 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/DDAutoTrackerManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTrackerManager.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | static NSString *DDAutoTrackerEventIDKey = @"DD_TRACKER_EVENTID_KEY"; 11 | static NSString *DDAutoTrackerInfoKey = @"DD_TRACKER_INFO_KEY"; 12 | 13 | typedef void(^DDAutoTrackerManagerBlock)(NSDictionary *trackerDictionary); 14 | 15 | @interface DDAutoTrackerManager : NSObject 16 | 17 | /** 18 | 是否开启调试模式 19 | */ 20 | @property (nonatomic, assign) BOOL isDebug; 21 | 22 | /** 23 | 配置数据 24 | */ 25 | @property (nonatomic, strong) NSArray *configArray; 26 | 27 | @property (nonatomic, copy) DDAutoTrackerManagerBlock successBlock; 28 | @property (nonatomic, copy) DDAutoTrackerManagerBlock debugBlock; 29 | 30 | + (DDAutoTrackerManager *)sharedInstance; 31 | 32 | /** 33 | 开始打点 34 | 35 | @param successBlock 成功回调 36 | @param debugBlock 调试模式回调 37 | */ 38 | - (void)startWithCompletionBlockWithSuccess:(DDAutoTrackerManagerBlock)successBlock debug:(DDAutoTrackerManagerBlock)debugBlock; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/DDAutoTrackerManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTrackerManager.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "DDAutoTrackerManager.h" 9 | #import "UIButton+DDAutoTracker.h" 10 | #import "UITableView+DDAutoTracker.h" 11 | #import "UICollectionView+DDAutoTracker.h" 12 | #import "UIView+DDAutoTracker.h" 13 | 14 | @implementation DDAutoTrackerManager 15 | 16 | + (instancetype)sharedInstance { 17 | static id _sharedInstance = nil; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | _sharedInstance = [[self alloc] init]; 21 | }); 22 | return _sharedInstance; 23 | } 24 | 25 | #pragma mark - public method 26 | /** 27 | 开始打点 28 | 29 | @param successBlock 成功回调 30 | @param debugBlock 调试模式回调 31 | */ 32 | - (void)startWithCompletionBlockWithSuccess:(DDAutoTrackerManagerBlock)successBlock debug:(DDAutoTrackerManagerBlock)debugBlock { 33 | static dispatch_once_t once; 34 | dispatch_once(&once, ^ { 35 | [UIButton startTracker]; 36 | [UITableView startTracker]; 37 | [UICollectionView startTracker]; 38 | [UIView startTracker]; 39 | }); 40 | 41 | self.successBlock = successBlock; 42 | self.debugBlock = debugBlock; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/DDAutoTrackerOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTrackerOperation.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface DDAutoTrackerOperation : NSObject 11 | 12 | + (DDAutoTrackerOperation *)sharedInstance; 13 | 14 | /** 15 | 发送日志 16 | 17 | @param eventId 事件id 18 | @param info 日志内容 19 | */ 20 | - (void)sendTrackerData:(NSString *)eventId info:(NSDictionary *)info; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/DDAutoTrackerOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTrackerOperation.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "DDAutoTrackerOperation.h" 9 | #import "DDAutoTrackerManager.h" 10 | #import "NSObject+DDAutoTracker.h" 11 | 12 | @implementation DDAutoTrackerOperation 13 | 14 | + (instancetype)sharedInstance { 15 | static id _sharedInstance = nil; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | _sharedInstance = [[self alloc] init]; 19 | }); 20 | return _sharedInstance; 21 | } 22 | 23 | /** 24 | 发送日志 25 | 26 | @param eventId 日志id 27 | @param info 日志内容 28 | */ 29 | - (void)sendTrackerData:(NSString *)eventId info:(NSDictionary *)info { 30 | NSDictionary *trackerDictionary = [[NSDictionary alloc] initWithObjectsAndKeys: 31 | eventId.length > 0 ? eventId : @"", DDAutoTrackerEventIDKey, 32 | info ? info : [[NSDictionary alloc] init], DDAutoTrackerInfoKey, nil]; 33 | 34 | if ([DDAutoTrackerManager sharedInstance].configArray.count > 0 && 35 | eventId.length > 0) { 36 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(DD_TRACKER_EVENTID_KEY == %@)",eventId]; 37 | NSArray *filtered = [[DDAutoTrackerManager sharedInstance].configArray filteredArrayUsingPredicate:predicate]; 38 | if ([filtered count] > 0) { 39 | if ([DDAutoTrackerManager sharedInstance].successBlock) { 40 | [DDAutoTrackerManager sharedInstance].successBlock(trackerDictionary); 41 | } 42 | } 43 | } 44 | 45 | if ([DDAutoTrackerManager sharedInstance].isDebug && 46 | [DDAutoTrackerManager sharedInstance].debugBlock) { 47 | [DDAutoTrackerManager sharedInstance].debugBlock(trackerDictionary); 48 | } 49 | } 50 | 51 | @end 52 | 53 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/NSObject+DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+DDAutoTracker.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface NSObject (DDAutoTracker) 11 | 12 | @property (nonatomic ,strong) NSDictionary *ddInfoDictionary; 13 | 14 | - (void)configInfoData:(id)obj; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/NSObject+DDAutoTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+DDAutoTracker.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "NSObject+DDAutoTracker.h" 9 | #import 10 | 11 | static void * ddInfoDictionaryPropertyKey = &ddInfoDictionaryPropertyKey; 12 | 13 | @implementation NSObject (DDAutoTracker) 14 | 15 | - (NSDictionary *)ddInfoDictionary { 16 | return objc_getAssociatedObject(self, ddInfoDictionaryPropertyKey); 17 | } 18 | 19 | - (void)setDdInfoDictionary:(NSDictionary *)ddInfoDictionary { 20 | if (ddInfoDictionary) { 21 | objc_setAssociatedObject(self, ddInfoDictionaryPropertyKey, ddInfoDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 22 | } 23 | } 24 | 25 | - (void)configInfoData:(id)obj { 26 | if (nil == obj) { 27 | return; 28 | } 29 | if ([obj isKindOfClass:[NSDictionary class]]) { 30 | self.ddInfoDictionary = obj; 31 | }else { 32 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 33 | 34 | unsigned count; 35 | objc_property_t *properties = class_copyPropertyList([obj class], &count); 36 | 37 | for (int i = 0; i < count; i++) { 38 | NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])]; 39 | if (key.length > 0 && 40 | [obj valueForKey:key]) { 41 | [dict setObject:[obj valueForKey:key] forKey:key]; 42 | } 43 | } 44 | 45 | free(properties); 46 | 47 | if (dict) { 48 | self.ddInfoDictionary = dict; 49 | } 50 | } 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UIButton+DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+DDAutoTracker.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface UIButton (DDAutoTracker) 11 | 12 | + (void)startTracker; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UIButton+DDAutoTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+DDAutoTracker.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "UIButton+DDAutoTracker.h" 9 | #import 10 | #import 11 | #import "DDAutoTrackerOperation.h" 12 | #import "NSObject+DDAutoTracker.h" 13 | 14 | @implementation UIButton (DDAutoTracker) 15 | 16 | + (void)startTracker { 17 | Method endTrackingMethod = class_getInstanceMethod(self, @selector(endTrackingWithTouch:withEvent:)); 18 | Method ddEndTrackingMethod = class_getInstanceMethod(self, @selector(dd_endTrackingWithTouch:withEvent:)); 19 | method_exchangeImplementations(endTrackingMethod, ddEndTrackingMethod); 20 | } 21 | 22 | - (void)dd_endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { 23 | //只监听UIButton 24 | if (![self isMemberOfClass:[UIButton class]]) { 25 | return; 26 | } 27 | 28 | [self dd_endTrackingWithTouch:touch withEvent:event]; 29 | NSArray *targers = [self.allTargets allObjects]; 30 | if (targers.count > 0) { 31 | NSArray *actions = [self actionsForTarget:[targers firstObject] forControlEvent:UIControlEventTouchUpInside]; 32 | if (actions.count > 0 && 33 | [[actions firstObject] length] > 0) { 34 | 35 | NSString *eventId = [NSString stringWithFormat:@"%@&&%@",NSStringFromClass([[targers firstObject] class]),[actions firstObject]]; 36 | NSDictionary *infoDictionary = [[targers firstObject] ddInfoDictionary]; 37 | [[DDAutoTrackerOperation sharedInstance] sendTrackerData:eventId 38 | info:infoDictionary]; 39 | } 40 | } 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UICollectionView+DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+DDAutoTracker.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface UICollectionView (DDAutoTracker) 11 | 12 | + (void)startTracker; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UICollectionView+DDAutoTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+DDAutoTracker.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "UICollectionView+DDAutoTracker.h" 9 | #import 10 | #import 11 | #import "DDAutoTrackerOperation.h" 12 | #import "NSObject+DDAutoTracker.h" 13 | 14 | @implementation UICollectionView (DDAutoTracker) 15 | 16 | + (void)startTracker { 17 | Method setDelegateMethod = class_getInstanceMethod(self, @selector(setDelegate:)); 18 | Method ddSetDelegateMethod = class_getInstanceMethod(self, @selector(dd_setDelegate:)); 19 | method_exchangeImplementations(setDelegateMethod, ddSetDelegateMethod); 20 | } 21 | 22 | - (void)dd_setDelegate:(id )delegate { 23 | 24 | //只监听UICollectionView 25 | if (![self isMemberOfClass:[UICollectionView class]]) { 26 | return; 27 | } 28 | 29 | [self dd_setDelegate:delegate]; 30 | if (delegate) { 31 | Class class = [delegate class]; 32 | SEL originSelector = @selector(collectionView:didSelectItemAtIndexPath:); 33 | SEL swizzlSelector = NSSelectorFromString(@"dd_didSelectItemAtIndexPath"); 34 | BOOL didAddMethod = class_addMethod(class, swizzlSelector, (IMP)dd_didSelectItemAtIndexPath, "v@:@@"); 35 | if (didAddMethod) { 36 | Method originMethod = class_getInstanceMethod(class, swizzlSelector); 37 | Method swizzlMethod = class_getInstanceMethod(class, originSelector); 38 | method_exchangeImplementations(originMethod, swizzlMethod); 39 | } 40 | } 41 | } 42 | 43 | void dd_didSelectItemAtIndexPath(id self, SEL _cmd, id collectionView, NSIndexPath *indexpath) { 44 | SEL selector = NSSelectorFromString(@"dd_didSelectItemAtIndexPath"); 45 | ((void(*)(id, SEL,id, NSIndexPath *))objc_msgSend)(self, selector, collectionView, indexpath); 46 | 47 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexpath]; 48 | 49 | NSString *targetString = NSStringFromClass([self class]); 50 | NSString *actionString = NSStringFromSelector(_cmd); 51 | 52 | NSString *eventId = [NSString stringWithFormat:@"%@&&%@",targetString,actionString]; 53 | NSDictionary *infoDictionary = [cell ddInfoDictionary]; 54 | 55 | [[DDAutoTrackerOperation sharedInstance] sendTrackerData:eventId 56 | info:infoDictionary]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UITableView+DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+DDAutoTracker.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface UITableView (DDAutoTracker) 11 | 12 | + (void)startTracker; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UITableView+DDAutoTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+DDAutoTracker.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "UITableView+DDAutoTracker.h" 9 | #import 10 | #import 11 | #import "DDAutoTrackerOperation.h" 12 | #import "NSObject+DDAutoTracker.h" 13 | 14 | @implementation UITableView (DDAutoTracker) 15 | 16 | + (void)startTracker { 17 | Method setDelegateMethod = class_getInstanceMethod(self, @selector(setDelegate:)); 18 | Method ddSetDelegateMethod = class_getInstanceMethod(self, @selector(dd_setDelegate:)); 19 | method_exchangeImplementations(setDelegateMethod, ddSetDelegateMethod); 20 | } 21 | 22 | - (void)dd_setDelegate:(id )delegate { 23 | 24 | //只监听UITableView 25 | if (![self isMemberOfClass:[UITableView class]]) { 26 | return; 27 | } 28 | 29 | [self dd_setDelegate:delegate]; 30 | 31 | if (delegate) { 32 | Class class = [delegate class]; 33 | SEL originSelector = @selector(tableView:didSelectRowAtIndexPath:); 34 | SEL swizzlSelector = NSSelectorFromString(@"dd_didSelectRowAtIndexPath"); 35 | BOOL didAddMethod = class_addMethod(class, swizzlSelector, (IMP)dd_didSelectRowAtIndexPath, "v@:@@"); 36 | if (didAddMethod) { 37 | Method originMethod = class_getInstanceMethod(class, swizzlSelector); 38 | Method swizzlMethod = class_getInstanceMethod(class, originSelector); 39 | method_exchangeImplementations(originMethod, swizzlMethod); 40 | } 41 | } 42 | } 43 | 44 | void dd_didSelectRowAtIndexPath(id self, SEL _cmd, id tableView, NSIndexPath *indexpath) { 45 | SEL selector = NSSelectorFromString(@"dd_didSelectRowAtIndexPath"); 46 | ((void(*)(id, SEL,id, NSIndexPath *))objc_msgSend)(self, selector, tableView, indexpath); 47 | 48 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexpath]; 49 | 50 | NSString *targetString = NSStringFromClass([self class]); 51 | NSString *actionString = NSStringFromSelector(_cmd); 52 | 53 | NSString *eventId = [NSString stringWithFormat:@"%@&&%@",targetString,actionString]; 54 | NSDictionary *infoDictionary = [cell ddInfoDictionary]; 55 | 56 | [[DDAutoTrackerOperation sharedInstance] sendTrackerData:eventId 57 | info:infoDictionary]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UIView+DDAutoTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+DDAutoTracker.h 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import 9 | 10 | @interface UIView (DDAutoTracker) 11 | 12 | + (void)startTracker; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /DDAutoTracker/Classes/UIView+DDAutoTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+DDAutoTracker.m 3 | // DDAutoTracker 4 | // 5 | // Created by 王海亮 on 2017/12/18. 6 | // 7 | 8 | #import "UIView+DDAutoTracker.h" 9 | #import 10 | #import 11 | #import "DDAutoTrackerOperation.h" 12 | #import "NSObject+DDAutoTracker.h" 13 | 14 | @implementation UIView (DDAutoTracker) 15 | 16 | + (void)startTracker { 17 | Method addGestureRecognizerMethod = class_getInstanceMethod(self, @selector(addGestureRecognizer:)); 18 | Method ddAddGestureRecognizerMethod = class_getInstanceMethod(self, @selector(dd_addGestureRecognizer:)); 19 | method_exchangeImplementations(addGestureRecognizerMethod, ddAddGestureRecognizerMethod); 20 | } 21 | 22 | - (void)dd_addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { 23 | [self dd_addGestureRecognizer:gestureRecognizer]; 24 | //只监听UITapGestureRecognizer事件 25 | if ([gestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]]) { 26 | Ivar targetsIvar = class_getInstanceVariable([UIGestureRecognizer class], "_targets"); 27 | id targetActionPairs = object_getIvar(gestureRecognizer, targetsIvar); 28 | 29 | Class targetActionPairClass = NSClassFromString(@"UIGestureRecognizerTarget"); 30 | Ivar targetIvar = class_getInstanceVariable(targetActionPairClass, "_target"); 31 | Ivar actionIvar = class_getInstanceVariable(targetActionPairClass, "_action"); 32 | 33 | for (id targetActionPair in targetActionPairs) { 34 | id target = object_getIvar(targetActionPair, targetIvar); 35 | SEL action = (__bridge void *)object_getIvar(targetActionPair, actionIvar); 36 | if (target && 37 | action) { 38 | Class class = [target class]; 39 | SEL originSelector = action; 40 | SEL swizzlSelector = NSSelectorFromString(@"dd_didTapView"); 41 | BOOL didAddMethod = class_addMethod(class, swizzlSelector, (IMP)dd_didTapView, "v@:@@"); 42 | if (didAddMethod) { 43 | Method originMethod = class_getInstanceMethod(class, swizzlSelector); 44 | Method swizzlMethod = class_getInstanceMethod(class, originSelector); 45 | method_exchangeImplementations(originMethod, swizzlMethod); 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | void dd_didTapView(id self, SEL _cmd, id gestureRecognizer) { 54 | NSMethodSignature *signture = [[self class] instanceMethodSignatureForSelector:_cmd]; 55 | NSUInteger numberOfArguments = signture.numberOfArguments; 56 | SEL selector = NSSelectorFromString(@"dd_didTapView"); 57 | if (3 == numberOfArguments) { 58 | ((void(*)(id, SEL, id))objc_msgSend)(self, selector, gestureRecognizer); 59 | }else if (2 == numberOfArguments) { 60 | ((void(*)(id, SEL))objc_msgSend)(self, selector); 61 | } 62 | 63 | NSString *aciton = NSStringFromSelector(_cmd); 64 | NSString *eventId = [NSString stringWithFormat:@"%@&&%@",NSStringFromClass([self class]),aciton]; 65 | NSDictionary *infoDictionary = [self ddInfoDictionary]; 66 | [[DDAutoTrackerOperation sharedInstance] sendTrackerData:eventId 67 | info:infoDictionary]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Example/DDAutoTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DDAutoTracker.xcodeproj/xcshareddata/xcschemes/DDAutoTracker-Example.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/DDAutoTracker.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DDAutoTracker-Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/DDAutoTracker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/DDAutoTracker.xcworkspace/xcuserdata/wanghailiang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luojilab/DDAutoTracker-iOS/23d5960fa593ce09165bea4c823f6d6539058c7e/Example/DDAutoTracker.xcworkspace/xcuserdata/wanghailiang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/DDAutoTracker/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/DDAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDAppDelegate.h 3 | // DDAutoTracker 4 | // 5 | // Created by yusipeng on 12/18/2017. 6 | // Copyright (c) 2017 yusipeng. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface DDAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/DDAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDAppDelegate.m 3 | // DDAutoTracker 4 | // 5 | // Created by yusipeng on 12/18/2017. 6 | // Copyright (c) 2017 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDAppDelegate.h" 10 | #import "DDExampleViewController.h" 11 | 12 | @implementation DDAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | // Override point for customization after application launch. 17 | 18 | // Override point for customization after application launch. 19 | 20 | DDExampleViewController *exampleViewController = [[DDExampleViewController alloc] init]; 21 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:exampleViewController]; 22 | self.window.rootViewController = navigationController; 23 | [self.window makeKeyAndVisible]; 24 | 25 | //开启打点 26 | [[DDAutoTrackerManager sharedInstance] startWithCompletionBlockWithSuccess:^(NSDictionary *trackerDictionary) { 27 | //成功打点回调 28 | } debug:^(NSDictionary *trackerDictionary) { 29 | //调试模式回调 30 | }]; 31 | //开启调试模式 32 | [DDAutoTrackerManager sharedInstance].isDebug = YES; 33 | //读取本地配置文件 34 | NSString * filePath = [[NSBundle mainBundle] pathForResource:@"tracker" ofType:@"json"]; 35 | NSData * jsonData = [NSData dataWithContentsOfFile:filePath]; 36 | if (jsonData) { 37 | NSError *error; 38 | NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData options: NSJSONReadingMutableContainers error: &error]; 39 | if (nil == error && 40 | jsonArray) { 41 | [DDAutoTrackerManager sharedInstance].configArray = jsonArray; 42 | } 43 | } 44 | 45 | return YES; 46 | } 47 | 48 | - (void)applicationWillResignActive:(UIApplication *)application 49 | { 50 | // 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. 51 | // 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. 52 | } 53 | 54 | - (void)applicationDidEnterBackground:(UIApplication *)application 55 | { 56 | // 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. 57 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 58 | } 59 | 60 | - (void)applicationWillEnterForeground:(UIApplication *)application 61 | { 62 | // 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. 63 | } 64 | 65 | - (void)applicationDidBecomeActive:(UIApplication *)application 66 | { 67 | // 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. 68 | } 69 | 70 | - (void)applicationWillTerminate:(UIApplication *)application 71 | { 72 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/DDAutoTracker-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 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/DDAutoTracker-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 UIKit; 15 | @import Foundation; 16 | #endif 17 | 18 | #import "DDAutoTracker.h" 19 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Models/DDExampleModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDExampleModel.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDExampleModel : NSObject 12 | 13 | @property (nonatomic, strong, readonly) NSString *name; 14 | @property (nonatomic, strong, readonly) Class controllerClass; 15 | 16 | - (instancetype)initWithName:(NSString *)name 17 | controllerClass:(Class)controllerClass; 18 | 19 | - (instancetype)init NS_UNAVAILABLE; 20 | + (instancetype)new NS_UNAVAILABLE; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Models/DDExampleModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDExampleModel.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDExampleModel.h" 10 | 11 | @implementation DDExampleModel 12 | 13 | - (instancetype)initWithName:(NSString *)name 14 | controllerClass:(Class)controllerClass { 15 | if (self = [super init]) { 16 | _name = [name copy]; 17 | _controllerClass = [controllerClass copy]; 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Models/DDItemModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDItemModel.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDItemModel : NSObject 12 | 13 | @property (nonatomic, strong, readonly) NSString *title; 14 | @property (nonatomic, strong, readonly) NSString *intro; 15 | 16 | - (instancetype)initWithTitle:(NSString *)title 17 | intro:(NSString *)intro; 18 | 19 | - (instancetype)init NS_UNAVAILABLE; 20 | + (instancetype)new NS_UNAVAILABLE; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Models/DDItemModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDItemModel.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDItemModel.h" 10 | 11 | @implementation DDItemModel 12 | 13 | - (instancetype)initWithTitle:(NSString *)title 14 | intro:(NSString *)intro { 15 | if (self = [super init]) { 16 | _title = [title copy]; 17 | _intro = [intro copy]; 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Resources/tracker.json: -------------------------------------------------------------------------------- 1 | [{"DD_TRACKER_EVENTID_KEY":"DDButtonViewController&&trackerButtonClick:"},{"DD_TRACKER_EVENTID_KEY":"DDTableViewController&&tableView:didSelectRowAtIndexPath:"},{"DD_TRACKER_EVENTID_KEY":"DDCollectionViewController&&collectionView:didSelectItemAtIndexPath:"},{"DD_TRACKER_EVENTID_KEY":"DDGestureViewController&&gestureLabelClick:"}] 2 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Utils/UIView+DDFrame.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+DDFrame.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (DDFrame) 12 | 13 | @property (nonatomic, assign) CGFloat dd_x; 14 | @property (nonatomic, assign) CGFloat dd_y; 15 | @property (nonatomic, assign) CGFloat dd_width; 16 | @property (nonatomic, assign) CGFloat dd_height; 17 | @property (nonatomic, assign) CGFloat dd_centerX; 18 | @property (nonatomic, assign) CGFloat dd_centerY; 19 | @property (nonatomic, assign) CGSize dd_size; 20 | @property (nonatomic, assign) CGFloat dd_top; 21 | @property (nonatomic, assign) CGFloat dd_bottom; 22 | @property (nonatomic, assign) CGFloat dd_left; 23 | @property (nonatomic, assign) CGFloat dd_right; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Utils/UIView+DDFrame.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+DDFrame.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "UIView+DDFrame.h" 10 | 11 | @implementation UIView (DDFrame) 12 | 13 | -(CGFloat)dd_x { 14 | return self.frame.origin.x; 15 | } 16 | 17 | -(void)setDd_x:(CGFloat)x { 18 | CGRect rect = self.frame; 19 | rect.origin.x = x; 20 | self.frame = rect; 21 | } 22 | 23 | -(CGFloat)dd_y { 24 | return self.frame.origin.y; 25 | } 26 | 27 | -(void)setDd_y:(CGFloat)y { 28 | CGRect rect = self.frame; 29 | rect.origin.y = y; 30 | self.frame = rect; 31 | } 32 | 33 | -(CGFloat)dd_width { 34 | return self.frame.size.width; 35 | } 36 | 37 | -(void)setDd_width:(CGFloat)width { 38 | CGRect rect = self.frame; 39 | rect.size.width = width; 40 | self.frame = rect; 41 | } 42 | 43 | -(CGFloat)dd_height { 44 | return self.frame.size.height; 45 | } 46 | 47 | -(void)setDd_height:(CGFloat)height { 48 | CGRect rect = self.frame; 49 | rect.size.height = height; 50 | self.frame = rect; 51 | } 52 | 53 | -(CGFloat)dd_centerX { 54 | return self.center.x; 55 | } 56 | 57 | -(void)setDd_centerX:(CGFloat)centerX { 58 | CGPoint center = self.center; 59 | center.x = centerX; 60 | self.center = center; 61 | } 62 | 63 | -(CGFloat)dd_centerY { 64 | return self.center.y; 65 | } 66 | 67 | -(void)setDd_centerY:(CGFloat)centerY { 68 | CGPoint center = self.center; 69 | center.y = centerY; 70 | self.center = center; 71 | } 72 | 73 | - (CGSize)dd_size { 74 | return self.frame.size; 75 | } 76 | 77 | - (void)setDd_size:(CGSize)size { 78 | CGRect frame = self.frame; 79 | frame.size = size; 80 | self.frame = frame; 81 | } 82 | 83 | - (CGFloat)dd_top { 84 | return self.frame.origin.y; 85 | } 86 | 87 | - (void)setDd_top:(CGFloat)t { 88 | self.frame = CGRectMake(self.dd_left, t, self.dd_width, self.dd_height); 89 | } 90 | 91 | - (CGFloat)dd_bottom { 92 | return self.frame.origin.y + self.frame.size.height; 93 | } 94 | 95 | - (void)setDd_bottom:(CGFloat)b { 96 | self.frame = CGRectMake(self.dd_left, b - self.dd_height, self.dd_width, self.dd_height); 97 | } 98 | 99 | - (CGFloat)dd_left { 100 | return self.frame.origin.x; 101 | } 102 | 103 | - (void)setDd_left:(CGFloat)l { 104 | self.frame = CGRectMake(l, self.dd_top, self.dd_width, self.dd_height); 105 | } 106 | 107 | - (CGFloat)dd_right { 108 | return self.frame.origin.x + self.frame.size.width; 109 | } 110 | 111 | - (void)setDd_right:(CGFloat)r { 112 | self.frame = CGRectMake(r - self.dd_width, self.dd_top, self.dd_width, self.dd_height); 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDButtonViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDButtonViewController.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDButtonViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDButtonViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDButtonViewController.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDButtonViewController.h" 10 | #import "UIView+DDFrame.h" 11 | 12 | @interface DDButtonViewController () 13 | 14 | @property (nonatomic, strong) UIButton *trackerButton; 15 | 16 | @end 17 | 18 | @implementation DDButtonViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view. 23 | [self setupSubViews]; 24 | 25 | //配置打点info信息 26 | self.ddInfoDictionary = @{@"id":@"your id", 27 | @"type":@"your type"}; 28 | } 29 | 30 | - (void)setupSubViews { 31 | [self.view addSubview:self.trackerButton]; 32 | } 33 | 34 | - (void)viewDidLayoutSubviews { 35 | [super viewDidLayoutSubviews]; 36 | self.trackerButton.dd_width = 180.f; 37 | self.trackerButton.dd_height = 44.f; 38 | self.trackerButton.center = self.view.center; 39 | } 40 | 41 | #pragma mark - button action 42 | 43 | - (void)trackerButtonClick:(UIButton *)sender { 44 | 45 | } 46 | 47 | #pragma mark - setters and getters 48 | 49 | - (UIButton *)trackerButton { 50 | if (nil == _trackerButton) { 51 | _trackerButton = [UIButton buttonWithType:UIButtonTypeCustom]; 52 | [_trackerButton setTitle:@"click me" forState:UIControlStateNormal]; 53 | [_trackerButton setBackgroundColor:[UIColor orangeColor]]; 54 | [_trackerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 55 | [_trackerButton addTarget:self 56 | action:@selector(trackerButtonClick:) 57 | forControlEvents:UIControlEventTouchUpInside]; 58 | } 59 | return _trackerButton; 60 | } 61 | 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDCollectionViewController.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDCollectionViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDCollectionViewController.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDCollectionViewController.h" 10 | #import "DDItemModel.h" 11 | #import "DDCollectionViewCell.h" 12 | 13 | static NSString *kDDCollectionViewCellIdentify = @"kDDCollectionViewCellIdentify"; 14 | 15 | @interface DDCollectionViewController () 16 | 17 | @property (nonatomic, strong) UICollectionView *collectionView; 18 | @property (nonatomic, strong) NSArray *data; 19 | 20 | @end 21 | 22 | @implementation DDCollectionViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view. 27 | [self setupSubviews]; 28 | } 29 | 30 | - (void)setupSubviews { 31 | [self.view addSubview:self.collectionView]; 32 | } 33 | 34 | - (void)viewDidLayoutSubviews { 35 | [super viewDidLayoutSubviews]; 36 | self.collectionView.frame = self.view.bounds; 37 | } 38 | 39 | #pragma mark - UICollectionViewDataSource 40 | - (CGSize)collectionView:(UICollectionView *)collectionView 41 | layout:(UICollectionViewLayout*)collectionViewLayout 42 | sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ 43 | return CGSizeMake(100, 100); 44 | } 45 | 46 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 47 | return UIEdgeInsetsMake(5, 5, 5, 5); 48 | } 49 | 50 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { 51 | return 10; 52 | } 53 | 54 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { 55 | return 10; 56 | } 57 | 58 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 59 | return 1; 60 | } 61 | 62 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 63 | return self.data.count; 64 | } 65 | 66 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 67 | DDCollectionViewCell *collectionViewCell = (DDCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kDDCollectionViewCellIdentify forIndexPath:indexPath]; 68 | if (self.data.count > indexPath.row) { 69 | DDItemModel *model = [self.data objectAtIndex:indexPath.row]; 70 | collectionViewCell.itemModel = model; 71 | } 72 | return collectionViewCell; 73 | } 74 | 75 | #pragma mark - UICollectionViewDelegate 76 | 77 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 78 | 79 | } 80 | 81 | #pragma mark - setters and getters 82 | 83 | - (UICollectionView *)collectionView { 84 | if (nil == _collectionView) { 85 | // UICollectionViewFlowLayout *viewFlowLayout = [[UICollectionViewFlowLayout alloc] init]; 86 | // [viewFlowLayout setItemSize:CGSizeMake(100, 100)]; 87 | // [viewFlowLayout setScrollDirection:UICollectionViewScrollDirectionVertical]; 88 | 89 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero 90 | collectionViewLayout:[UICollectionViewFlowLayout new]]; 91 | [_collectionView setBackgroundColor:[UIColor whiteColor]]; 92 | [_collectionView registerClass:[DDCollectionViewCell class] forCellWithReuseIdentifier:kDDCollectionViewCellIdentify]; 93 | 94 | [_collectionView setDelegate:self]; 95 | [_collectionView setDataSource:self]; 96 | } 97 | return _collectionView; 98 | } 99 | 100 | - (NSArray *)data { 101 | if (nil == _data) { 102 | _data = @[[[DDItemModel alloc] initWithTitle:@"title a" intro:@"intro a"], 103 | [[DDItemModel alloc] initWithTitle:@"title b" intro:@"intro b"], 104 | [[DDItemModel alloc] initWithTitle:@"title c" intro:@"intro c"], 105 | [[DDItemModel alloc] initWithTitle:@"title d" intro:@"intro d"], 106 | [[DDItemModel alloc] initWithTitle:@"title e" intro:@"intro e"],]; 107 | } 108 | return _data; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDExampleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDExampleViewController.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDExampleViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDExampleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDExampleViewController.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDExampleViewController.h" 10 | //Models 11 | #import "DDExampleModel.h" 12 | //Controllers 13 | #import "DDButtonViewController.h" 14 | #import "DDTableViewController.h" 15 | #import "DDCollectionViewController.h" 16 | #import "DDGestureViewController.h" 17 | 18 | @interface DDExampleViewController () 19 | 20 | @property (nonatomic, strong) UITableView *tableView; 21 | @property (nonatomic, strong) NSArray *data; 22 | 23 | @end 24 | 25 | @implementation DDExampleViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | // Do any additional setup after loading the view. 30 | self.title = NSStringFromClass([self class]); 31 | [self setupSubViews]; 32 | self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" 33 | style:UIBarButtonItemStylePlain 34 | target:nil 35 | action:nil]; 36 | } 37 | 38 | - (void)setupSubViews { 39 | [self.view addSubview:self.tableView]; 40 | } 41 | 42 | - (void)viewDidLayoutSubviews { 43 | [super viewDidLayoutSubviews]; 44 | self.tableView.frame = self.view.bounds; 45 | } 46 | 47 | #pragma mark - UITableViewDataSource 48 | 49 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 50 | return 1; 51 | } 52 | 53 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 54 | return self.data.count; 55 | } 56 | 57 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 58 | static NSString *demoCellIdentifier = @"demoCellIdentifier"; 59 | UITableViewCell *demoCell = [tableView dequeueReusableCellWithIdentifier:demoCellIdentifier]; 60 | if (nil == demoCell) { 61 | demoCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 62 | reuseIdentifier:demoCellIdentifier]; 63 | demoCell.selectionStyle = UITableViewCellSelectionStyleNone; 64 | } 65 | if (self.data.count > indexPath.row) { 66 | DDExampleModel *model = [self.data objectAtIndex:indexPath.row]; 67 | demoCell.textLabel.text = model.name; 68 | //配置打点数据 69 | [demoCell configInfoData:model]; 70 | } 71 | return demoCell; 72 | } 73 | 74 | #pragma mark - UITableViewDelegate 75 | 76 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 77 | if (self.data.count > indexPath.row) { 78 | DDExampleModel *item = [self.data objectAtIndex:indexPath.row]; 79 | if (item.controllerClass) { 80 | id obj = [[item.controllerClass alloc] init]; 81 | if ([obj isKindOfClass:[UIViewController class]]) { 82 | UIViewController *viewController = (UIViewController *)obj; 83 | viewController.title = NSStringFromClass(item.controllerClass); 84 | viewController.view.backgroundColor = [UIColor whiteColor]; 85 | [self.navigationController pushViewController:viewController animated:YES]; 86 | } 87 | } 88 | } 89 | } 90 | 91 | #pragma mark - setters and getters 92 | 93 | - (UITableView *)tableView { 94 | if (nil == _tableView) { 95 | _tableView = [[UITableView alloc] init]; 96 | _tableView.dataSource = self; 97 | _tableView.delegate = self; 98 | } 99 | return _tableView; 100 | } 101 | 102 | - (NSArray *)data { 103 | if (nil == _data) { 104 | _data = [[NSArray alloc] initWithObjects: 105 | [[DDExampleModel alloc] initWithName:@"UIButton Example" 106 | controllerClass:[DDButtonViewController class]], 107 | [[DDExampleModel alloc] initWithName:@"UITableView Example" 108 | controllerClass:[DDTableViewController class]], 109 | [[DDExampleModel alloc] initWithName:@"UICollectionView Example" 110 | controllerClass:[DDCollectionViewController class]], 111 | [[DDExampleModel alloc] initWithName:@"Gesture Example" 112 | controllerClass:[DDGestureViewController class]], 113 | nil]; 114 | } 115 | return _data; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDGestureViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDGestureViewController.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDGestureViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDGestureViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDGestureViewController.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDGestureViewController.h" 10 | #import "UIView+DDFrame.h" 11 | 12 | @interface DDGestureViewController () 13 | 14 | @property (nonatomic, strong) UILabel *gestureLabel; 15 | 16 | @end 17 | 18 | @implementation DDGestureViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view. 23 | [self setupSubviews]; 24 | 25 | //配置打点info信息 26 | self.ddInfoDictionary = @{@"id":@"your id", 27 | @"type":@"your type"}; 28 | } 29 | 30 | - (void)setupSubviews { 31 | [self.view addSubview:self.gestureLabel]; 32 | } 33 | 34 | - (void)viewDidLayoutSubviews { 35 | [super viewDidLayoutSubviews]; 36 | self.gestureLabel.dd_width = 180.f; 37 | self.gestureLabel.dd_height = 44.f; 38 | self.gestureLabel.center = self.view.center; 39 | } 40 | #pragma mark - label action 41 | 42 | - (void)gestureLabelClick:(id)sender { 43 | 44 | } 45 | 46 | #pragma mark - setters and getters 47 | 48 | - (UILabel *)gestureLabel { 49 | if (nil == _gestureLabel) { 50 | _gestureLabel = [[UILabel alloc] init]; 51 | _gestureLabel.backgroundColor = [UIColor orangeColor]; 52 | _gestureLabel.textColor = [UIColor whiteColor]; 53 | _gestureLabel.font = [UIFont boldSystemFontOfSize:16.f]; 54 | _gestureLabel.textAlignment = NSTextAlignmentCenter; 55 | _gestureLabel.text = @"click me"; 56 | _gestureLabel.userInteractionEnabled = YES; 57 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureLabelClick:)]; 58 | [_gestureLabel addGestureRecognizer:tap]; 59 | } 60 | return _gestureLabel; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDTableViewController.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DDTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/ViewControllers/DDTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDTableViewController.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDTableViewController.h" 10 | #import "DDItemModel.h" 11 | 12 | @interface DDTableViewController () 13 | 14 | @property (nonatomic, strong) NSArray *data; 15 | 16 | @end 17 | 18 | @implementation DDTableViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | } 24 | 25 | - (void)didReceiveMemoryWarning { 26 | [super didReceiveMemoryWarning]; 27 | // Dispose of any resources that can be recreated. 28 | } 29 | 30 | #pragma mark - Table view data source 31 | 32 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 33 | return 1; 34 | } 35 | 36 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 37 | return self.data.count; 38 | } 39 | 40 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 41 | static NSString *tableViewCellIdentifier = @"tableViewCellIdentifier"; 42 | UITableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:tableViewCellIdentifier]; 43 | if (nil == tableViewCell) { 44 | tableViewCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 45 | reuseIdentifier:tableViewCellIdentifier]; 46 | tableViewCell.selectionStyle = UITableViewCellSelectionStyleNone; 47 | } 48 | if (self.data.count > indexPath.row) { 49 | DDItemModel *model = [self.data objectAtIndex:indexPath.row]; 50 | tableViewCell.textLabel.text = model.title; 51 | tableViewCell.detailTextLabel.text = model.intro; 52 | 53 | //配置打点数据 54 | [tableViewCell configInfoData:model]; 55 | } 56 | return tableViewCell; 57 | } 58 | 59 | #pragma mark - Table view delegate 60 | 61 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 62 | 63 | } 64 | 65 | #pragma mark - setters and getters 66 | 67 | - (NSArray *)data { 68 | if (nil == _data) { 69 | _data = @[[[DDItemModel alloc] initWithTitle:@"title a" intro:@"intro a"], 70 | [[DDItemModel alloc] initWithTitle:@"title b" intro:@"intro b"], 71 | [[DDItemModel alloc] initWithTitle:@"title c" intro:@"intro c"], 72 | [[DDItemModel alloc] initWithTitle:@"title d" intro:@"intro d"], 73 | [[DDItemModel alloc] initWithTitle:@"title e" intro:@"intro e"],]; 74 | } 75 | return _data; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Views/DDCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DDCollectionViewCell.h 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DDItemModel.h" 11 | 12 | @interface DDCollectionViewCell : UICollectionViewCell 13 | 14 | @property (nonatomic, strong) DDItemModel *itemModel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/Views/DDCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDCollectionViewCell.m 3 | // DDAutoTracker_Example 4 | // 5 | // Created by 王海亮 on 2017/12/14. 6 | // Copyright © 2017年 yusipeng. All rights reserved. 7 | // 8 | 9 | #import "DDCollectionViewCell.h" 10 | #import "UIView+DDFrame.h" 11 | 12 | @interface DDCollectionViewCell () 13 | 14 | @property (nonatomic, strong) UILabel *titleLabel; 15 | @property (nonatomic, strong) UILabel *introLabel; 16 | 17 | @end 18 | 19 | @implementation DDCollectionViewCell 20 | 21 | - (instancetype)init { 22 | self = [super init]; 23 | if (self) { 24 | [self setupSubviews]; 25 | } 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | [self setupSubviews]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)setupSubviews { 38 | [self.contentView setBackgroundColor:[UIColor orangeColor]]; 39 | [self.contentView addSubview:self.titleLabel]; 40 | [self.contentView addSubview:self.introLabel]; 41 | } 42 | 43 | - (void)setItemModel:(DDItemModel *)itemModel { 44 | _itemModel = itemModel; 45 | //配置打点信息 46 | [self configInfoData:_itemModel]; 47 | self.titleLabel.text = _itemModel.title; 48 | self.introLabel.text = _itemModel.intro; 49 | } 50 | 51 | #pragma mark - setters and getters 52 | 53 | - (UILabel *)titleLabel { 54 | if (nil == _titleLabel) { 55 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 100, 30)]; 56 | _titleLabel.textColor = [UIColor whiteColor]; 57 | _titleLabel.textAlignment = NSTextAlignmentCenter; 58 | _titleLabel.font = [UIFont boldSystemFontOfSize:14.f]; 59 | } 60 | return _titleLabel; 61 | } 62 | 63 | - (UILabel *)introLabel { 64 | if (nil == _introLabel) { 65 | _introLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 40, 100, 30)]; 66 | _introLabel.textColor = [UIColor whiteColor]; 67 | _introLabel.textAlignment = NSTextAlignmentCenter; 68 | _introLabel.font = [UIFont systemFontOfSize:12.f]; 69 | } 70 | return _introLabel; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/DDAutoTracker/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DDAutoTracker 4 | // 5 | // Created by yusipeng on 12/18/2017. 6 | // Copyright (c) 2017 yusipeng. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "DDAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DDAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'DDAutoTracker_Example' do 4 | pod 'DDAutoTracker', :path => '../' 5 | 6 | target 'DDAutoTracker_Tests' do 7 | inherit! :search_paths 8 | 9 | pod 'FBSnapshotTestCase' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DDAutoTracker (0.1.0) 3 | - FBSnapshotTestCase (2.1.4): 4 | - FBSnapshotTestCase/SwiftSupport (= 2.1.4) 5 | - FBSnapshotTestCase/Core (2.1.4) 6 | - FBSnapshotTestCase/SwiftSupport (2.1.4): 7 | - FBSnapshotTestCase/Core 8 | 9 | DEPENDENCIES: 10 | - DDAutoTracker (from `../`) 11 | - FBSnapshotTestCase 12 | 13 | EXTERNAL SOURCES: 14 | DDAutoTracker: 15 | :path: ../ 16 | 17 | SPEC CHECKSUMS: 18 | DDAutoTracker: 20764f1eb6bfe309686f7966406a95e768e401ee 19 | FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a 20 | 21 | PODFILE CHECKSUM: fc96756595b659a3071b3e47c4f2d29b9da10124 22 | 23 | COCOAPODS: 1.3.1 24 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIApplication+StrictKeyWindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @interface UIApplication (StrictKeyWindow) 14 | 15 | /** 16 | @return The receiver's @c keyWindow. Raises an assertion if @c nil. 17 | */ 18 | - (UIWindow *)fb_strictKeyWindow; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIApplication+StrictKeyWindow.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @implementation UIApplication (StrictKeyWindow) 14 | 15 | - (UIWindow *)fb_strictKeyWindow 16 | { 17 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 18 | if (!keyWindow) { 19 | [NSException raise:@"FBSnapshotTestCaseNilKeyWindowException" 20 | format:@"Snapshot tests must be hosted by an application with a key window. Please ensure your test" 21 | " host sets up a key window at launch (either via storyboards or programmatically) and doesn't" 22 | " do anything to remove it while snapshot tests are running."]; 23 | } 24 | return keyWindow; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Gabriel Handford on 3/1/09. 3 | // Copyright 2009-2013. All rights reserved. 4 | // Created by John Boiles on 10/20/11. 5 | // Copyright (c) 2011. All rights reserved 6 | // Modified by Felix Schulze on 2/11/13. 7 | // Copyright 2013. All rights reserved. 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the "Software"), to deal in the Software without 12 | // restriction, including without limitation the rights to use, 13 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be 19 | // included in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | // OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | 31 | #import 32 | 33 | @interface UIImage (Compare) 34 | 35 | - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Gabriel Handford on 3/1/09. 3 | // Copyright 2009-2013. All rights reserved. 4 | // Created by John Boiles on 10/20/11. 5 | // Copyright (c) 2011. All rights reserved 6 | // Modified by Felix Schulze on 2/11/13. 7 | // Copyright 2013. All rights reserved. 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the "Software"), to deal in the Software without 12 | // restriction, including without limitation the rights to use, 13 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be 19 | // included in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | // OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | 31 | #import 32 | 33 | // This makes debugging much more fun 34 | typedef union { 35 | uint32_t raw; 36 | unsigned char bytes[4]; 37 | struct { 38 | char red; 39 | char green; 40 | char blue; 41 | char alpha; 42 | } __attribute__ ((packed)) pixels; 43 | } FBComparePixel; 44 | 45 | @implementation UIImage (Compare) 46 | 47 | - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance 48 | { 49 | NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size."); 50 | 51 | CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage)); 52 | CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage)); 53 | 54 | // The images have the equal size, so we could use the smallest amount of bytes because of byte padding 55 | size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage)); 56 | size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow; 57 | void *referenceImagePixels = calloc(1, referenceImageSizeBytes); 58 | void *imagePixels = calloc(1, referenceImageSizeBytes); 59 | 60 | if (!referenceImagePixels || !imagePixels) { 61 | free(referenceImagePixels); 62 | free(imagePixels); 63 | return NO; 64 | } 65 | 66 | CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels, 67 | referenceImageSize.width, 68 | referenceImageSize.height, 69 | CGImageGetBitsPerComponent(self.CGImage), 70 | minBytesPerRow, 71 | CGImageGetColorSpace(self.CGImage), 72 | (CGBitmapInfo)kCGImageAlphaPremultipliedLast 73 | ); 74 | CGContextRef imageContext = CGBitmapContextCreate(imagePixels, 75 | imageSize.width, 76 | imageSize.height, 77 | CGImageGetBitsPerComponent(image.CGImage), 78 | minBytesPerRow, 79 | CGImageGetColorSpace(image.CGImage), 80 | (CGBitmapInfo)kCGImageAlphaPremultipliedLast 81 | ); 82 | 83 | if (!referenceImageContext || !imageContext) { 84 | CGContextRelease(referenceImageContext); 85 | CGContextRelease(imageContext); 86 | free(referenceImagePixels); 87 | free(imagePixels); 88 | return NO; 89 | } 90 | 91 | CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage); 92 | CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage); 93 | 94 | CGContextRelease(referenceImageContext); 95 | CGContextRelease(imageContext); 96 | 97 | BOOL imageEqual = YES; 98 | 99 | // Do a fast compare if we can 100 | if (tolerance == 0) { 101 | imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0); 102 | } else { 103 | // Go through each pixel in turn and see if it is different 104 | const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height; 105 | 106 | FBComparePixel *p1 = referenceImagePixels; 107 | FBComparePixel *p2 = imagePixels; 108 | 109 | NSInteger numDiffPixels = 0; 110 | for (int n = 0; n < pixelCount; ++n) { 111 | // If this pixel is different, increment the pixel diff count and see 112 | // if we have hit our limit. 113 | if (p1->raw != p2->raw) { 114 | numDiffPixels ++; 115 | 116 | CGFloat percent = (CGFloat)numDiffPixels / pixelCount; 117 | if (percent > tolerance) { 118 | imageEqual = NO; 119 | break; 120 | } 121 | } 122 | 123 | p1++; 124 | p2++; 125 | } 126 | } 127 | 128 | free(referenceImagePixels); 129 | free(imagePixels); 130 | 131 | return imageEqual; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Gabriel Handford on 3/1/09. 3 | // Copyright 2009-2013. All rights reserved. 4 | // Created by John Boiles on 10/20/11. 5 | // Copyright (c) 2011. All rights reserved 6 | // Modified by Felix Schulze on 2/11/13. 7 | // Copyright 2013. All rights reserved. 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the "Software"), to deal in the Software without 12 | // restriction, including without limitation the rights to use, 13 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be 19 | // included in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | // OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | 31 | #import 32 | 33 | @interface UIImage (Diff) 34 | 35 | - (UIImage *)fb_diffWithImage:(UIImage *)image; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Gabriel Handford on 3/1/09. 3 | // Copyright 2009-2013. All rights reserved. 4 | // Created by John Boiles on 10/20/11. 5 | // Copyright (c) 2011. All rights reserved 6 | // Modified by Felix Schulze on 2/11/13. 7 | // Copyright 2013. All rights reserved. 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the "Software"), to deal in the Software without 12 | // restriction, including without limitation the rights to use, 13 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be 19 | // included in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | // OTHER DEALINGS IN THE SOFTWARE. 29 | // 30 | 31 | #import 32 | 33 | @implementation UIImage (Diff) 34 | 35 | - (UIImage *)fb_diffWithImage:(UIImage *)image 36 | { 37 | if (!image) { 38 | return nil; 39 | } 40 | CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height)); 41 | UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); 42 | CGContextRef context = UIGraphicsGetCurrentContext(); 43 | [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; 44 | CGContextSetAlpha(context, 0.5); 45 | CGContextBeginTransparencyLayer(context, NULL); 46 | [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; 47 | CGContextSetBlendMode(context, kCGBlendModeDifference); 48 | CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor); 49 | CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height)); 50 | CGContextEndTransparencyLayer(context); 51 | UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext(); 52 | UIGraphicsEndImageContext(); 53 | return returnImage; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @interface UIImage (Snapshot) 14 | 15 | /// Uses renderInContext: to get a snapshot of the layer. 16 | + (UIImage *)fb_imageForLayer:(CALayer *)layer; 17 | 18 | /// Uses renderInContext: to get a snapshot of the view layer. 19 | + (UIImage *)fb_imageForViewLayer:(UIView *)view; 20 | 21 | /// Uses drawViewHierarchyInRect: to get a snapshot of the view and adds the view into a window if needed. 22 | + (UIImage *)fb_imageForView:(UIView *)view; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | @implementation UIImage (Snapshot) 15 | 16 | + (UIImage *)fb_imageForLayer:(CALayer *)layer 17 | { 18 | CGRect bounds = layer.bounds; 19 | NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer); 20 | NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer); 21 | 22 | UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0); 23 | CGContextRef context = UIGraphicsGetCurrentContext(); 24 | NSAssert1(context, @"Could not generate context for layer %@", layer); 25 | CGContextSaveGState(context); 26 | [layer layoutIfNeeded]; 27 | [layer renderInContext:context]; 28 | CGContextRestoreGState(context); 29 | 30 | UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); 31 | UIGraphicsEndImageContext(); 32 | return snapshot; 33 | } 34 | 35 | + (UIImage *)fb_imageForViewLayer:(UIView *)view 36 | { 37 | [view layoutIfNeeded]; 38 | return [self fb_imageForLayer:view.layer]; 39 | } 40 | 41 | + (UIImage *)fb_imageForView:(UIView *)view 42 | { 43 | CGRect bounds = view.bounds; 44 | NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view); 45 | NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view); 46 | 47 | // If the input view is already a UIWindow, then just use that. Otherwise wrap in a window. 48 | UIWindow *window = [view isKindOfClass:[UIWindow class]] ? (UIWindow *)view : view.window; 49 | BOOL removeFromSuperview = NO; 50 | if (!window) { 51 | window = [[UIApplication sharedApplication] fb_strictKeyWindow]; 52 | } 53 | 54 | if (!view.window && view != window) { 55 | [window addSubview:view]; 56 | removeFromSuperview = YES; 57 | } 58 | 59 | UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0); 60 | [view layoutIfNeeded]; 61 | [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; 62 | 63 | UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); 64 | UIGraphicsEndImageContext(); 65 | 66 | if (removeFromSuperview) { 67 | [view removeFromSuperview]; 68 | } 69 | 70 | return snapshot; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | #import 15 | 16 | #import 17 | 18 | #import 19 | 20 | /* 21 | There are three ways of setting reference image directories. 22 | 23 | 1. Set the preprocessor macro FB_REFERENCE_IMAGE_DIR to a double quoted 24 | c-string with the path. 25 | 2. Set an environment variable named FB_REFERENCE_IMAGE_DIR with the path. This 26 | takes precedence over the preprocessor macro to allow for run-time override. 27 | 3. Keep everything unset, which will cause the reference images to be looked up 28 | inside the bundle holding the current test, in the 29 | Resources/ReferenceImages_* directories. 30 | */ 31 | #ifndef FB_REFERENCE_IMAGE_DIR 32 | #define FB_REFERENCE_IMAGE_DIR "" 33 | #endif 34 | 35 | /** 36 | Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. 37 | @param view The view to snapshot 38 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 39 | @param suffixes An NSOrderedSet of strings for the different suffixes 40 | @param tolerance The percentage of pixels that can differ and still count as an 'identical' view 41 | */ 42 | #define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__, tolerance__) \ 43 | FBSnapshotVerifyViewOrLayerWithOptions(View, view__, identifier__, suffixes__, tolerance__) 44 | 45 | #define FBSnapshotVerifyView(view__, identifier__) \ 46 | FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0) 47 | 48 | 49 | /** 50 | Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. 51 | @param layer The layer to snapshot 52 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 53 | @param suffixes An NSOrderedSet of strings for the different suffixes 54 | @param tolerance The percentage of pixels that can differ and still count as an 'identical' layer 55 | */ 56 | #define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__, tolerance__) \ 57 | FBSnapshotVerifyViewOrLayerWithOptions(Layer, layer__, identifier__, suffixes__, tolerance__) 58 | 59 | #define FBSnapshotVerifyLayer(layer__, identifier__) \ 60 | FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0) 61 | 62 | 63 | #define FBSnapshotVerifyViewOrLayerWithOptions(what__, viewOrLayer__, identifier__, suffixes__, tolerance__) \ 64 | { \ 65 | NSString *errorDescription = [self snapshotVerifyViewOrLayer:viewOrLayer__ identifier:identifier__ suffixes:suffixes__ tolerance:tolerance__]; \ 66 | BOOL noErrors = (errorDescription == nil); \ 67 | XCTAssertTrue(noErrors, @"%@", errorDescription); \ 68 | } 69 | 70 | 71 | /** 72 | The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test 73 | and compare an image of the view to a reference image that write lots of complex layout-code tests. 74 | 75 | In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES. 76 | 77 | @attention When recording, the reference image directory should be explicitly 78 | set, otherwise the images may be written to somewhere inside the 79 | simulator directory. 80 | 81 | For example: 82 | @code 83 | - (void)setUp 84 | { 85 | [super setUp]; 86 | self.recordMode = YES; 87 | } 88 | @endcode 89 | */ 90 | @interface FBSnapshotTestCase : XCTestCase 91 | 92 | /** 93 | When YES, the test macros will save reference images, rather than performing an actual test. 94 | */ 95 | @property (readwrite, nonatomic, assign) BOOL recordMode; 96 | 97 | /** 98 | When @c YES appends the name of the device model and OS to the snapshot file name. 99 | The default value is @c NO. 100 | */ 101 | @property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic; 102 | 103 | /** 104 | When YES, renders a snapshot of the complete view hierarchy as visible onscreen. 105 | There are several things that do not work if renderInContext: is used. 106 | - UIVisualEffect #70 107 | - UIAppearance #91 108 | - Size Classes #92 109 | 110 | @attention If the view does't belong to a UIWindow, it will create one and add the view as a subview. 111 | */ 112 | @property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect; 113 | 114 | - (void)setUp NS_REQUIRES_SUPER; 115 | - (void)tearDown NS_REQUIRES_SUPER; 116 | 117 | /** 118 | Performs the comparison or records a snapshot of the layer if recordMode is YES. 119 | @param viewOrLayer The UIView or CALayer to snapshot 120 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 121 | @param suffixes An NSOrderedSet of strings for the different suffixes 122 | @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care 123 | @returns nil if the comparison (or saving of the reference image) succeeded. Otherwise it contains an error description. 124 | */ 125 | - (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer 126 | identifier:(NSString *)identifier 127 | suffixes:(NSOrderedSet *)suffixes 128 | tolerance:(CGFloat)tolerance; 129 | 130 | /** 131 | Performs the comparison or records a snapshot of the layer if recordMode is YES. 132 | @param layer The Layer to snapshot 133 | @param referenceImagesDirectory The directory in which reference images are stored. 134 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 135 | @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care 136 | @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 137 | @returns YES if the comparison (or saving of the reference image) succeeded. 138 | */ 139 | - (BOOL)compareSnapshotOfLayer:(CALayer *)layer 140 | referenceImagesDirectory:(NSString *)referenceImagesDirectory 141 | identifier:(NSString *)identifier 142 | tolerance:(CGFloat)tolerance 143 | error:(NSError **)errorPtr; 144 | 145 | /** 146 | Performs the comparison or records a snapshot of the view if recordMode is YES. 147 | @param view The view to snapshot 148 | @param referenceImagesDirectory The directory in which reference images are stored. 149 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 150 | @param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care 151 | @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 152 | @returns YES if the comparison (or saving of the reference image) succeeded. 153 | */ 154 | - (BOOL)compareSnapshotOfView:(UIView *)view 155 | referenceImagesDirectory:(NSString *)referenceImagesDirectory 156 | identifier:(NSString *)identifier 157 | tolerance:(CGFloat)tolerance 158 | error:(NSError **)errorPtr; 159 | 160 | /** 161 | Checks if reference image with identifier based name exists in the reference images directory. 162 | @param referenceImagesDirectory The directory in which reference images are stored. 163 | @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. 164 | @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 165 | @returns YES if reference image exists. 166 | */ 167 | - (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory 168 | identifier:(NSString *)identifier 169 | error:(NSError **)errorPtr; 170 | 171 | /** 172 | Returns the reference image directory. 173 | 174 | Helper function used to implement the assert macros. 175 | 176 | @param dir directory to use if environment variable not specified. Ignored if null or empty. 177 | */ 178 | - (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir; 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | @implementation FBSnapshotTestCase 15 | { 16 | FBSnapshotTestController *_snapshotController; 17 | } 18 | 19 | #pragma mark - Overrides 20 | 21 | - (void)setUp 22 | { 23 | [super setUp]; 24 | _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:NSStringFromClass([self class])]; 25 | } 26 | 27 | - (void)tearDown 28 | { 29 | _snapshotController = nil; 30 | [super tearDown]; 31 | } 32 | 33 | - (BOOL)recordMode 34 | { 35 | return _snapshotController.recordMode; 36 | } 37 | 38 | - (void)setRecordMode:(BOOL)recordMode 39 | { 40 | NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); 41 | _snapshotController.recordMode = recordMode; 42 | } 43 | 44 | - (BOOL)isDeviceAgnostic 45 | { 46 | return _snapshotController.deviceAgnostic; 47 | } 48 | 49 | - (void)setDeviceAgnostic:(BOOL)deviceAgnostic 50 | { 51 | NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); 52 | _snapshotController.deviceAgnostic = deviceAgnostic; 53 | } 54 | 55 | - (BOOL)usesDrawViewHierarchyInRect 56 | { 57 | return _snapshotController.usesDrawViewHierarchyInRect; 58 | } 59 | 60 | - (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect 61 | { 62 | NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); 63 | _snapshotController.usesDrawViewHierarchyInRect = usesDrawViewHierarchyInRect; 64 | } 65 | 66 | #pragma mark - Public API 67 | 68 | - (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer 69 | identifier:(NSString *)identifier 70 | suffixes:(NSOrderedSet *)suffixes 71 | tolerance:(CGFloat)tolerance 72 | { 73 | if (nil == viewOrLayer) { 74 | return @"Object to be snapshotted must not be nil"; 75 | } 76 | NSString *referenceImageDirectory = [self getReferenceImageDirectoryWithDefault:(@ FB_REFERENCE_IMAGE_DIR)]; 77 | if (referenceImageDirectory == nil) { 78 | return @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme."; 79 | } 80 | if (suffixes.count == 0) { 81 | return [NSString stringWithFormat:@"Suffixes set cannot be empty %@", suffixes]; 82 | } 83 | 84 | BOOL testSuccess = NO; 85 | NSError *error = nil; 86 | NSMutableArray *errors = [NSMutableArray array]; 87 | 88 | if (self.recordMode) { 89 | NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes.firstObject]; 90 | BOOL referenceImageSaved = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:(identifier) tolerance:tolerance error:&error]; 91 | if (!referenceImageSaved) { 92 | [errors addObject:error]; 93 | } 94 | } else { 95 | for (NSString *suffix in suffixes) { 96 | NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffix]; 97 | BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory identifier:(identifier) error:&error]; 98 | 99 | if (referenceImageAvailable) { 100 | BOOL comparisonSuccess = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory identifier:identifier tolerance:tolerance error:&error]; 101 | [errors removeAllObjects]; 102 | if (comparisonSuccess) { 103 | testSuccess = YES; 104 | break; 105 | } else { 106 | [errors addObject:error]; 107 | } 108 | } else { 109 | [errors addObject:error]; 110 | } 111 | } 112 | } 113 | 114 | if (!testSuccess) { 115 | return [NSString stringWithFormat:@"Snapshot comparison failed: %@", errors.firstObject]; 116 | } 117 | if (self.recordMode) { 118 | return @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"; 119 | } 120 | 121 | return nil; 122 | } 123 | 124 | - (BOOL)compareSnapshotOfLayer:(CALayer *)layer 125 | referenceImagesDirectory:(NSString *)referenceImagesDirectory 126 | identifier:(NSString *)identifier 127 | tolerance:(CGFloat)tolerance 128 | error:(NSError **)errorPtr 129 | { 130 | return [self _compareSnapshotOfViewOrLayer:layer 131 | referenceImagesDirectory:referenceImagesDirectory 132 | identifier:identifier 133 | tolerance:tolerance 134 | error:errorPtr]; 135 | } 136 | 137 | - (BOOL)compareSnapshotOfView:(UIView *)view 138 | referenceImagesDirectory:(NSString *)referenceImagesDirectory 139 | identifier:(NSString *)identifier 140 | tolerance:(CGFloat)tolerance 141 | error:(NSError **)errorPtr 142 | { 143 | return [self _compareSnapshotOfViewOrLayer:view 144 | referenceImagesDirectory:referenceImagesDirectory 145 | identifier:identifier 146 | tolerance:tolerance 147 | error:errorPtr]; 148 | } 149 | 150 | - (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory 151 | identifier:(NSString *)identifier 152 | error:(NSError **)errorPtr 153 | { 154 | NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); 155 | _snapshotController.referenceImagesDirectory = referenceImagesDirectory; 156 | UIImage *referenceImage = [_snapshotController referenceImageForSelector:self.invocation.selector 157 | identifier:identifier 158 | error:errorPtr]; 159 | 160 | return (referenceImage != nil); 161 | } 162 | 163 | - (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir 164 | { 165 | NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"]; 166 | if (envReferenceImageDirectory) { 167 | return envReferenceImageDirectory; 168 | } 169 | if (dir && dir.length > 0) { 170 | return dir; 171 | } 172 | return [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"]; 173 | } 174 | 175 | 176 | #pragma mark - Private API 177 | 178 | - (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer 179 | referenceImagesDirectory:(NSString *)referenceImagesDirectory 180 | identifier:(NSString *)identifier 181 | tolerance:(CGFloat)tolerance 182 | error:(NSError **)errorPtr 183 | { 184 | _snapshotController.referenceImagesDirectory = referenceImagesDirectory; 185 | return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer 186 | selector:self.invocation.selector 187 | identifier:identifier 188 | tolerance:tolerance 189 | error:errorPtr]; 190 | } 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | /** 18 | Returns a Boolean value that indicates whether the snapshot test is running in 64Bit. 19 | This method is a convenience for creating the suffixes set based on the architecture 20 | that the test is running. 21 | 22 | @returns @c YES if the test is running in 64bit, otherwise @c NO. 23 | */ 24 | BOOL FBSnapshotTestCaseIs64Bit(void); 25 | 26 | /** 27 | Returns a default set of strings that is used to append a suffix based on the architectures. 28 | @warning Do not modify this function, you can create your own and use it with @c FBSnapshotVerifyViewWithOptions() 29 | 30 | @returns An @c NSOrderedSet object containing strings that are appended to the reference images directory. 31 | */ 32 | NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void); 33 | 34 | /** 35 | Returns a fully «normalized» file name. 36 | Strips punctuation and spaces and replaces them with @c _. Also appends the device model, running OS and screen size to the file name. 37 | 38 | @returns An @c NSString object containing the passed @c fileName with the device model, OS and screen size appended at the end. 39 | */ 40 | NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | BOOL FBSnapshotTestCaseIs64Bit(void) 16 | { 17 | #if __LP64__ 18 | return YES; 19 | #else 20 | return NO; 21 | #endif 22 | } 23 | 24 | NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void) 25 | { 26 | NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init]; 27 | [suffixesSet addObject:@"_32"]; 28 | [suffixesSet addObject:@"_64"]; 29 | if (FBSnapshotTestCaseIs64Bit()) { 30 | return [suffixesSet reversedOrderedSet]; 31 | } 32 | return [suffixesSet copy]; 33 | } 34 | 35 | NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName) 36 | { 37 | UIDevice *device = [UIDevice currentDevice]; 38 | UIWindow *keyWindow = [[UIApplication sharedApplication] fb_strictKeyWindow]; 39 | CGSize screenSize = keyWindow.bounds.size; 40 | NSString *os = device.systemVersion; 41 | 42 | fileName = [NSString stringWithFormat:@"%@_%@%@_%.0fx%.0f", fileName, device.model, os, screenSize.width, screenSize.height]; 43 | 44 | NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new]; 45 | [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; 46 | [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; 47 | NSArray *validComponents = [fileName componentsSeparatedByCharactersInSet:invalidCharacters]; 48 | fileName = [validComponents componentsJoinedByString:@"_"]; 49 | 50 | return fileName; 51 | } -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) { 15 | FBSnapshotTestControllerErrorCodeUnknown, 16 | FBSnapshotTestControllerErrorCodeNeedsRecord, 17 | FBSnapshotTestControllerErrorCodePNGCreationFailed, 18 | FBSnapshotTestControllerErrorCodeImagesDifferentSizes, 19 | FBSnapshotTestControllerErrorCodeImagesDifferent, 20 | }; 21 | /** 22 | Errors returned by the methods of FBSnapshotTestController use this domain. 23 | */ 24 | extern NSString *const FBSnapshotTestControllerErrorDomain; 25 | 26 | /** 27 | Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. 28 | */ 29 | extern NSString *const FBReferenceImageFilePathKey; 30 | 31 | /** 32 | Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. 33 | */ 34 | extern NSString *const FBReferenceImageKey; 35 | 36 | /** 37 | Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. 38 | */ 39 | extern NSString *const FBCapturedImageKey; 40 | 41 | /** 42 | Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. 43 | */ 44 | extern NSString *const FBDiffedImageKey; 45 | 46 | /** 47 | Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel- 48 | by-pixel comparison of images. 49 | Instances are initialized with the test class, and directories to read and write to. 50 | */ 51 | @interface FBSnapshotTestController : NSObject 52 | 53 | /** 54 | Record snapshots. 55 | */ 56 | @property (readwrite, nonatomic, assign) BOOL recordMode; 57 | 58 | /** 59 | When @c YES appends the name of the device model and OS to the snapshot file name. 60 | The default value is @c NO. 61 | */ 62 | @property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic; 63 | 64 | /** 65 | Uses drawViewHierarchyInRect:afterScreenUpdates: to draw the image instead of renderInContext: 66 | */ 67 | @property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect; 68 | 69 | /** 70 | The directory in which referfence images are stored. 71 | */ 72 | @property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory; 73 | 74 | /** 75 | @param testClass The subclass of FBSnapshotTestCase that is using this controller. 76 | @returns An instance of FBSnapshotTestController. 77 | */ 78 | - (instancetype)initWithTestClass:(Class)testClass; 79 | 80 | /** 81 | Designated initializer. 82 | @param testName The name of the tests. 83 | @returns An instance of FBSnapshotTestController. 84 | */ 85 | - (instancetype)initWithTestName:(NSString *)testName; 86 | 87 | /** 88 | Performs the comparison of the layer. 89 | @param layer The Layer to snapshot. 90 | @param selector The test method being run. 91 | @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. 92 | @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 93 | @returns YES if the comparison (or saving of the reference image) succeeded. 94 | */ 95 | - (BOOL)compareSnapshotOfLayer:(CALayer *)layer 96 | selector:(SEL)selector 97 | identifier:(NSString *)identifier 98 | error:(NSError **)errorPtr; 99 | 100 | /** 101 | Performs the comparison of the view. 102 | @param view The view to snapshot. 103 | @param selector The test method being run. 104 | @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. 105 | @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 106 | @returns YES if the comparison (or saving of the reference image) succeeded. 107 | */ 108 | - (BOOL)compareSnapshotOfView:(UIView *)view 109 | selector:(SEL)selector 110 | identifier:(NSString *)identifier 111 | error:(NSError **)errorPtr; 112 | 113 | /** 114 | Performs the comparison of a view or layer. 115 | @param view The view or layer to snapshot. 116 | @param selector The test method being run. 117 | @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. 118 | @param tolerance The percentage of pixels that can differ and still be considered 'identical' 119 | @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). 120 | @returns YES if the comparison (or saving of the reference image) succeeded. 121 | */ 122 | - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer 123 | selector:(SEL)selector 124 | identifier:(NSString *)identifier 125 | tolerance:(CGFloat)tolerance 126 | error:(NSError **)errorPtr; 127 | 128 | /** 129 | Loads a reference image. 130 | @param selector The test method being run. 131 | @param identifier The optional identifier, used when multiple images are tested in a single -test method. 132 | @param errorPtr An error, if this methods returns nil, the error will be something useful. 133 | @returns An image. 134 | */ 135 | - (UIImage *)referenceImageForSelector:(SEL)selector 136 | identifier:(NSString *)identifier 137 | error:(NSError **)errorPtr; 138 | 139 | /** 140 | Performs a pixel-by-pixel comparison of the two images with an allowable margin of error. 141 | @param referenceImage The reference (correct) image. 142 | @param image The image to test against the reference. 143 | @param tolerance The percentage of pixels that can differ and still be considered 'identical' 144 | @param errorPtr An error that indicates why the comparison failed if it does. 145 | @returns YES if the comparison succeeded and the images are the same(ish). 146 | */ 147 | - (BOOL)compareReferenceImage:(UIImage *)referenceImage 148 | toImage:(UIImage *)image 149 | tolerance:(CGFloat)tolerance 150 | error:(NSError **)errorPtr; 151 | 152 | /** 153 | Saves the reference image and the test image to `failedOutputDirectory`. 154 | @param referenceImage The reference (correct) image. 155 | @param testImage The image to test against the reference. 156 | @param selector The test method being run. 157 | @param identifier The optional identifier, used when multiple images are tested in a single -test method. 158 | @param errorPtr An error that indicates why the comparison failed if it does. 159 | @returns YES if the save succeeded. 160 | */ 161 | - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage 162 | testImage:(UIImage *)testImage 163 | selector:(SEL)selector 164 | identifier:(NSString *)identifier 165 | error:(NSError **)errorPtr; 166 | @end 167 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | #import 18 | 19 | NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain"; 20 | NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey"; 21 | NSString *const FBReferenceImageKey = @"FBReferenceImageKey"; 22 | NSString *const FBCapturedImageKey = @"FBCapturedImageKey"; 23 | NSString *const FBDiffedImageKey = @"FBDiffedImageKey"; 24 | 25 | typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) { 26 | FBTestSnapshotFileNameTypeReference, 27 | FBTestSnapshotFileNameTypeFailedReference, 28 | FBTestSnapshotFileNameTypeFailedTest, 29 | FBTestSnapshotFileNameTypeFailedTestDiff, 30 | }; 31 | 32 | @implementation FBSnapshotTestController 33 | { 34 | NSString *_testName; 35 | NSFileManager *_fileManager; 36 | } 37 | 38 | #pragma mark - Initializers 39 | 40 | - (instancetype)initWithTestClass:(Class)testClass; 41 | { 42 | return [self initWithTestName:NSStringFromClass(testClass)]; 43 | } 44 | 45 | - (instancetype)initWithTestName:(NSString *)testName 46 | { 47 | if (self = [super init]) { 48 | _testName = [testName copy]; 49 | _deviceAgnostic = NO; 50 | 51 | _fileManager = [[NSFileManager alloc] init]; 52 | } 53 | return self; 54 | } 55 | 56 | #pragma mark - Overrides 57 | 58 | - (NSString *)description 59 | { 60 | return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory]; 61 | } 62 | 63 | #pragma mark - Public API 64 | 65 | - (BOOL)compareSnapshotOfLayer:(CALayer *)layer 66 | selector:(SEL)selector 67 | identifier:(NSString *)identifier 68 | error:(NSError **)errorPtr 69 | { 70 | return [self compareSnapshotOfViewOrLayer:layer 71 | selector:selector 72 | identifier:identifier 73 | tolerance:0 74 | error:errorPtr]; 75 | } 76 | 77 | - (BOOL)compareSnapshotOfView:(UIView *)view 78 | selector:(SEL)selector 79 | identifier:(NSString *)identifier 80 | error:(NSError **)errorPtr 81 | { 82 | return [self compareSnapshotOfViewOrLayer:view 83 | selector:selector 84 | identifier:identifier 85 | tolerance:0 86 | error:errorPtr]; 87 | } 88 | 89 | - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer 90 | selector:(SEL)selector 91 | identifier:(NSString *)identifier 92 | tolerance:(CGFloat)tolerance 93 | error:(NSError **)errorPtr 94 | { 95 | if (self.recordMode) { 96 | return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr]; 97 | } else { 98 | return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr]; 99 | } 100 | } 101 | 102 | - (UIImage *)referenceImageForSelector:(SEL)selector 103 | identifier:(NSString *)identifier 104 | error:(NSError **)errorPtr 105 | { 106 | NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; 107 | UIImage *image = [UIImage imageWithContentsOfFile:filePath]; 108 | if (nil == image && NULL != errorPtr) { 109 | BOOL exists = [_fileManager fileExistsAtPath:filePath]; 110 | if (!exists) { 111 | *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain 112 | code:FBSnapshotTestControllerErrorCodeNeedsRecord 113 | userInfo:@{ 114 | FBReferenceImageFilePathKey: filePath, 115 | NSLocalizedDescriptionKey: @"Unable to load reference image.", 116 | NSLocalizedFailureReasonErrorKey: @"Reference image not found. You need to run the test in record mode", 117 | }]; 118 | } else { 119 | *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain 120 | code:FBSnapshotTestControllerErrorCodeUnknown 121 | userInfo:nil]; 122 | } 123 | } 124 | return image; 125 | } 126 | 127 | - (BOOL)compareReferenceImage:(UIImage *)referenceImage 128 | toImage:(UIImage *)image 129 | tolerance:(CGFloat)tolerance 130 | error:(NSError **)errorPtr 131 | { 132 | BOOL sameImageDimensions = CGSizeEqualToSize(referenceImage.size, image.size); 133 | if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance]) { 134 | return YES; 135 | } 136 | 137 | if (NULL != errorPtr) { 138 | NSString *errorDescription = sameImageDimensions ? @"Images different" : @"Images different sizes"; 139 | NSString *errorReason = sameImageDimensions ? [NSString stringWithFormat:@"image pixels differed by more than %.2f%% from the reference image", tolerance * 100] 140 | : [NSString stringWithFormat:@"referenceImage:%@, image:%@", NSStringFromCGSize(referenceImage.size), NSStringFromCGSize(image.size)]; 141 | FBSnapshotTestControllerErrorCode errorCode = sameImageDimensions ? FBSnapshotTestControllerErrorCodeImagesDifferent : FBSnapshotTestControllerErrorCodeImagesDifferentSizes; 142 | 143 | *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain 144 | code:errorCode 145 | userInfo:@{ 146 | NSLocalizedDescriptionKey: errorDescription, 147 | NSLocalizedFailureReasonErrorKey: errorReason, 148 | FBReferenceImageKey: referenceImage, 149 | FBCapturedImageKey: image, 150 | FBDiffedImageKey: [referenceImage fb_diffWithImage:image], 151 | }]; 152 | } 153 | return NO; 154 | } 155 | 156 | - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage 157 | testImage:(UIImage *)testImage 158 | selector:(SEL)selector 159 | identifier:(NSString *)identifier 160 | error:(NSError **)errorPtr 161 | { 162 | NSData *referencePNGData = UIImagePNGRepresentation(referenceImage); 163 | NSData *testPNGData = UIImagePNGRepresentation(testImage); 164 | 165 | NSString *referencePath = [self _failedFilePathForSelector:selector 166 | identifier:identifier 167 | fileNameType:FBTestSnapshotFileNameTypeFailedReference]; 168 | 169 | NSError *creationError = nil; 170 | BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent] 171 | withIntermediateDirectories:YES 172 | attributes:nil 173 | error:&creationError]; 174 | if (!didCreateDir) { 175 | if (NULL != errorPtr) { 176 | *errorPtr = creationError; 177 | } 178 | return NO; 179 | } 180 | 181 | if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) { 182 | return NO; 183 | } 184 | 185 | NSString *testPath = [self _failedFilePathForSelector:selector 186 | identifier:identifier 187 | fileNameType:FBTestSnapshotFileNameTypeFailedTest]; 188 | 189 | if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) { 190 | return NO; 191 | } 192 | 193 | NSString *diffPath = [self _failedFilePathForSelector:selector 194 | identifier:identifier 195 | fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff]; 196 | 197 | UIImage *diffImage = [referenceImage fb_diffWithImage:testImage]; 198 | NSData *diffImageData = UIImagePNGRepresentation(diffImage); 199 | 200 | if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) { 201 | return NO; 202 | } 203 | 204 | NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n" 205 | @"ksdiff \"%@\" \"%@\"", referencePath, testPath); 206 | 207 | return YES; 208 | } 209 | 210 | #pragma mark - Private API 211 | 212 | - (NSString *)_fileNameForSelector:(SEL)selector 213 | identifier:(NSString *)identifier 214 | fileNameType:(FBTestSnapshotFileNameType)fileNameType 215 | { 216 | NSString *fileName = nil; 217 | switch (fileNameType) { 218 | case FBTestSnapshotFileNameTypeFailedReference: 219 | fileName = @"reference_"; 220 | break; 221 | case FBTestSnapshotFileNameTypeFailedTest: 222 | fileName = @"failed_"; 223 | break; 224 | case FBTestSnapshotFileNameTypeFailedTestDiff: 225 | fileName = @"diff_"; 226 | break; 227 | default: 228 | fileName = @""; 229 | break; 230 | } 231 | fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)]; 232 | if (0 < identifier.length) { 233 | fileName = [fileName stringByAppendingFormat:@"_%@", identifier]; 234 | } 235 | 236 | if (self.isDeviceAgnostic) { 237 | fileName = FBDeviceAgnosticNormalizedFileName(fileName); 238 | } 239 | 240 | if ([[UIScreen mainScreen] scale] > 1) { 241 | fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]]; 242 | } 243 | fileName = [fileName stringByAppendingPathExtension:@"png"]; 244 | return fileName; 245 | } 246 | 247 | - (NSString *)_referenceFilePathForSelector:(SEL)selector 248 | identifier:(NSString *)identifier 249 | { 250 | NSString *fileName = [self _fileNameForSelector:selector 251 | identifier:identifier 252 | fileNameType:FBTestSnapshotFileNameTypeReference]; 253 | NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:_testName]; 254 | filePath = [filePath stringByAppendingPathComponent:fileName]; 255 | return filePath; 256 | } 257 | 258 | - (NSString *)_failedFilePathForSelector:(SEL)selector 259 | identifier:(NSString *)identifier 260 | fileNameType:(FBTestSnapshotFileNameType)fileNameType 261 | { 262 | NSString *fileName = [self _fileNameForSelector:selector 263 | identifier:identifier 264 | fileNameType:fileNameType]; 265 | NSString *folderPath = NSTemporaryDirectory(); 266 | if (getenv("IMAGE_DIFF_DIR")) { 267 | folderPath = @(getenv("IMAGE_DIFF_DIR")); 268 | } 269 | NSString *filePath = [folderPath stringByAppendingPathComponent:_testName]; 270 | filePath = [filePath stringByAppendingPathComponent:fileName]; 271 | return filePath; 272 | } 273 | 274 | - (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer 275 | selector:(SEL)selector 276 | identifier:(NSString *)identifier 277 | tolerance:(CGFloat)tolerance 278 | error:(NSError **)errorPtr 279 | { 280 | UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr]; 281 | if (nil != referenceImage) { 282 | UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer]; 283 | BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr]; 284 | if (!imagesSame) { 285 | NSError *saveError = nil; 286 | if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) { 287 | NSLog(@"Error saving test images: %@", saveError); 288 | } 289 | } 290 | return imagesSame; 291 | } 292 | return NO; 293 | } 294 | 295 | - (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer 296 | selector:(SEL)selector 297 | identifier:(NSString *)identifier 298 | error:(NSError **)errorPtr 299 | { 300 | UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer]; 301 | return [self _saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr]; 302 | } 303 | 304 | - (BOOL)_saveReferenceImage:(UIImage *)image 305 | selector:(SEL)selector 306 | identifier:(NSString *)identifier 307 | error:(NSError **)errorPtr 308 | { 309 | BOOL didWrite = NO; 310 | if (nil != image) { 311 | NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; 312 | NSData *pngData = UIImagePNGRepresentation(image); 313 | if (nil != pngData) { 314 | NSError *creationError = nil; 315 | BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] 316 | withIntermediateDirectories:YES 317 | attributes:nil 318 | error:&creationError]; 319 | if (!didCreateDir) { 320 | if (NULL != errorPtr) { 321 | *errorPtr = creationError; 322 | } 323 | return NO; 324 | } 325 | didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr]; 326 | if (didWrite) { 327 | NSLog(@"Reference image save at: %@", filePath); 328 | } 329 | } else { 330 | if (nil != errorPtr) { 331 | *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain 332 | code:FBSnapshotTestControllerErrorCodePNGCreationFailed 333 | userInfo:@{ 334 | FBReferenceImageFilePathKey: filePath, 335 | }]; 336 | } 337 | } 338 | } 339 | return didWrite; 340 | } 341 | 342 | - (UIImage *)_imageForViewOrLayer:(id)viewOrLayer 343 | { 344 | if ([viewOrLayer isKindOfClass:[UIView class]]) { 345 | if (_usesDrawViewHierarchyInRect) { 346 | return [UIImage fb_imageForView:viewOrLayer]; 347 | } else { 348 | return [UIImage fb_imageForViewLayer:viewOrLayer]; 349 | } 350 | } else if ([viewOrLayer isKindOfClass:[CALayer class]]) { 351 | return [UIImage fb_imageForLayer:viewOrLayer]; 352 | } else { 353 | [NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer]; 354 | } 355 | return nil; 356 | } 357 | 358 | @end 359 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/SwiftSupport.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #if swift(>=3) 12 | public extension FBSnapshotTestCase { 13 | public func FBSnapshotVerifyView(_ view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 14 | FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) 15 | } 16 | 17 | public func FBSnapshotVerifyLayer(_ layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 18 | FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) 19 | } 20 | 21 | private func FBSnapshotVerifyViewOrLayer(_ viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 22 | let envReferenceImageDirectory = self.getReferenceImageDirectory(withDefault: FB_REFERENCE_IMAGE_DIR) 23 | var error: NSError? 24 | var comparisonSuccess = false 25 | 26 | if let envReferenceImageDirectory = envReferenceImageDirectory { 27 | for suffix in suffixes { 28 | let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" 29 | if viewOrLayer.isKind(of: UIView.self) { 30 | do { 31 | try compareSnapshot(of: viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) 32 | comparisonSuccess = true 33 | } catch let error1 as NSError { 34 | error = error1 35 | comparisonSuccess = false 36 | } 37 | } else if viewOrLayer.isKind(of: CALayer.self) { 38 | do { 39 | try compareSnapshot(of: viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) 40 | comparisonSuccess = true 41 | } catch let error1 as NSError { 42 | error = error1 43 | comparisonSuccess = false 44 | } 45 | } else { 46 | assertionFailure("Only UIView and CALayer classes can be snapshotted") 47 | } 48 | 49 | assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line) 50 | 51 | if comparisonSuccess || recordMode { 52 | break 53 | } 54 | 55 | assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line) 56 | } 57 | } else { 58 | XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.") 59 | } 60 | } 61 | 62 | func assert(_ assertion: Bool, message: String, file: StaticString, line: UInt) { 63 | if !assertion { 64 | XCTFail(message, file: file, line: line) 65 | } 66 | } 67 | } 68 | #else 69 | public extension FBSnapshotTestCase { 70 | public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 71 | FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) 72 | } 73 | 74 | public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 75 | FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line) 76 | } 77 | 78 | private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { 79 | let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR) 80 | var error: NSError? 81 | var comparisonSuccess = false 82 | 83 | if let envReferenceImageDirectory = envReferenceImageDirectory { 84 | for suffix in suffixes { 85 | let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" 86 | if viewOrLayer.isKindOfClass(UIView) { 87 | do { 88 | try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) 89 | comparisonSuccess = true 90 | } catch let error1 as NSError { 91 | error = error1 92 | comparisonSuccess = false 93 | } 94 | } else if viewOrLayer.isKindOfClass(CALayer) { 95 | do { 96 | try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance) 97 | comparisonSuccess = true 98 | } catch let error1 as NSError { 99 | error = error1 100 | comparisonSuccess = false 101 | } 102 | } else { 103 | assertionFailure("Only UIView and CALayer classes can be snapshotted") 104 | } 105 | 106 | assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line) 107 | 108 | if comparisonSuccess || recordMode { 109 | break 110 | } 111 | 112 | assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line) 113 | } 114 | } else { 115 | XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.") 116 | } 117 | } 118 | 119 | func assert(assertion: Bool, message: String, file: StaticString, line: UInt) { 120 | if !assertion { 121 | XCTFail(message, file: file, line: line) 122 | } 123 | } 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For the FBSnapshotTestCase software 4 | 5 | Copyright (c) 2013, Facebook, Inc. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name Facebook nor the names of its contributors may be used to 17 | endorse or promote products derived from this software without specific 18 | prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Example/Pods/FBSnapshotTestCase/README.md: -------------------------------------------------------------------------------- 1 | FBSnapshotTestCase 2 | ====================== 3 | 4 | [![Build Status](https://travis-ci.org/facebook/ios-snapshot-test-case.svg)](https://travis-ci.org/facebook/ios-snapshot-test-case) [![Cocoa Pod Version](https://cocoapod-badges.herokuapp.com/v/FBSnapshotTestCase/badge.svg)](http://cocoadocs.org/docsets/FBSnapshotTestCase/) 5 | 6 | What it does 7 | ------------ 8 | 9 | A "snapshot test case" takes a configured `UIView` or `CALayer` and uses the 10 | `renderInContext:` method to get an image snapshot of its contents. It 11 | compares this snapshot to a "reference image" stored in your source code 12 | repository and fails the test if the two images don't match. 13 | 14 | Why? 15 | ---- 16 | 17 | At Facebook we write a lot of UI code. As you might imagine, each type of 18 | feed story is rendered using a subclass of `UIView`. There are a lot of edge 19 | cases that we want to handle correctly: 20 | 21 | - What if there is more text than can fit in the space available? 22 | - What if an image doesn't match the size of an image view? 23 | - What should the highlighted state look like? 24 | 25 | It's straightforward to test logic code, but less obvious how you should test 26 | views. You can do a lot of rectangle asserts, but these are hard to understand 27 | or visualize. Looking at an image diff shows you exactly what changed and how 28 | it will look to users. 29 | 30 | We developed `FBSnapshotTestCase` to make snapshot tests easy. 31 | 32 | Installation with CocoaPods 33 | --------------------------- 34 | 35 | 1. Add the following lines to your Podfile: 36 | 37 | ``` 38 | target "Tests" do 39 | pod 'FBSnapshotTestCase' 40 | end 41 | ``` 42 | 43 | If you support iOS 7 use `FBSnapshotTestCase/Core` instead, which doesn't contain Swift support. 44 | 45 | Replace "Tests" with the name of your test project. 46 | 47 | 2. There are [three ways](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L19-L29) of setting reference image directories, the recommended one is to define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should point to the directory where you want reference images to be stored. At Facebook, we normally use this: 48 | 49 | |Name|Value| 50 | |:---|:----| 51 | |`FB_REFERENCE_IMAGE_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages`| 52 | 53 | 54 | ![](FBSnapshotTestCaseDemo/Scheme_FB_REFERENCE_IMAGE_DIR.png) 55 | 56 | Creating a snapshot test 57 | ------------------------ 58 | 59 | 1. Subclass `FBSnapshotTestCase` instead of `XCTestCase`. 60 | 2. From within your test, use `FBSnapshotVerifyView`. 61 | 3. Run the test once with `self.recordMode = YES;` in the test's `-setUp` 62 | method. (This creates the reference images on disk.) 63 | 4. Remove the line enabling record mode and run the test. 64 | 65 | Features 66 | -------- 67 | 68 | - Automatically names reference images on disk according to test class and 69 | selector. 70 | - Prints a descriptive error message to the console on failure. (Bonus: 71 | failure message includes a one-line command to see an image diff if 72 | you have [Kaleidoscope](http://www.kaleidoscopeapp.com) installed.) 73 | - Supply an optional "identifier" if you want to perform multiple snapshots 74 | in a single test method. 75 | - Support for `CALayer` via `FBSnapshotVerifyLayer`. 76 | - `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes. 77 | - `isDeviceAgnostic` to allow appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version and screen size to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices). 78 | 79 | Notes 80 | ----- 81 | 82 | Your unit test must be an "application test", not a "logic test." (That is, it 83 | must be run within the Simulator so that it has access to UIKit.) In Xcode 5 84 | and later new projects only offer application tests, but older projects will 85 | have separate targets for the two types. 86 | 87 | Authors 88 | ------- 89 | 90 | `FBSnapshotTestCase` was written at Facebook by 91 | [Jonathan Dann](https://facebook.com/j.p.dann) with significant contributions by 92 | [Todd Krabach](https://facebook.com/toddkrabach). 93 | 94 | License 95 | ------- 96 | 97 | `FBSnapshotTestCase` is BSD-licensed. See `LICENSE`. 98 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/DDAutoTracker.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DDAutoTracker", 3 | "version": "0.1.0", 4 | "summary": "轻量级无痕埋点解决方案", 5 | "description": "无埋点方案主要依靠AOP(Aspect Oriented Programming)面向切片编程,通过预编译方式和运行期动态代理针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。", 6 | "homepage": "https://gitlab.luojilab.com/iget-iOS/DDAutoTracker", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "wanghailiang": "wanghailiang@luojilab.com" 13 | }, 14 | "source": { 15 | "git": "https://gitlab.luojilab.com/iget-iOS/DDAutoTracker.git", 16 | "tag": "v0.1.0" 17 | }, 18 | "platforms": { 19 | "ios": "8.0" 20 | }, 21 | "source_files": "DDAutoTracker/Classes/*.{h,m}", 22 | "public_header_files": "DDAutoTracker/Classes/*.h" 23 | } 24 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DDAutoTracker (0.1.0) 3 | - FBSnapshotTestCase (2.1.4): 4 | - FBSnapshotTestCase/SwiftSupport (= 2.1.4) 5 | - FBSnapshotTestCase/Core (2.1.4) 6 | - FBSnapshotTestCase/SwiftSupport (2.1.4): 7 | - FBSnapshotTestCase/Core 8 | 9 | DEPENDENCIES: 10 | - DDAutoTracker (from `../`) 11 | - FBSnapshotTestCase 12 | 13 | EXTERNAL SOURCES: 14 | DDAutoTracker: 15 | :path: ../ 16 | 17 | SPEC CHECKSUMS: 18 | DDAutoTracker: 20764f1eb6bfe309686f7966406a95e768e401ee 19 | FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a 20 | 21 | PODFILE CHECKSUM: fc96756595b659a3071b3e47c4f2d29b9da10124 22 | 23 | COCOAPODS: 1.3.1 24 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/DDAutoTracker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/FBSnapshotTestCase.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/Pods-DDAutoTracker_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 68 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/Pods-DDAutoTracker_Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 68 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/wanghailiang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DDAutoTracker.xcscheme 8 | 9 | isShown 10 | 11 | 12 | FBSnapshotTestCase.xcscheme 13 | 14 | isShown 15 | 16 | 17 | Pods-DDAutoTracker_Example.xcscheme 18 | 19 | isShown 20 | 21 | 22 | Pods-DDAutoTracker_Tests.xcscheme 23 | 24 | isShown 25 | 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/DDAutoTracker-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_DDAutoTracker : NSObject 3 | @end 4 | @implementation PodsDummy_DDAutoTracker 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/DDAutoTracker-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/DDAutoTracker-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "DDAutoTracker.h" 14 | #import "DDAutoTrackerManager.h" 15 | #import "DDAutoTrackerOperation.h" 16 | #import "NSObject+DDAutoTracker.h" 17 | #import "UIButton+DDAutoTracker.h" 18 | #import "UICollectionView+DDAutoTracker.h" 19 | #import "UITableView+DDAutoTracker.h" 20 | #import "UIView+DDAutoTracker.h" 21 | 22 | FOUNDATION_EXPORT double DDAutoTrackerVersionNumber; 23 | FOUNDATION_EXPORT const unsigned char DDAutoTrackerVersionString[]; 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/DDAutoTracker.modulemap: -------------------------------------------------------------------------------- 1 | framework module DDAutoTracker { 2 | umbrella header "DDAutoTracker-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/DDAutoTracker.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | PODS_BUILD_DIR = $BUILD_DIR 5 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/DDAutoTracker/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 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_FBSnapshotTestCase : NSObject 3 | @end 4 | @implementation PodsDummy_FBSnapshotTestCase 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "FBSnapshotTestCase.h" 14 | #import "FBSnapshotTestCasePlatform.h" 15 | #import "FBSnapshotTestController.h" 16 | 17 | FOUNDATION_EXPORT double FBSnapshotTestCaseVersionNumber; 18 | FOUNDATION_EXPORT const unsigned char FBSnapshotTestCaseVersionString[]; 19 | 20 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase.modulemap: -------------------------------------------------------------------------------- 1 | framework module FBSnapshotTestCase { 2 | umbrella header "FBSnapshotTestCase-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/FBSnapshotTestCase 2 | ENABLE_BITCODE = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 6 | OTHER_LDFLAGS = -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/FBSnapshotTestCase 12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 13 | SKIP_INSTALL = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/FBSnapshotTestCase/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 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.1.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/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 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## DDAutoTracker 5 | 6 | Copyright (c) 2017 yusipeng 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 - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-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) 2017 yusipeng <yusipeng@luojilab.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 | License 38 | MIT 39 | Title 40 | DDAutoTracker 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_DDAutoTracker_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_DDAutoTracker_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 10 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 11 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 12 | 13 | install_framework() 14 | { 15 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 16 | local source="${BUILT_PRODUCTS_DIR}/$1" 17 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 18 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 19 | elif [ -r "$1" ]; then 20 | local source="$1" 21 | fi 22 | 23 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 24 | 25 | if [ -L "${source}" ]; then 26 | echo "Symlinked..." 27 | source="$(readlink "${source}")" 28 | fi 29 | 30 | # Use filter instead of exclude so missing patterns don't throw errors. 31 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 32 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 33 | 34 | local basename 35 | basename="$(basename -s .framework "$1")" 36 | binary="${destination}/${basename}.framework/${basename}" 37 | if ! [ -r "$binary" ]; then 38 | binary="${destination}/${basename}" 39 | fi 40 | 41 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 42 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 43 | strip_invalid_archs "$binary" 44 | fi 45 | 46 | # Resign the code if required by the build settings to avoid unstable apps 47 | code_sign_if_enabled "${destination}/$(basename "$1")" 48 | 49 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 50 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 51 | local swift_runtime_libs 52 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 53 | for lib in $swift_runtime_libs; do 54 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 55 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 56 | code_sign_if_enabled "${destination}/${lib}" 57 | done 58 | fi 59 | } 60 | 61 | # Copies the dSYM of a vendored framework 62 | install_dsym() { 63 | local source="$1" 64 | if [ -r "$source" ]; then 65 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" 66 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" 67 | fi 68 | } 69 | 70 | # Signs a framework with the provided identity 71 | code_sign_if_enabled() { 72 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 73 | # Use the current code_sign_identitiy 74 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 75 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 76 | 77 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 78 | code_sign_cmd="$code_sign_cmd &" 79 | fi 80 | echo "$code_sign_cmd" 81 | eval "$code_sign_cmd" 82 | fi 83 | } 84 | 85 | # Strip invalid architectures 86 | strip_invalid_archs() { 87 | binary="$1" 88 | # Get architectures for current file 89 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 90 | stripped="" 91 | for arch in $archs; do 92 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 93 | # Strip non-valid architectures in-place 94 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 95 | stripped="$stripped $arch" 96 | fi 97 | done 98 | if [[ "$stripped" ]]; then 99 | echo "Stripped $binary of architectures:$stripped" 100 | fi 101 | } 102 | 103 | 104 | if [[ "$CONFIGURATION" == "Debug" ]]; then 105 | install_framework "${BUILT_PRODUCTS_DIR}/DDAutoTracker/DDAutoTracker.framework" 106 | fi 107 | if [[ "$CONFIGURATION" == "Release" ]]; then 108 | install_framework "${BUILT_PRODUCTS_DIR}/DDAutoTracker/DDAutoTracker.framework" 109 | fi 110 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 111 | wait 112 | fi 113 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_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 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_DDAutoTracker_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_DDAutoTracker_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker/DDAutoTracker.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "DDAutoTracker" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_DDAutoTracker_Example { 2 | umbrella header "Pods-DDAutoTracker_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Example/Pods-DDAutoTracker_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker/DDAutoTracker.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "DDAutoTracker" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/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 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## FBSnapshotTestCase 5 | 6 | BSD License 7 | 8 | For the FBSnapshotTestCase software 9 | 10 | Copyright (c) 2013, Facebook, Inc. 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | * Redistributions of source code must retain the above copyright notice, 17 | this list of conditions and the following disclaimer. 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | * Neither the name Facebook nor the names of its contributors may be used to 22 | endorse or promote products derived from this software without specific 23 | prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 26 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | Generated by CocoaPods - https://cocoapods.org 37 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-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 | BSD License 18 | 19 | For the FBSnapshotTestCase software 20 | 21 | Copyright (c) 2013, Facebook, Inc. 22 | All rights reserved. 23 | 24 | Redistribution and use in source and binary forms, with or without 25 | modification, are permitted provided that the following conditions are met: 26 | 27 | * Redistributions of source code must retain the above copyright notice, 28 | this list of conditions and the following disclaimer. 29 | * Redistributions in binary form must reproduce the above copyright notice, 30 | this list of conditions and the following disclaimer in the documentation 31 | and/or other materials provided with the distribution. 32 | * Neither the name Facebook nor the names of its contributors may be used to 33 | endorse or promote products derived from this software without specific 34 | prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 40 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 41 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 42 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 43 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 44 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 | 47 | License 48 | BSD 49 | Title 50 | FBSnapshotTestCase 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | FooterText 56 | Generated by CocoaPods - https://cocoapods.org 57 | Title 58 | 59 | Type 60 | PSGroupSpecifier 61 | 62 | 63 | StringsTable 64 | Acknowledgements 65 | Title 66 | Acknowledgements 67 | 68 | 69 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_DDAutoTracker_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_DDAutoTracker_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 10 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 11 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 12 | 13 | install_framework() 14 | { 15 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 16 | local source="${BUILT_PRODUCTS_DIR}/$1" 17 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 18 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 19 | elif [ -r "$1" ]; then 20 | local source="$1" 21 | fi 22 | 23 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 24 | 25 | if [ -L "${source}" ]; then 26 | echo "Symlinked..." 27 | source="$(readlink "${source}")" 28 | fi 29 | 30 | # Use filter instead of exclude so missing patterns don't throw errors. 31 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 32 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 33 | 34 | local basename 35 | basename="$(basename -s .framework "$1")" 36 | binary="${destination}/${basename}.framework/${basename}" 37 | if ! [ -r "$binary" ]; then 38 | binary="${destination}/${basename}" 39 | fi 40 | 41 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 42 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 43 | strip_invalid_archs "$binary" 44 | fi 45 | 46 | # Resign the code if required by the build settings to avoid unstable apps 47 | code_sign_if_enabled "${destination}/$(basename "$1")" 48 | 49 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 50 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 51 | local swift_runtime_libs 52 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 53 | for lib in $swift_runtime_libs; do 54 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 55 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 56 | code_sign_if_enabled "${destination}/${lib}" 57 | done 58 | fi 59 | } 60 | 61 | # Copies the dSYM of a vendored framework 62 | install_dsym() { 63 | local source="$1" 64 | if [ -r "$source" ]; then 65 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" 66 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" 67 | fi 68 | } 69 | 70 | # Signs a framework with the provided identity 71 | code_sign_if_enabled() { 72 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 73 | # Use the current code_sign_identitiy 74 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 75 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 76 | 77 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 78 | code_sign_cmd="$code_sign_cmd &" 79 | fi 80 | echo "$code_sign_cmd" 81 | eval "$code_sign_cmd" 82 | fi 83 | } 84 | 85 | # Strip invalid architectures 86 | strip_invalid_archs() { 87 | binary="$1" 88 | # Get architectures for current file 89 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 90 | stripped="" 91 | for arch in $archs; do 92 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 93 | # Strip non-valid architectures in-place 94 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 95 | stripped="$stripped $arch" 96 | fi 97 | done 98 | if [[ "$stripped" ]]; then 99 | echo "Stripped $binary of architectures:$stripped" 100 | fi 101 | } 102 | 103 | 104 | if [[ "$CONFIGURATION" == "Debug" ]]; then 105 | install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework" 106 | fi 107 | if [[ "$CONFIGURATION" == "Release" ]]; then 108 | install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework" 109 | fi 110 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 111 | wait 112 | fi 113 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_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 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 14 | 15 | case "${TARGETED_DEVICE_FAMILY}" in 16 | 1,2) 17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 18 | ;; 19 | 1) 20 | TARGET_DEVICE_ARGS="--target-device iphone" 21 | ;; 22 | 2) 23 | TARGET_DEVICE_ARGS="--target-device ipad" 24 | ;; 25 | 3) 26 | TARGET_DEVICE_ARGS="--target-device tv" 27 | ;; 28 | 4) 29 | TARGET_DEVICE_ARGS="--target-device watch" 30 | ;; 31 | *) 32 | TARGET_DEVICE_ARGS="--target-device mac" 33 | ;; 34 | esac 35 | 36 | install_resource() 37 | { 38 | if [[ "$1" = /* ]] ; then 39 | RESOURCE_PATH="$1" 40 | else 41 | RESOURCE_PATH="${PODS_ROOT}/$1" 42 | fi 43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 44 | cat << EOM 45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 46 | EOM 47 | exit 1 48 | fi 49 | case $RESOURCE_PATH in 50 | *.storyboard) 51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 53 | ;; 54 | *.xib) 55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 57 | ;; 58 | *.framework) 59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 63 | ;; 64 | *.xcdatamodel) 65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 67 | ;; 68 | *.xcdatamodeld) 69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 71 | ;; 72 | *.xcmappingmodel) 73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 75 | ;; 76 | *.xcassets) 77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 79 | ;; 80 | *) 81 | echo "$RESOURCE_PATH" || true 82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 83 | ;; 84 | esac 85 | } 86 | 87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 92 | fi 93 | rm -f "$RESOURCES_TO_COPY" 94 | 95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 96 | then 97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 99 | while read line; do 100 | if [[ $line != "${PODS_ROOT}*" ]]; then 101 | XCASSET_FILES+=("$line") 102 | fi 103 | done <<<"$OTHER_XCASSETS" 104 | 105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 106 | fi 107 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_DDAutoTracker_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_DDAutoTracker_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "$PODS_CONFIGURATION_BUILD_DIR/FBSnapshotTestCase" "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker/DDAutoTracker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_DDAutoTracker_Tests { 2 | umbrella header "Pods-DDAutoTracker_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-DDAutoTracker_Tests/Pods-DDAutoTracker_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "$PODS_CONFIGURATION_BUILD_DIR/FBSnapshotTestCase" "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/DDAutoTracker/DDAutoTracker.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Example/Tests/Tests-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/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | @import FBSnapshotTestCase; 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DDAutoTrackerTests.m 3 | // DDAutoTrackerTests 4 | // 5 | // Created by yusipeng on 12/18/2017. 6 | // Copyright (c) 2017 yusipeng. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | 11 | @interface Tests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation Tests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 |   Copyright 2017  Luojilab 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DDAutoTracker 2 | 3 | 4 | ## 埋点的重要性 5 | 6 | 1. 提高增长率:精准营销分析,优化推广效果,改进落地页,提升转化率 7 | 2. 提升用户活跃度:更细分的自定义活跃与留存分析,全面提升用户活跃度 8 | 3. 精准运营:用户分群,用户画像,多维交叉分析,实现精准运营 9 | 4. 优化产品体验:通过行为分析,事件分析,漏斗分析,优化产品体验 10 | 11 | ## 常见的埋点方式 12 | 13 | ### 1. 代码埋点 14 | 通过手写代码的方式进行埋点。代码埋点存在高度耦合、依赖发版、无法动态更新、容易误删、重复埋点等问题。 15 | 16 | ### 2. 无埋点 17 | 无埋点方式主要是指不需要代码埋点,通过app端上报控件信息给埋点配置服务器,数据人员通过上报数据进行配置埋点信息,然后再下发给app端,实现无需添加代码、动态配置收集用户使用app的所有事件。 18 | 19 | ## 无埋点实现方式 20 | 21 | iOS 无埋点方案主要依靠AOP(Aspect Oriented Programming)面向切片编程,通过预编译方式和运行期动态代理针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。 22 | 23 | iOS实现AOP编程,主要依赖iOS的runtime机制,method swizzling方法,method swizzling本质上就是对IMP(一个函数指针,保存了方法的地址)和SEL(类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号)进行交换。 24 | 25 | method swizzling是发生在运行时的,主要用于在运行时将两个method进行交换,我们可以将method swizzling代码写到任何地方,但是只有在这段method swizzling码执行完毕之后互换才起作用。 26 | 27 | ## method swizzling原理 28 | 29 | 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。 30 | ![method_swizzling.png](http://upload-images.jianshu.io/upload_images/2137852-c26e668dd1dd2326.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | 利用method_exchangeImplementations、class_replaceMethod、method_setImplementation等方法偷换selector的IMP。 33 | 34 | ![method_swizzling_2.png](http://upload-images.jianshu.io/upload_images/2137852-96ceeb5f81db7fe7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 35 | 36 | 例: 37 | ```objective-c 38 | #import 39 | 40 | @interface UIViewController (DDTracker) 41 | 42 | @end 43 | ``` 44 | 45 | ```objective-c 46 | #import "UIViewController+DDTracker.h" 47 | #import 48 | 49 | @implementation UIViewController (DDTracker) 50 | 51 | + (void)load { 52 | Method originalSelector = class_getInstanceMethod(self, @selector(viewWillAppear:)); 53 | Method swizzledSelector = class_getInstanceMethod(self, @selector(swiz_viewWillAppear:)); 54 | method_exchangeImplementations(originalSelector, swizzledSelector); 55 | } 56 | 57 | - (void)swiz_viewWillAppear:(BOOL)animated 58 | { 59 | //在这里填写需要插入的代码 60 | [self sendTrackerData]; 61 | 62 | //执行原来的代码,不影响代码逻辑 63 | [self swiz_viewWillAppear:animated]; 64 | } 65 | 66 | - (void)sendTrackerData { 67 | 68 | } 69 | ``` 70 | 71 | ## iOS常用UI控件 72 | 73 | ### UIControl 74 | 75 | UIControl是UIView的子类,当然也是UIResponder的子类。UIControl是诸如UIButton、UISwitch、UITextField等控件的父类,通过endTrackingWithTouch:withEvent:方法可以监控UIControl的触摸事件。 76 | 77 | 78 | ### UITableView 79 | 80 | UITableView是iOS主要列表控件,通过UITableViewDelegate的tableView:didSelectRowAtIndexPath:方法可以监控到UITableView的点击事件。 81 | 82 | ### UICollectionView 83 | 84 | UICollectionView 这个类是iOS6 引进的API,它的布局更加灵活,简单来说就是多列的UITableView,UICollectionView的实现和UITableView的实现基本一样,也是存在datasource和delegate的,通过UICollectionViewDelegate的collectionView:didSelectItemAtIndexPath:方法可以监控到UICollectionView的点击事件。 85 | 86 | ### UIView 87 | 88 | iOS中UIView添加UITapGestureRecognizer手势实现点击效果,通过UITapGestureRecognizer的initWithTarget:action:方法可以监控到UIView的点击事件。 89 | 90 | 91 | ## 事件ID 92 | 93 | 事件ID的组成主要是通过Viewcontroller(或TableCell)、Class和action通过一定规则拼接组成。 94 | 95 | ## 上报控件信息 96 | 97 | 当用户点击控件时,通过runtime运行时在执行响应方法后,调用接口,将控件ID以及当前class中绑定的数据取出来传给服务器。 98 | 99 | ## 配置信息下拉 100 | 101 | ```objective-c 102 | [ 103 | { 104 | "DD_TRACKER_EVENTID_KEY":"DDButtonViewController&&trackerButtonClick:" 105 | }, 106 | { 107 | "DD_TRACKER_EVENTID_KEY":"DDTableViewController&&tableView:didSelectRowAtIndexPath:" 108 | }, 109 | { 110 | "DD_TRACKER_EVENTID_KEY":"DDCollectionViewController&&collectionView:didSelectItemAtIndexPath:" 111 | }, 112 | { 113 | "DD_TRACKER_EVENTID_KEY":"DDGestureViewController&&gestureLabelClick:" 114 | } 115 | ] 116 | ``` 117 | 118 | ## 上报埋点事件 119 | 120 | 通过校验配置信息中的“ DD_TRACKER_EVENTID_KEY”字段来判断是否发送打点信息。 121 | 122 | ## 使用方法 123 | 124 | ```objective-c 125 | #import "DDAppDelegate.h" 126 | 127 | @implementation DDAppDelegate 128 | 129 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 130 | { 131 | // Override point for customization after application launch. 132 | 133 | //开启打点 134 | [[DDAutoTrackerManager sharedInstance] startWithCompletionBlockWithSuccess:^(NSDictionary *trackerDictionary) { 135 | //成功打点回调 136 | } debug:^(NSDictionary *trackerDictionary) { 137 | //调试模式回调 138 | }]; 139 | //开启调试模式 140 | [DDAutoTrackerManager sharedInstance].isDebug = YES; 141 | //读取本地配置文件 142 | NSString * filePath = [[NSBundle mainBundle] pathForResource:@"tracker" ofType:@"json"]; 143 | NSData * jsonData = [NSData dataWithContentsOfFile:filePath]; 144 | if (jsonData) { 145 | NSError *error; 146 | NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData options: NSJSONReadingMutableContainers error: &error]; 147 | if (nil == error && 148 | jsonArray) { 149 | [DDAutoTrackerManager sharedInstance].configArray = jsonArray; 150 | } 151 | } 152 | } 153 | 154 | return YES; 155 | } 156 | 157 | @end 158 | ``` 159 | 160 | 注:建议将"DDAutoTracker.h"引用放在".pch"文件中 161 | 162 | #### 数据绑定 163 | 164 | 大多数情况下,需要绑定一些控件信息。建议使用服务端返回的数据为准。调用“configInfoData:”方法配置需要上报的数据,例: 165 | 166 | ```objective-c 167 | #import "DDViewController.h" 168 | 169 | @interface DDViewController () 170 | 171 | @end 172 | 173 | @implementation DDViewController 174 | 175 | - (void)viewDidLoad { 176 | [super viewDidLoad]; 177 | // Do any additional setup after loading the view. 178 | //配置打点info信息 179 | [self configInfoData:@{@"id":@"your id", 180 | @"type":@"your type"}]; 181 | } 182 | 183 | @end 184 | ``` 185 | 186 | ## 结语 187 | 188 | 如果您觉得本项目对您有帮助,请点"star"支持一下,谢谢! 189 | 190 | [文章地址](http://www.jianshu.com/p/9b279c2b1f7d) 191 | 192 | ## Example 193 | 194 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 195 | 196 | ## Requirements 197 | 198 | ## Installation 199 | 200 | DDAutoTracker is available through [CocoaPods](http://cocoapods.org). To install 201 | it, simply add the following line to your Podfile: 202 | 203 | ```ruby 204 | pod 'DDAutoTracker' 205 | ``` 206 | 207 | ## Author 208 | 209 | wanghailiang, wanghailiang@luojilab.com 210 | 211 | ### License 212 | 213 |   Copyright 2017  Luojilab 214 | 215 | Licensed under the Apache License, Version 2.0 (the "License"); 216 | you may not use this file except in compliance with the License. 217 | You may obtain a copy of the License at 218 | 219 | http://www.apache.org/licenses/LICENSE-2.0 220 | 221 | Unless required by applicable law or agreed to in writing, software 222 | distributed under the License is distributed on an "AS IS" BASIS, 223 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 224 | See the License for the specific language governing permissions and 225 | limitations under the License. 226 | 227 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------