├── IOKit.framework ├── Versions │ └── Current └── IOKit.tbd ├── .DS_Store ├── PTFakeTouch ├── .DS_Store ├── addition │ ├── NSBundle-KIFAdditions.h │ ├── XCTestCase-KIFAdditions.h │ ├── UIScreen+KIFAdditions.h │ ├── IOHIDEvent+KIF.h │ ├── NSString+KIFAdditions.h │ ├── UIScreen+KIFAdditions.m │ ├── NSException-KIFAdditions.h │ ├── CGGeometry-KIFAdditions.m │ ├── NSPredicate+KIFAdditions.h │ ├── UIWindow-KIFAdditions.m │ ├── NSError-KIFAdditions.h │ ├── UIWindow-KIFAdditions.h │ ├── UIScrollView-KIFAdditions.h │ ├── UITableView-KIFAdditions.h │ ├── UIEvent+KIFAdditions.h │ ├── NSFileManager-KIFAdditions.h │ ├── NSBundle-KIFAdditions.m │ ├── NSString+KIFAdditions.m │ ├── UIView-Debugging.h │ ├── CGGeometry-KIFAdditions.h │ ├── LoadableCategory.h │ ├── NSException-KIFAdditions.m │ ├── CALayer-KIFAdditions.h │ ├── NSError-KIFAdditions.m │ ├── UITouch-KIFAdditions.h │ ├── CALayer-KIFAdditions.m │ ├── UIScrollView-KIFAdditions.m │ ├── NSFileManager-KIFAdditions.m │ ├── NSPredicate+KIFAdditions.m │ ├── UIEvent+KIFAdditions.m │ ├── XCTestCase-KIFAdditions.m │ ├── UITableView-KIFAdditions.m │ ├── UIApplication-KIFAdditions.h │ ├── UIAccessibilityElement-KIFAdditions.h │ ├── UIView-KIFAdditions.h │ ├── UITouch-KIFAdditions.m │ ├── UIView-Debugging.m │ ├── IOHIDEvent+KIF.m │ ├── UIApplication-KIFAdditions.m │ ├── UIAccessibilityElement-KIFAdditions.m │ └── UIView-KIFAdditions.m ├── PTFakeTouch.h ├── PTFakeMetaTouch.h ├── Info.plist ├── FixCategoryBug.h └── PTFakeMetaTouch.m ├── PTFakeTouch.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ ├── pt.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ └── tangxuan.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ ├── tangxuan.xcuserdatad │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── PTFakeTouch.xcscheme │ └── pt.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── PTFakeTouch.xcscheme └── project.pbxproj ├── PTFakeTouchTests ├── Info.plist └── PTFakeTouchTests.m ├── README.md └── PTFakeTouch copy-Info.plist /IOKit.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /IOKit.framework/IOKit.tbd: -------------------------------------------------------------------------------- 1 | Versions/A/IOKit.tbd -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanxt/PTFakeTouch/HEAD/.DS_Store -------------------------------------------------------------------------------- /PTFakeTouch/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanxt/PTFakeTouch/HEAD/PTFakeTouch/.DS_Store -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/project.xcworkspace/xcuserdata/pt.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanxt/PTFakeTouch/HEAD/PTFakeTouch.xcodeproj/project.xcworkspace/xcuserdata/pt.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/project.xcworkspace/xcuserdata/tangxuan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuanxt/PTFakeTouch/HEAD/PTFakeTouch.xcodeproj/project.xcworkspace/xcuserdata/tangxuan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSBundle-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Brian Nickel on 7/27/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (KIFAdditions) 12 | 13 | + (NSBundle *)KIFTestBundle; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/XCTestCase-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Tony DiPasquale on 12/9/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "KIFTestActor.h" 11 | 12 | @interface XCTestCase (KIFAdditions) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIScreen+KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Steven King on 25/02/2016. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface UIScreen (KIFAdditions) 12 | 13 | @property (nonatomic, readonly) CGFloat majorSwipeDisplacement; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/IOHIDEvent+KIF.h: -------------------------------------------------------------------------------- 1 | // 2 | // IOHIDEvent+KIF.h 3 | // testAnything 4 | // 5 | // Created by PugaTang on 16/4/1. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | 10 | typedef struct __IOHIDEvent * IOHIDEventRef; 11 | IOHIDEventRef kif_IOHIDEventWithTouches(NSArray *touches) CF_RETURNS_RETAINED; -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSString+KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Alex Odawa on 1/28/16. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #pragma mark - NSString 12 | @interface NSString (KIFAdditions) 13 | 14 | - (BOOL)KIF_isEqualToStringOrAttributedString:(id)aString; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIScreen+KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Steven King on 25/02/2016. 6 | // 7 | // 8 | 9 | #import "UIScreen+KIFAdditions.h" 10 | 11 | @implementation UIScreen (KIFAdditions) 12 | 13 | - (CGFloat)majorSwipeDisplacement { 14 | return self.bounds.size.width * 0.625; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSException-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSException-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Tony DiPasquale on 12/20/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSException (KIFAdditions) 12 | 13 | + (NSException *)failureInFile:(NSString *)file atLine:(NSInteger)line withDescription:(NSString *)formatString, ...; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/CGGeometry-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // CGGeometry-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/22/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "CGGeometry-KIFAdditions.h" 11 | 12 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSPredicate+KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSPredicate+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Alex Odawa on 2/3/15. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSPredicate (KIFAdditions) 12 | 13 | @property NSString *kifPredicateDescription; 14 | 15 | - (NSArray *)flatten; 16 | - (NSCompoundPredicate *)minusSubpredicatesFrom:(NSPredicate *)otherPredicate; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIWindow-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UIWindow-KIFAdditions.h" 11 | 12 | 13 | //@implementation UIWindow (KIFAdditions) 14 | // 15 | //@end 16 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSError-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Brian Nickel on 7/27/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface NSError (KIFAdditions) 12 | 13 | + (instancetype)KIFErrorWithUnderlyingError:(NSError *)underlyingError format:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3); 14 | + (instancetype)KIFErrorWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIWindow-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | 13 | @interface UIWindow (KIFAdditions) 14 | 15 | - (UIResponder *)firstResponder; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PTFakeTouch/PTFakeTouch.h: -------------------------------------------------------------------------------- 1 | // 2 | // FakeTouch.h 3 | // FakeTouch 4 | // 5 | // Created by PugaTang on 16/4/7. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | #ifdef DEBUG 14 | #define RLog(fmt, ...) 15 | #define DLog(fmt, ...) NSLog((@"PThelper " fmt), ##__VA_ARGS__); 16 | #else 17 | #define DLog(fmt, ...) 18 | #define RLog(fmt, ...) NSLog((@"PThelper " fmt), ##__VA_ARGS__); 19 | #endif 20 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIScrollView-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/22/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | 13 | @interface UIScrollView (KIFAdditions) 14 | 15 | - (void)scrollViewToVisible:(UIView *)view animated:(BOOL)animated; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UITableView-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Hilton Campbell on 4/12/14. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | @interface UITableView (KIFAdditions) 13 | 14 | - (BOOL)dragCell:(UITableViewCell *)cell toIndexPath:(NSIndexPath *)indexPath error:(NSError **)error; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIEvent+KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIEvent+KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Thomas on 3/1/15. 6 | // 7 | // 8 | 9 | #import 10 | #import "FixCategoryBug.h" 11 | KW_FIX_CATEGORY_BUG_H(UIEvent_KIFAdditions) 12 | 13 | // Exposes methods of UITouchesEvent so that the compiler doesn't complain 14 | @interface UIEvent (KIFAdditionsPrivateHeaders) 15 | - (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)arg2; 16 | - (void)_clearTouches; 17 | @end 18 | 19 | @interface UIEvent (KIFAdditions) 20 | - (void)kif_setEventWithTouches:(NSArray *)touches; 21 | @end -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSFileManager-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileManager-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Michael Thole on 6/1/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | 13 | @interface NSFileManager (KIFAdditions) 14 | 15 | - (NSString *)createUserDirectory:(NSSearchPathDirectory)searchPath; 16 | - (BOOL)recursivelyCreateDirectory:(NSString *)path; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSBundle-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Brian Nickel on 7/27/13. 6 | // 7 | // 8 | 9 | #import "NSBundle-KIFAdditions.h" 10 | #import "KIFTestCase.h" 11 | #import "LoadableCategory.h" 12 | 13 | MAKE_CATEGORIES_LOADABLE(NSBundle_KIFAdditions) 14 | 15 | @implementation NSBundle (KIFAdditions) 16 | 17 | + (NSBundle *)KIFTestBundle 18 | { 19 | static NSBundle *bundle; 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | bundle = [self bundleForClass:[KIFTestCase class]]; 23 | }); 24 | return bundle; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSString+KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Alex Odawa on 1/28/16. 6 | // 7 | // 8 | 9 | #import "NSString+KIFAdditions.h" 10 | 11 | #pragma mark - NSString 12 | @implementation NSString (KIFAdditions) 13 | 14 | - (BOOL)KIF_isEqualToStringOrAttributedString:(id)aString; 15 | { 16 | // Somtimes Accessibility Elements will return an AXAttributedString. 17 | // This compares the raw backing string against a vanilla NSString, ignoring any attributes. 18 | if ([aString respondsToSelector:@selector(string)]) { 19 | return [self isEqualToString:[(id)aString string]]; 20 | } 21 | return [self isEqualToString:aString]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIView-Debugging.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Debugging.h 3 | // KIF 4 | // 5 | // Created by Graeme Arthur on 02/05/15. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface UIView (Debugging) 12 | /*! 13 | @abstract Prints the view hiererchy, starting from the top window(s), along with accessibility information, which is more related to KIF than the usual information given by the 'description' method. 14 | */ 15 | +(void)printViewHierarchy; 16 | 17 | /*! 18 | @abstract Prints the view hiererchy, starting from this view, along with accessibility information, which is more related to KIF than the usual information given by the 'description' method. 19 | */ 20 | -(void)printViewHierarchy; 21 | 22 | @end -------------------------------------------------------------------------------- /PTFakeTouch/addition/CGGeometry-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // CGGeometry-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/22/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | CG_INLINE CGPoint CGPointCenteredInRect(CGRect bounds) { 13 | return CGPointMake(bounds.origin.x + bounds.size.width * 0.5f, bounds.origin.y + bounds.size.height * 0.5f); 14 | } 15 | 16 | CG_INLINE CGPoint CGPointMidPoint(CGPoint point1, CGPoint point2) { 17 | return CGPointMake((point1.x + point2.x) / 2.0f, (point1.y + point2.y) / 2.0f); 18 | } 19 | -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/xcuserdata/tangxuan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PTFakeTouch.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 15BBDEF81D61AD5B0098330F 16 | 17 | primary 18 | 19 | 20 | 15BBDF021D61AD5B0098330F 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PTFakeTouch/PTFakeMetaTouch.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTFakeMetaTouch.h 3 | // PTFakeTouch 4 | // 5 | // Created by PugaTang on 16/4/20. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PTFakeMetaTouch : NSObject 13 | /** 14 | * Fake a touch event 构造一个触屏基础操作 15 | * 16 | * @param pointId 触屏操作的序列号 17 | * @param point 操作的目的位置 18 | * @param phase 操作的类别 19 | * 20 | * @return pointId 返回操作的序列号 21 | */ 22 | + (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase; 23 | /** 24 | * Get a not used pointId 获取一个没有使用过的触屏序列号 25 | * 26 | * @return pointId 返回序列号 27 | */ 28 | + (NSInteger)getAvailablePointId; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/LoadableCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // LoadableCategory.h 3 | // KIF 4 | // 5 | // Created by Karl Stenerud on 7/16/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | /** Make all categories in the current file loadable without using -load-all. 11 | * 12 | * Normally, compilers will skip linking files that contain only categories. 13 | * Adding a call to this macro adds a dummy class, which causes the linker 14 | * to add the file. 15 | * 16 | * @param UNIQUE_NAME A globally unique name. 17 | */ 18 | #define MAKE_CATEGORIES_LOADABLE(UNIQUE_NAME) @interface FORCELOAD_##UNIQUE_NAME : NSObject @end @implementation FORCELOAD_##UNIQUE_NAME @end 19 | -------------------------------------------------------------------------------- /PTFakeTouchTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PTFakeTouch 2 | Simulate touch events for iOS [User mode] 3 | 4 | Just build it and add this framework to your project. 5 | Then you can use it. 6 | 7 | Addtions are from kif. 8 | 9 | USE CASE 10 | ------------- 11 | Click a point at screen 12 | 13 | NSInteger pointId = [PTFakeTouch fakeTouchId:[PTFakeTouch getAvailablePointId] AtPoint:CGPointMake(100,100) withTouchPhase:UITouchPhaseBegan]; 14 | [PTFakeTouch fakeTouchId:pointId AtPoint:CGPointMake(100,100) withTouchPhase:UITouchPhaseEnded]; 15 | 16 | Swipe screen 17 | 18 | NSInteger pointId = [PTFakeTouch fakeTouchId:[PTFakeTouch getAvailablePointId] AtPoint:CGPointMake(100,100) withTouchPhase:UITouchPhaseBegan]; 19 | [PTFakeTouch fakeTouchId:pointId AtPoint:CGPointMake(300,300) withTouchPhase:UITouchPhaseMoved]; 20 | [PTFakeTouch fakeTouchId:pointId AtPoint:CGPointMake(300,300) withTouchPhase:UITouchPhaseEnded]; 21 | -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/xcuserdata/pt.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PTFakeTouch copy.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | PTFakeTouch.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 15BBDEF81D61AD5B0098330F 21 | 22 | primary 23 | 24 | 25 | 15BBDF021D61AD5B0098330F 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSException-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSException-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Tony DiPasquale on 12/20/13. 6 | // 7 | // 8 | 9 | #import "NSException-KIFAdditions.h" 10 | 11 | @implementation NSException (KIFAdditions) 12 | 13 | + (NSException *)failureInFile:(NSString *)file atLine:(NSInteger)line withDescription:(NSString *)formatString, ... 14 | { 15 | va_list argumentList; 16 | va_start(argumentList, formatString); 17 | 18 | NSString *reason = [[NSString alloc] initWithFormat:formatString arguments:argumentList]; 19 | 20 | va_end(argumentList); 21 | 22 | return [NSException exceptionWithName:@"KIFFailureException" 23 | reason: reason 24 | userInfo:@{@"FilenameKey": file, 25 | @"LineNumberKey": @(line)}]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /PTFakeTouch/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 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PTFakeTouch copy-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 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PTFakeTouch/FixCategoryBug.h: -------------------------------------------------------------------------------- 1 | // 2 | // FixCategoryBug.h 3 | // FakeTouch 4 | // 5 | // Created by PugaTang on 16/4/7. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | #ifndef MainLib_FixCategoryBug_h 10 | #define MainLib_FixCategoryBug_h 11 | 12 | #define __kw_to_string_1(x) #x 13 | #define __kw_to_string(x) __kw_to_string_1(x) 14 | 15 | // 需要在有category的头文件中调用,例如 KW_FIX_CATEGORY_BUG_H(NSString_Extented) 16 | #define KW_FIX_CATEGORY_BUG_H(name) \ 17 | @interface KW_FIX_CATEGORY_BUG_##name : NSObject \ 18 | +(void)print; \ 19 | @end 20 | 21 | // 需要在有category的源文件中调用,例如 KW_FIX_CATEGORY_BUG_M(NSString_Extented) 22 | #define KW_FIX_CATEGORY_BUG_M(name) \ 23 | @implementation KW_FIX_CATEGORY_BUG_##name \ 24 | + (void)print { \ 25 | NSLog(@"[Enable]"); \ 26 | } \ 27 | @end \ 28 | 29 | 30 | // 在target中启用这个宏,其实就是调用下category中定义的类的print方法。 31 | #define KW_ENABLE_CATEGORY(name) [KW_FIX_CATEGORY_BUG_##name print] 32 | 33 | #endif -------------------------------------------------------------------------------- /PTFakeTouchTests/PTFakeTouchTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTFakeTouchTests.m 3 | // PTFakeTouchTests 4 | // 5 | // Created by tangxuan on 16/8/15. 6 | // Copyright © 2016年 tangxuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PTFakeTouchTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PTFakeTouchTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/CALayer-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer-KIFAdditions.h 3 | // Pods 4 | // 5 | // Created by Radu Ciobanu on 28/01/2016. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | // 10 | 11 | #import 12 | 13 | @interface CALayer (KIFAdditions) 14 | 15 | /** 16 | * @method hasAnimations 17 | * @abstract Traverses self's hierarchy of layers and checks whether any 18 | * visible sublayers or self have ongoing anymations. 19 | * @return YES if an animated layer has been found, NO otherwise. 20 | */ 21 | - (BOOL)hasAnimations; 22 | 23 | /*! 24 | @method performBlockOnDescendentLayers: 25 | @abstract Calls a block on the layer itself and on all its descendent layers. 26 | @param block The block that will be called on the layers. Stop the traversation 27 | of the layers by assigning YES to the stop-parameter of the block. 28 | */ 29 | - (void)performBlockOnDescendentLayers:(void (^)(CALayer *layer, BOOL *stop))block; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSError-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Brian Nickel on 7/27/13. 6 | // 7 | // 8 | 9 | #import "NSError-KIFAdditions.h" 10 | #import "LoadableCategory.h" 11 | #import "KIFTestActor.h" 12 | 13 | MAKE_CATEGORIES_LOADABLE(NSError_KIFAdditions) 14 | 15 | @implementation NSError (KIFAdditions) 16 | 17 | + (instancetype)KIFErrorWithFormat:(NSString *)format, ... 18 | { 19 | va_list args; 20 | va_start(args, format); 21 | NSString *description = [[NSString alloc] initWithFormat:format arguments:args]; 22 | va_end(args); 23 | 24 | return [self errorWithDomain:@"KIFTest" code:KIFTestStepResultFailure userInfo:@{NSLocalizedDescriptionKey: description}]; 25 | } 26 | 27 | + (instancetype)KIFErrorWithUnderlyingError:(NSError *)underlyingError format:(NSString *)format, ... 28 | { 29 | va_list args; 30 | va_start(args, format); 31 | NSString *description = [[NSString alloc] initWithFormat:format arguments:args]; 32 | va_end(args); 33 | 34 | NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:description, NSLocalizedDescriptionKey, underlyingError, NSUnderlyingErrorKey, nil]; 35 | 36 | return [self errorWithDomain:@"KIFTest" code:KIFTestStepResultFailure userInfo:userInfo]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UITouch-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITouch-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | #import "IOHIDEvent+KIF.h" 12 | #import "FixCategoryBug.h" 13 | 14 | KW_FIX_CATEGORY_BUG_H(UITouch_KIFAdditions) 15 | 16 | @interface UITouch () 17 | 18 | 19 | - (void)setWindow:(UIWindow *)window; 20 | - (void)setView:(UIView *)view; 21 | - (void)setTapCount:(NSUInteger)tapCount; 22 | - (void)setIsTap:(BOOL)isTap; 23 | - (void)setTimestamp:(NSTimeInterval)timestamp; 24 | - (void)setPhase:(UITouchPhase)touchPhase; 25 | - (void)setGestureView:(UIView *)view; 26 | - (void)_setLocationInWindow:(CGPoint)location resetPrevious:(BOOL)resetPrevious; 27 | - (void)_setIsFirstTouchForView:(BOOL)firstTouchForView; 28 | - (void)_setIsTapToClick:(BOOL)isTapToClick; 29 | 30 | - (void)_setHidEvent:(IOHIDEventRef)event; 31 | 32 | @end 33 | 34 | @interface UITouch (KIFAdditions) 35 | 36 | - (id)initInView:(UIView *)view; 37 | - (id)initAtPoint:(CGPoint)point inView:(UIView *)view; 38 | - (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window; 39 | - (id)initTouch; 40 | - (void)resetTouch; 41 | 42 | - (void)setLocationInWindow:(CGPoint)location; 43 | - (void)setPhaseAndUpdateTimestamp:(UITouchPhase)phase; 44 | 45 | @end 46 | 47 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/CALayer-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer-KIFAdditions.m 3 | // Pods 4 | // 5 | // Created by Radu Ciobanu on 28/01/2016. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | // 10 | 11 | #import "CALayer-KIFAdditions.h" 12 | 13 | @implementation CALayer (KIFAdditions) 14 | 15 | - (BOOL)hasAnimations 16 | { 17 | __block BOOL result = NO; 18 | [self performBlockOnDescendentLayers:^(CALayer *layer, BOOL *stop) { 19 | // explicitly exclude _UIParallaxMotionEffect as it is used in alertviews, and we don't want every alertview to be paused) 20 | BOOL hasAnimation = layer.animationKeys.count != 0 && ![layer.animationKeys isEqualToArray:@[@"_UIParallaxMotionEffect"]]; 21 | if (hasAnimation && !layer.hidden) { 22 | result = YES; 23 | if (stop != NULL) { 24 | *stop = YES; 25 | } 26 | } 27 | }]; 28 | return result; 29 | } 30 | 31 | - (void)performBlockOnDescendentLayers:(void (^)(CALayer *layer, BOOL *stop))block 32 | { 33 | BOOL stop = NO; 34 | [self performBlockOnDescendentLayers:block stop:&stop]; 35 | } 36 | 37 | - (void)performBlockOnDescendentLayers:(void (^)(CALayer *, BOOL *))block stop:(BOOL *)stop 38 | { 39 | block(self, stop); 40 | if (*stop) { 41 | return; 42 | } 43 | 44 | for (CALayer *layer in self.sublayers) { 45 | [layer performBlockOnDescendentLayers:block stop:stop]; 46 | if (*stop) { 47 | return; 48 | } 49 | } 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIScrollView-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/22/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UIScrollView-KIFAdditions.h" 11 | #import "LoadableCategory.h" 12 | #import "UIApplication-KIFAdditions.h" 13 | #import "UIView-KIFAdditions.h" 14 | 15 | 16 | MAKE_CATEGORIES_LOADABLE(UIScrollView_KIFAdditions) 17 | 18 | 19 | @implementation UIScrollView (KIFAdditions) 20 | 21 | - (void)scrollViewToVisible:(UIView *)view animated:(BOOL)animated; 22 | { 23 | CGRect viewFrame = [self convertRect:view.bounds fromView:view]; 24 | CGPoint contentOffset = self.contentOffset; 25 | 26 | if (CGRectGetMaxX(viewFrame) > self.contentOffset.x + CGRectGetWidth(self.bounds)) { 27 | contentOffset.x = MIN(CGRectGetMaxX(viewFrame) - CGRectGetWidth(self.bounds), CGRectGetMinX(viewFrame)); 28 | } else if (CGRectGetMinX(viewFrame) < self.contentOffset.x) { 29 | contentOffset.x = MAX(CGRectGetMaxX(viewFrame) - CGRectGetWidth(self.bounds), CGRectGetMinX(viewFrame)); 30 | } 31 | 32 | if (CGRectGetMaxY(viewFrame) > self.contentOffset.y + CGRectGetHeight(self.bounds)) { 33 | contentOffset.y = MIN(CGRectGetMaxY(viewFrame) - CGRectGetHeight(self.bounds), CGRectGetMinY(viewFrame)); 34 | } else if (CGRectGetMinY(viewFrame) < self.contentOffset.y) { 35 | contentOffset.y = MAX(CGRectGetMaxY(viewFrame) - CGRectGetHeight(self.bounds), CGRectGetMinY(viewFrame)); 36 | } 37 | 38 | if (!CGPointEqualToPoint(contentOffset, self.contentOffset)) { 39 | [self setContentOffset:contentOffset animated:animated]; 40 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.2, false); 41 | } 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSFileManager-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileManager-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Michael Thole on 6/1/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "NSFileManager-KIFAdditions.h" 11 | #import "LoadableCategory.h" 12 | 13 | 14 | MAKE_CATEGORIES_LOADABLE(NSFileManager_KIFAdditions) 15 | 16 | 17 | @implementation NSFileManager (KIFAdditions) 18 | 19 | #pragma mark Public Methods 20 | 21 | - (NSString *)createUserDirectory:(NSSearchPathDirectory)searchPath; 22 | { 23 | NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES); 24 | if (!paths.count) { 25 | return nil; 26 | } 27 | 28 | NSString *rootDirectory = paths[0]; 29 | 30 | BOOL isDir; 31 | BOOL created = NO; 32 | if ([self fileExistsAtPath:rootDirectory isDirectory:&isDir] && isDir) { 33 | created = YES; 34 | } else { 35 | created = [self recursivelyCreateDirectory:rootDirectory]; 36 | } 37 | 38 | return created ? rootDirectory : nil; 39 | } 40 | 41 | - (BOOL)recursivelyCreateDirectory:(NSString *)path; 42 | { 43 | BOOL isDir = NO; 44 | BOOL isParentADir = NO; 45 | NSString *parentDir = nil; 46 | 47 | if (![self fileExistsAtPath:path isDirectory:&isDir]) { 48 | // if file doesn't exist, first create parent 49 | parentDir = [path stringByDeletingLastPathComponent]; 50 | 51 | if (!parentDir.length || [parentDir isEqualToString:@"/"]) { 52 | isParentADir = YES; 53 | } else { 54 | isParentADir = [self recursivelyCreateDirectory:parentDir]; 55 | } 56 | 57 | if (isParentADir) { 58 | isDir = [self createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil]; 59 | } else { 60 | return NO; 61 | } 62 | } 63 | 64 | return isDir; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/NSPredicate+KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSPredicate+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Alex Odawa on 2/3/15. 6 | // 7 | // 8 | 9 | #import 10 | #import "NSPredicate+KIFAdditions.h" 11 | 12 | @implementation NSPredicate (KIFAdditions) 13 | 14 | - (NSArray *)flatten 15 | { 16 | NSMutableArray *result = [[NSMutableArray alloc] init]; 17 | 18 | if ([self isKindOfClass:[NSCompoundPredicate class]]) { 19 | for (NSPredicate *predicate in ((NSCompoundPredicate *)self).subpredicates) { 20 | [result addObjectsFromArray:[predicate flatten]]; 21 | } 22 | } else { 23 | [result addObject:self]; 24 | } 25 | 26 | return result; 27 | } 28 | 29 | - (NSCompoundPredicate *)minusSubpredicatesFrom:(NSPredicate *)otherPredicate; 30 | { 31 | if (self == otherPredicate) { 32 | return nil; 33 | } 34 | NSMutableSet *subpredicates = [NSMutableSet setWithArray:[self flatten]]; 35 | NSMutableSet *otherSubpredicates = [NSMutableSet setWithArray:[otherPredicate flatten]]; 36 | [subpredicates minusSet:otherSubpredicates]; 37 | return [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType 38 | subpredicates:[subpredicates allObjects]]; 39 | } 40 | 41 | - (void)setKifPredicateDescription:(NSString *)description; 42 | { 43 | NSString *desc = description.copy; 44 | objc_setAssociatedObject(self, @selector(kifPredicateDescription), desc, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 45 | } 46 | 47 | - (NSString *)kifPredicateDescription; 48 | { 49 | id object = objc_getAssociatedObject(self, @selector(kifPredicateDescription)); 50 | if (object) { 51 | return object; 52 | } 53 | // Compound predicates containing subpredicates with the kifPredicateDescription set should still get our pretty formatting. 54 | if ([self isKindOfClass:[NSCompoundPredicate class]]) { 55 | NSArray *subpredicates = [self flatten]; 56 | NSString *description = @""; 57 | 58 | for (NSPredicate *predicate in subpredicates) { 59 | if (description.length > 0) { 60 | description = [description stringByAppendingString:@", "]; 61 | } 62 | description = [description stringByAppendingString:predicate.kifPredicateDescription]; 63 | } 64 | if (description.length > 0) { 65 | return description; 66 | } 67 | } 68 | 69 | return self.description; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIEvent+KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIEvent+KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Thomas on 3/1/15. 6 | // 7 | // 8 | 9 | #import "UIEvent+KIFAdditions.h" 10 | #import "LoadableCategory.h" 11 | #import "IOHIDEvent+KIF.h" 12 | 13 | KW_FIX_CATEGORY_BUG_M(UIEvent_KIFAdditions) 14 | 15 | MAKE_CATEGORIES_LOADABLE(UIEvent_KIFAdditions) 16 | 17 | 18 | // 19 | // GSEvent is an undeclared object. We don't need to use it ourselves but some 20 | // Apple APIs (UIScrollView in particular) require the x and y fields to be present. 21 | // 22 | @interface KIFGSEventProxy : NSObject 23 | { 24 | @public 25 | unsigned int flags; 26 | unsigned int type; 27 | unsigned int ignored1; 28 | float x1; 29 | float y1; 30 | float x2; 31 | float y2; 32 | unsigned int ignored2[10]; 33 | unsigned int ignored3[7]; 34 | float sizeX; 35 | float sizeY; 36 | float x3; 37 | float y3; 38 | unsigned int ignored4[3]; 39 | } 40 | @end 41 | 42 | @implementation KIFGSEventProxy 43 | @end 44 | 45 | typedef struct __GSEvent * GSEventRef; 46 | 47 | @interface UIEvent (KIFAdditionsMorePrivateHeaders) 48 | - (void)_setGSEvent:(GSEventRef)event; 49 | - (void)_setHIDEvent:(IOHIDEventRef)event; 50 | - (void)_setTimestamp:(NSTimeInterval)timestemp; 51 | @end 52 | 53 | @implementation UIEvent (KIFAdditions) 54 | 55 | - (void)kif_setEventWithTouches:(NSArray *)touches 56 | { 57 | NSOperatingSystemVersion iOS8 = {8, 0, 0}; 58 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] 59 | && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS8]) { 60 | [self kif_setIOHIDEventWithTouches:touches]; 61 | } else { 62 | [self kif_setGSEventWithTouches:touches]; 63 | } 64 | } 65 | 66 | - (void)kif_setGSEventWithTouches:(NSArray *)touches 67 | { 68 | UITouch *touch = touches[0]; 69 | CGPoint location = [touch locationInView:touch.window]; 70 | KIFGSEventProxy *gsEventProxy = [[KIFGSEventProxy alloc] init]; 71 | gsEventProxy->x1 = location.x; 72 | gsEventProxy->y1 = location.y; 73 | gsEventProxy->x2 = location.x; 74 | gsEventProxy->y2 = location.y; 75 | gsEventProxy->x3 = location.x; 76 | gsEventProxy->y3 = location.y; 77 | gsEventProxy->sizeX = 1.0; 78 | gsEventProxy->sizeY = 1.0; 79 | gsEventProxy->flags = ([touch phase] == UITouchPhaseEnded) ? 0x1010180 : 0x3010180; 80 | gsEventProxy->type = 3001; 81 | 82 | [self _setGSEvent:(GSEventRef)gsEventProxy]; 83 | 84 | [self _setTimestamp:(((UITouch*)touches[0]).timestamp)]; 85 | } 86 | 87 | - (void)kif_setIOHIDEventWithTouches:(NSArray *)touches 88 | { 89 | IOHIDEventRef event = kif_IOHIDEventWithTouches(touches); 90 | [self _setHIDEvent:event]; 91 | CFRelease(event); 92 | } 93 | 94 | @end -------------------------------------------------------------------------------- /PTFakeTouch/addition/XCTestCase-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Tony DiPasquale on 12/9/13. 6 | // 7 | // 8 | 9 | #import "XCTestCase-KIFAdditions.h" 10 | #import "LoadableCategory.h" 11 | #import "UIApplication-KIFAdditions.h" 12 | #import 13 | 14 | MAKE_CATEGORIES_LOADABLE(TestCase_KIFAdditions) 15 | 16 | static inline void Swizzle(Class c, SEL orig, SEL new) 17 | { 18 | Method origMethod = class_getInstanceMethod(c, orig); 19 | Method newMethod = class_getInstanceMethod(c, new); 20 | if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 21 | class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 22 | else 23 | method_exchangeImplementations(origMethod, newMethod); 24 | } 25 | 26 | @interface XCTestCase () 27 | - (void)_recordUnexpectedFailureWithDescription:(id)arg1 exception:(id)arg2; 28 | @end 29 | 30 | @implementation XCTestCase (KIFAdditions) 31 | 32 | - (void)failWithException:(NSException *)exception stopTest:(BOOL)stop 33 | { 34 | self.continueAfterFailure = YES; 35 | 36 | [self recordFailureWithDescription:exception.description inFile:exception.userInfo[@"FilenameKey"] atLine:[exception.userInfo[@"LineNumberKey"] unsignedIntegerValue] expected:NO]; 37 | 38 | if (stop) { 39 | [self writeScreenshotForException:exception]; 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | Swizzle([XCTestCase class], @selector(_recordUnexpectedFailureWithDescription:exception:), @selector(KIF_recordUnexpectedFailureWithDescription:exception:)); 43 | }); 44 | [exception raise]; 45 | } 46 | } 47 | 48 | - (void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop 49 | { 50 | NSException *lastException = exceptions.lastObject; 51 | for (NSException *exception in exceptions) { 52 | [self failWithException:exception stopTest:(exception == lastException ? stop : NO)]; 53 | } 54 | } 55 | 56 | - (void)KIF_recordUnexpectedFailureWithDescription:(id)arg1 exception:(NSException *)arg2 57 | { 58 | if (![[arg2 name] isEqualToString:@"KIFFailureException"]) { 59 | [self KIF_recordUnexpectedFailureWithDescription:arg1 exception:arg2]; 60 | } 61 | } 62 | 63 | - (void)writeScreenshotForException:(NSException *)exception; 64 | { 65 | [[UIApplication sharedApplication] writeScreenshotForLine:[exception.userInfo[@"LineNumberKey"] unsignedIntegerValue] inFile:exception.userInfo[@"FilenameKey"] description:nil error:NULL]; 66 | } 67 | 68 | @end 69 | 70 | #ifdef __IPHONE_8_0 71 | 72 | @interface XCTestSuite () 73 | - (void)_recordUnexpectedFailureForTestRun:(id)arg1 description:(id)arg2 exception:(id)arg3; 74 | @end 75 | 76 | @implementation XCTestSuite (KIFAdditions) 77 | 78 | + (void)load 79 | { 80 | Swizzle([XCTestSuite class], @selector(_recordUnexpectedFailureForTestRun:description:exception:), @selector(KIF_recordUnexpectedFailureForTestRun:description:exception:)); 81 | } 82 | 83 | - (void)KIF_recordUnexpectedFailureForTestRun:(XCTestSuiteRun *)arg1 description:(id)arg2 exception:(NSException *)arg3 84 | { 85 | if (![[arg3 name] isEqualToString:@"KIFFailureException"]) { 86 | [self KIF_recordUnexpectedFailureForTestRun:arg1 description:arg2 exception:arg3]; 87 | } else { 88 | [arg1 recordFailureWithDescription:[NSString stringWithFormat:@"Test suite stopped on fatal error: %@", arg3.description] inFile:arg3.userInfo[@"FilenameKey"] atLine:[arg3.userInfo[@"LineNumberKey"] unsignedIntegerValue] expected:NO]; 89 | } 90 | } 91 | 92 | @end 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UITableView-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Hilton Campbell on 4/12/14. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UITableView-KIFAdditions.h" 11 | #import "UIView-KIFAdditions.h" 12 | #import "UIApplication-KIFAdditions.h" 13 | #import "UITouch-KIFAdditions.h" 14 | #import "CGGeometry-KIFAdditions.h" 15 | #import "NSError-KIFAdditions.h" 16 | 17 | @implementation UITableView (KIFAdditions) 18 | 19 | #define DRAG_STEP_DISTANCE 5 20 | 21 | - (BOOL)dragCell:(UITableViewCell *)cell toIndexPath:(NSIndexPath *)indexPath error:(NSError **)error; 22 | { 23 | UIView *sourceReorderControl = [[cell subviewsWithClassNameOrSuperClassNamePrefix:@"UITableViewCellReorderControl"] lastObject]; 24 | if (!sourceReorderControl) { 25 | if (error) { 26 | *error = [NSError KIFErrorWithFormat:@"Failed to find reorder control for cell"]; 27 | } 28 | return NO; 29 | } 30 | 31 | CGPoint sourcePoint = [self convertPoint:CGPointCenteredInRect(sourceReorderControl.bounds) fromView:sourceReorderControl]; 32 | 33 | // If section < 0, search from the end of the table. 34 | if (indexPath.section < 0) { 35 | indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:self.numberOfSections + indexPath.section]; 36 | } 37 | 38 | // If row < 0, search from the end of the section. 39 | if (indexPath.row < 0) { 40 | indexPath = [NSIndexPath indexPathForRow:[self numberOfRowsInSection:indexPath.section] + indexPath.row inSection:indexPath.section]; 41 | } 42 | 43 | CGPoint destinationPoint = CGPointMake(sourcePoint.x, CGPointCenteredInRect([self rectForRowAtIndexPath:indexPath]).y); 44 | 45 | // Create the touch (there should only be one touch object for the whole drag) 46 | UITouch *touch = [[UITouch alloc] initAtPoint:sourcePoint inView:self]; 47 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 48 | 49 | UIEvent *eventDown = [self eventWithTouch:touch]; 50 | [[UIApplication sharedApplication] sendEvent:eventDown]; 51 | 52 | // Hold long enough to enter reordering mode 53 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.2, false); 54 | 55 | CGPoint currentLocation = sourcePoint; 56 | while (currentLocation.y < destinationPoint.y - DRAG_STEP_DISTANCE || currentLocation.y > destinationPoint.y + DRAG_STEP_DISTANCE) { 57 | if (currentLocation.y < destinationPoint.y) { 58 | currentLocation.y += DRAG_STEP_DISTANCE; 59 | } else { 60 | currentLocation.y -= DRAG_STEP_DISTANCE; 61 | } 62 | 63 | [touch setLocationInWindow:[self.window convertPoint:currentLocation fromView:self]]; 64 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseMoved]; 65 | 66 | UIEvent *eventDrag = [self eventWithTouch:touch]; 67 | [[UIApplication sharedApplication] sendEvent:eventDrag]; 68 | 69 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.01, false); 70 | } 71 | 72 | // Hold long enough for the animations to catch up 73 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.2, false); 74 | 75 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 76 | 77 | UIEvent *eventUp = [self eventWithTouch:touch]; 78 | [[UIApplication sharedApplication] sendEvent:eventUp]; 79 | 80 | // Dispatching the event doesn't actually update the first responder, so fake it 81 | if (touch.view == self && [self canBecomeFirstResponder]) { 82 | [self becomeFirstResponder]; 83 | } 84 | return YES; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /PTFakeTouch/PTFakeMetaTouch.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTFakeMetaTouch.m 3 | // PTFakeTouch 4 | // 5 | // Created by PugaTang on 16/4/20. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | #import "PTFakeMetaTouch.h" 10 | #import "UITouch-KIFAdditions.h" 11 | #import "UIApplication-KIFAdditions.h" 12 | #import "UIEvent+KIFAdditions.h" 13 | static NSMutableArray *touchAry; 14 | @implementation PTFakeMetaTouch 15 | 16 | + (void)load{ 17 | KW_ENABLE_CATEGORY(UITouch_KIFAdditions); 18 | KW_ENABLE_CATEGORY(UIEvent_KIFAdditions); 19 | touchAry = [[NSMutableArray alloc] init]; 20 | for (NSInteger i = 0; i<100; i++) { 21 | UITouch *touch = [[UITouch alloc] initTouch]; 22 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 23 | [touchAry addObject:touch]; 24 | } 25 | } 26 | 27 | + (NSInteger)fakeTouchId:(NSInteger)pointId AtPoint:(CGPoint)point withTouchPhase:(UITouchPhase)phase{ 28 | //DLog(@"4. fakeTouchId , phase : %ld ",(long)phase); 29 | if (pointId==0) { 30 | //随机一个没有使用的pointId 31 | pointId = [self getAvailablePointId]; 32 | if (pointId==0) { 33 | DLog(@"PTFakeTouch ERROR! pointId all used"); 34 | return 0; 35 | } 36 | } 37 | pointId = pointId - 1; 38 | UITouch *touch = [touchAry objectAtIndex:pointId]; 39 | if (phase == UITouchPhaseBegan) { 40 | touch = nil; 41 | touch = [[UITouch alloc] initAtPoint:point inWindow:[UIApplication sharedApplication].keyWindow]; 42 | 43 | #warning - Keyboard - 44 | //// Keyboard FIX: Artem Levkovich, ITRex Group: http://itrexgroup.com 45 | CGRect keyboardFrame; 46 | // AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 47 | // keyboardFrame = appDelegate.keyboardFrame; (get keyboard frame using UIKeyboardDidShowNotification) 48 | if([[[UIApplication sharedApplication].windows lastObject] isKindOfClass:NSClassFromString(@"UIRemoteKeyboardWindow")] && (CGRectContainsPoint(CGRectMake(0, [UIApplication sharedApplication].keyWindow.frame.size.height-keyboardFrame.size.height, [UIApplication sharedApplication].keyWindow.frame.size.width, keyboardFrame.size.height), point))) { 49 | touch = [[UITouch alloc] initAtPoint:point inWindow:[[UIApplication sharedApplication].windows lastObject]]; 50 | } 51 | 52 | [touchAry replaceObjectAtIndex:pointId withObject:touch]; 53 | [touch setLocationInWindow:point]; 54 | }else{ 55 | [touch setLocationInWindow:point]; 56 | [touch setPhaseAndUpdateTimestamp:phase]; 57 | } 58 | 59 | 60 | 61 | UIEvent *event = [self eventWithTouches:touchAry]; 62 | [[UIApplication sharedApplication] sendEvent:event]; 63 | if ((touch.phase==UITouchPhaseBegan)||touch.phase==UITouchPhaseMoved) { 64 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; 65 | } 66 | return (pointId+1); 67 | } 68 | 69 | 70 | + (UIEvent *)eventWithTouches:(NSArray *)touches 71 | { 72 | // _touchesEvent is a private selector, interface is exposed in UIApplication(KIFAdditionsPrivate) 73 | UIEvent *event = [[UIApplication sharedApplication] _touchesEvent]; 74 | [event _clearTouches]; 75 | [event kif_setEventWithTouches:touches]; 76 | 77 | for (UITouch *aTouch in touches) { 78 | [event _addTouch:aTouch forDelayedDelivery:NO]; 79 | } 80 | 81 | return event; 82 | } 83 | 84 | + (NSInteger)getAvailablePointId{ 85 | NSInteger availablePointId=0; 86 | NSMutableArray *availableIds = [[NSMutableArray alloc]init]; 87 | for (NSInteger i=0; i 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 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/xcuserdata/tangxuan.xcuserdatad/xcschemes/PTFakeTouch.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 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIApplication-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | #define UIApplicationCurrentRunMode ([[UIApplication sharedApplication] currentRunLoopMode]) 13 | 14 | /*! 15 | @abstract When mocking @c -openURL:, this notification is posted. 16 | */ 17 | UIKIT_EXTERN NSString *const UIApplicationDidMockOpenURLNotification; 18 | 19 | /*! 20 | @abstract When mocking @c -canOpenURL:, this notification is posted. 21 | */ 22 | UIKIT_EXTERN NSString *const UIApplicationDidMockCanOpenURLNotification; 23 | 24 | /*! 25 | @abstract The key for the opened URL in the @c UIApplicationDidMockOpenURLNotification notification. 26 | */ 27 | UIKIT_EXTERN NSString *const UIApplicationOpenedURLKey; 28 | 29 | @interface UIApplication (KIFAdditions) 30 | 31 | /*! 32 | @abstract Finds an accessibility element with a matching label, value, and traits across all windows in the application starting at the frontmost window. 33 | @param label The accessibility label of the element to search for. 34 | @param value The accessibility value of the element to search for. If @c nil, all values will be accepted. 35 | @param traits The accessibility traits of the element to search for. Elements that do not include at least these traits are ignored. 36 | @return The found accessibility element or @c nil if the element could not be found. 37 | */ 38 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value traits:(UIAccessibilityTraits)traits; 39 | 40 | /*! 41 | @abstract Finds an accessibility element where @c matchBlock returns @c YES, across all windows in the application starting at the fronmost window. 42 | @discussion This method should be used if @c accessibilityElementWithLabel:accessibilityValue:traits: does not meet your requirements. For example, if you are searching for an element that begins with a pattern or if of a certain view type. 43 | @param matchBlock A block to be performed on each element to see if it passes. 44 | */ 45 | - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessibilityElement *))matchBlock; 46 | 47 | /*! 48 | @returns The window containing the keyboard or @c nil if the keyboard is not visible. 49 | */ 50 | - (UIWindow *)keyboardWindow; 51 | 52 | /*! 53 | @returns The topmost window containing a @c UIDatePicker. 54 | */ 55 | - (UIWindow *)datePickerWindow; 56 | 57 | /*! 58 | @returns The topmost window containing a @c UIPickerView. 59 | */ 60 | - (UIWindow *)pickerViewWindow; 61 | 62 | /*! 63 | @returns The topmost window containing a @c UIDimmingView. 64 | */ 65 | - (UIWindow *)dimmingViewWindow; 66 | 67 | /*! 68 | @returns All windows in the application, including the key window even if it does not appear in @c -windows. 69 | */ 70 | - (NSArray *)windowsWithKeyWindow; 71 | 72 | /*! 73 | @abstract Writes a screenshot to disk. 74 | @discussion This method only works if the @c KIF_SCREENSHOTS environment variable is set. 75 | @param lineNumber The line number in the code at which the screenshot was taken. 76 | @param filename The name of the file in which the screenshot was taken. 77 | @param description An optional description of the scene being captured. 78 | @param error If the method returns @c YES, this optional parameter provides additional information as to why it failed. 79 | @returns @c YES if the screenshot was written to disk, otherwise @c NO. 80 | */ 81 | - (BOOL)writeScreenshotForLine:(NSUInteger)lineNumber inFile:(NSString *)filename description:(NSString *)description error:(NSError **)error; 82 | 83 | /*! 84 | @returns The current run loop mode. 85 | */ 86 | - (CFStringRef)currentRunLoopMode; 87 | 88 | /*! 89 | @abstract Swizzles the run loop modes so KIF can better switch between them. 90 | */ 91 | + (void)swizzleRunLoop; 92 | 93 | /*! 94 | @abstract Starts mocking requests to @c -openURL:, announcing all requests with a notification. 95 | @discussion After calling this method, whenever @c -openURL: is called a notification named @c UIApplicationDidMockOpenURLNotification with the URL in the @c UIApplicationOpenedURL will be raised and the normal behavior will be cancelled. 96 | @param returnValue The value to return when @c -openURL: is called. 97 | */ 98 | + (void)startMockingOpenURLWithReturnValue:(BOOL)returnValue; 99 | 100 | /*! 101 | @abstract Stops the application from mocking requests to @c -openURL:. 102 | */ 103 | + (void)stopMockingOpenURL; 104 | 105 | @end 106 | 107 | @interface UIApplication (Private) 108 | - (UIWindow *)statusBarWindow; 109 | @property(getter=isStatusBarHidden) BOOL statusBarHidden; 110 | @end 111 | 112 | @interface UIApplication (KIFAdditionsPrivate) 113 | - (UIEvent *)_touchesEvent; 114 | @end 115 | 116 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIAccessibilityElement-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccessibilityElement-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/23/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | 13 | @interface UIAccessibilityElement (KIFAdditions) 14 | 15 | /*! 16 | @abstract Finds the first view that the accessibility element is part of. 17 | @discussion There is not always a one-to-one mapping between views and accessibility elements. Accessibility elements may not even map to the view you will expect. For instance, table view cell accessibility elements return the @c UITableView and keyboard keys map to the keyboard as a whole. 18 | 19 | @param element The accessibility element. 20 | @return The first matching @c UIView as determined by the accessibility API. 21 | */ 22 | + (UIView *)viewContainingAccessibilityElement:(UIAccessibilityElement *)element; 23 | 24 | /*! 25 | @abstract Finds an accessibility element and view with a matching label, value, and traits, optionally passing a tappability test. 26 | @discussion This method combines @c +accessibilityElementWithLabel:value:traits:error: and @c +viewContainingAccessibilityElement:tappable:error: for convenience. 27 | @param foundElement The found accessibility element or @c nil if the method returns @c NO. Can be @c NULL. 28 | @param foundView The first matching view for @c foundElement as determined by the accessibility API or @c nil if the view is hidden or fails the tappability test. Can be @c NULL. 29 | @param label The accessibility label of the element to wait for. 30 | @param value The accessibility value of the element to tap. 31 | @param traits The accessibility traits of the element to wait for. Elements that do not include at least these traits are ignored. 32 | @param error A reference to an error object to be populated when no matching element or view is found. Can be @c NULL. 33 | @result @c YES if the element and view were found. Otherwise @c NO. 34 | */ 35 | + (BOOL)accessibilityElement:(out UIAccessibilityElement **)foundElement view:(out UIView **)foundView withLabel:(NSString *)label value:(NSString *)value traits:(UIAccessibilityTraits)traits tappable:(BOOL)mustBeTappable error:(out NSError **)error; 36 | 37 | /*! 38 | @abstract Finds an accessibility element with a matching label, value, and traits. 39 | @discussion This functionality is identical to -[UIApplication accessibilityElementWithLabel:accessibilityValue:traits:] except that it detailed error messaging in the case where the element cannot be found. 40 | @param label The accessibility label of the element to wait for. 41 | @param value The accessibility value of the element to tap. 42 | @param traits The accessibility traits of the element to wait for. Elements that do not include at least these traits are ignored. 43 | @param error A reference to an error object to be populated when no element is found. Can be @c NULL. 44 | @return The found accessibility element. If @c nil see the @c error for a detailed reason. 45 | */ 46 | + (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label value:(NSString *)value traits:(UIAccessibilityTraits)traits error:(out NSError **)error; 47 | 48 | /*! 49 | @abstract Finds an accessibility element and view where the element passes the predicate, optionally passing a tappability test. 50 | @param foundElement The found accessibility element or @c nil if the method returns @c NO. Can be @c NULL. 51 | @param foundView The first matching view for @c foundElement as determined by the accessibility API or @c nil if the view is hidden or fails the tappability test. Can be @c NULL. 52 | @param predicate The predicate to test the accessibility element on. 53 | @param error A reference to an error object to be populated when no matching element or view is found. Can be @c NULL. 54 | @result @c YES if the element and view were found. Otherwise @c NO. 55 | */ 56 | + (BOOL)accessibilityElement:(out UIAccessibilityElement **)foundElement view:(out UIView **)foundView withElementMatchingPredicate:(NSPredicate *)predicate tappable:(BOOL)mustBeTappable error:(out NSError **)error; 57 | 58 | /*! 59 | @abstract Finds and attempts to make visible a view for a given accessibility element. 60 | @discussion If the element is found, off screen, and is inside a scroll view, this method will attempt to programmatically scroll the view onto the screen before performing any logic as to if the view is tappable. 61 | 62 | @param element The accessibility element. 63 | @param mustBeTappable If @c YES, a tappability test will be performed. 64 | @param error A reference to an error object to be populated when no element is found. Can be @c NULL. 65 | @return The first matching view as determined by the accessibility API or nil if the view is hidden or fails the tappability test. 66 | */ 67 | + (UIView *)viewContainingAccessibilityElement:(UIAccessibilityElement *)element tappable:(BOOL)mustBeTappable error:(NSError **)error; 68 | 69 | /*! 70 | @abstract Returns a human readable string of UIAccessiblityTrait names, derived from UIAccessibilityConstants.h. 71 | @param traits The accessibility traits to list. 72 | */ 73 | + (NSString *)stringFromAccessibilityTraits:(UIAccessibilityTraits)traits; 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIView-KIFAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView-KIFAdditions.h 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import 11 | 12 | extern double KIFDegreesToRadians(double deg); 13 | extern double KIFRadiansToDegrees(double rad); 14 | 15 | typedef CGPoint KIFDisplacement; 16 | 17 | @interface UIView (KIFAdditions) 18 | 19 | @property (nonatomic, readonly, getter=isProbablyTappable) BOOL probablyTappable; 20 | 21 | - (BOOL)isDescendantOfFirstResponder; 22 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label; 23 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label traits:(UIAccessibilityTraits)traits; 24 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value traits:(UIAccessibilityTraits)traits; 25 | 26 | /*! 27 | @method accessibilityElementMatchingBlock: 28 | @abstract Finds the descendent accessibility element that matches the conditions defined by the match block. 29 | @param matchBlock A block which returns YES for matching elements. 30 | @result The matching accessibility element. 31 | */ 32 | - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessibilityElement *))matchBlock; 33 | 34 | - (UIView *)subviewWithClassNamePrefix:(NSString *)prefix __deprecated; 35 | - (NSArray *)subviewsWithClassNamePrefix:(NSString *)prefix; 36 | - (UIView *)subviewWithClassNameOrSuperClassNamePrefix:(NSString *)prefix __deprecated; 37 | - (NSArray *)subviewsWithClassNameOrSuperClassNamePrefix:(NSString *)prefix; 38 | 39 | - (void)flash; 40 | - (void)tap; 41 | - (void)tapAtPoint:(CGPoint)point; 42 | - (void)twoFingerTapAtPoint:(CGPoint)point; 43 | - (void)longPressAtPoint:(CGPoint)point duration:(NSTimeInterval)duration; 44 | 45 | /*! 46 | @method dragFromPoint:toPoint: 47 | @abstract Simulates dragging a finger on the screen between the given points. 48 | @discussion Causes the application to dispatch a sequence of touch events which simulate dragging a finger from startPoint to endPoint. 49 | @param startPoint The point at which to start the drag, in the coordinate system of the receiver. 50 | @param endPoint The point at which to end the drag, in the coordinate system of the receiver. 51 | */ 52 | - (void)dragFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint; 53 | - (void)dragFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint steps:(NSUInteger)stepCount; 54 | - (void)dragFromPoint:(CGPoint)startPoint displacement:(KIFDisplacement)displacement steps:(NSUInteger)stepCount; 55 | - (void)dragAlongPathWithPoints:(CGPoint *)points count:(NSInteger)count; 56 | - (void)twoFingerPanFromPoint:(CGPoint)startPoint toPoint:(CGPoint)toPoint steps:(NSUInteger)stepCount; 57 | - (void)pinchAtPoint:(CGPoint)centerPoint distance:(CGFloat)distance steps:(NSUInteger)stepCount; 58 | - (void)zoomAtPoint:(CGPoint)centerPoint distance:(CGFloat)distance steps:(NSUInteger)stepCount; 59 | - (void)twoFingerRotateAtPoint:(CGPoint)centerPoint angle:(CGFloat)angleInDegrees; 60 | /*! 61 | @method isTappableWithHitTestResultView: 62 | @abstract Easy hook to override whether a hit test result makes a view tappable. 63 | @discussion Some times, your view hierarchies involve putting overlays over views that would otherwise be tappable. Since KIF doesn't know about these exceptions, you can override this method as a convenient way of hooking in to the check for something being tappable. Your implementation will probably want to call up to super. 64 | @param hitView The view -hitTest: returned when trying to tap on a point inside your view's bounds 65 | @result Whether or not the view is tappable. 66 | */ 67 | - (BOOL)isTappableWithHitTestResultView:(UIView *)hitView; 68 | 69 | /*! 70 | @method isTappableInRect: 71 | @abstract Whether or not the receiver can be tapped inside the given rectangular area. 72 | @discussion Determines whether or not tapping within the given rectangle would actually hit the receiver or one of its children. This is useful for determining if the view is actually on screen and enabled. 73 | @param rect A rectangle specifying an area in the receiver in the receiver's frame coordinates. 74 | @result Whether or not the view is tappable. 75 | */ 76 | - (BOOL)isTappableInRect:(CGRect)rect; 77 | 78 | /*! 79 | @method tappablePointInRect:(CGRect)rect; 80 | @abstract Finds a point in the receiver that is tappable. 81 | @discussion Finds a tappable point in the receiver, where tappable is defined as a point that, when tapped, will hit the receiver. 82 | @param rect A rectangle specifying an area in the receiver in the receiver's frame coordinates. 83 | @result A tappable point in the receivers frame coordinates. 84 | */ 85 | - (CGPoint)tappablePointInRect:(CGRect)rect; 86 | 87 | - (UIEvent *)eventWithTouch:(UITouch *)touch; 88 | 89 | /*! 90 | @abstract Evaluates if user interaction is enabled including edge cases. 91 | */ 92 | - (BOOL)isUserInteractionActuallyEnabled; 93 | 94 | /*! 95 | @abstract Evaluates if the view and all its superviews are visible. 96 | */ 97 | - (BOOL)isVisibleInViewHierarchy; 98 | 99 | /*! 100 | @method performBlockOnDescendentViews: 101 | @abstract Calls a block on the view itself and on all its descendent views. 102 | @param block The block that will be called on the views. Stop the traversation of the views by assigning YES to the stop-parameter of the block. 103 | */ 104 | - (void)performBlockOnDescendentViews:(void (^)(UIView *view, BOOL *stop))block; 105 | 106 | /*! 107 | @method performBlockOnAscendentViews: 108 | @abstract Calls a block on the view itself and on all its superviews. 109 | @param block The block that will be called on the views. Stop the traversation of the views by assigning YES to the stop-parameter of the block. 110 | */ 111 | - (void)performBlockOnAscendentViews:(void (^)(UIView *view, BOOL *stop))block; 112 | 113 | /*! 114 | @abstract Returns either the current window or another window if a transform is applied. Returns `nil` if all windows in the application have transforms. 115 | */ 116 | @property (nonatomic, readonly) UIWindow *windowOrIdentityWindow; 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UITouch-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITouch-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UITouch-KIFAdditions.h" 11 | #import "LoadableCategory.h" 12 | #import 13 | 14 | KW_FIX_CATEGORY_BUG_M(UITouch_KIFAdditions) 15 | 16 | MAKE_CATEGORIES_LOADABLE(UITouch_KIFAdditions) 17 | 18 | typedef struct { 19 | unsigned int _firstTouchForView:1; 20 | unsigned int _isTap:1; 21 | unsigned int _isDelayed:1; 22 | unsigned int _sentTouchesEnded:1; 23 | unsigned int _abandonForwardingRecord:1; 24 | } UITouchFlags; 25 | 26 | 27 | 28 | @implementation UITouch (KIFAdditions) 29 | 30 | - (id)initInView:(UIView *)view; 31 | { 32 | CGRect frame = view.frame; 33 | CGPoint centerPoint = CGPointMake(frame.size.width * 0.5f, frame.size.height * 0.5f); 34 | return [self initAtPoint:centerPoint inView:view]; 35 | } 36 | 37 | - (id)initAtPoint:(CGPoint)point inWindow:(UIWindow *)window; 38 | { 39 | self = [super init]; 40 | if (self == nil) { 41 | return nil; 42 | } 43 | 44 | // Create a fake tap touch 45 | [self setWindow:window]; // Wipes out some values. Needs to be first. 46 | 47 | //[self setTapCount:1]; 48 | [self _setLocationInWindow:point resetPrevious:YES]; 49 | 50 | UIView *hitTestView = [window hitTest:point withEvent:nil]; 51 | 52 | [self setView:hitTestView]; 53 | [self setPhase:UITouchPhaseBegan]; 54 | NSOperatingSystemVersion iOS14 = {14, 0, 0}; 55 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS14]) { 56 | [self _setIsTapToClick:NO]; 57 | } else { 58 | [self _setIsFirstTouchForView:YES]; 59 | [self setIsTap:NO]; 60 | } 61 | [self setTimestamp:[[NSProcessInfo processInfo] systemUptime]]; 62 | if ([self respondsToSelector:@selector(setGestureView:)]) { 63 | [self setGestureView:hitTestView]; 64 | } 65 | 66 | // Starting with iOS 9, internal IOHIDEvent must be set for UITouch object 67 | NSOperatingSystemVersion iOS9 = {9, 0, 0}; 68 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS9]) { 69 | [self kif_setHidEvent]; 70 | } 71 | 72 | return self; 73 | } 74 | 75 | - (void)resetTouch{ 76 | // Create a fake tap touch 77 | UIWindow *window = [[UIApplication sharedApplication] keyWindow]; 78 | CGPoint point = CGPointMake(0, 0); 79 | [self setWindow:window]; // Wipes out some values. Needs to be first. 80 | 81 | //[self setTapCount:1]; 82 | [self _setLocationInWindow:CGPointMake(0, 0) resetPrevious:YES]; 83 | 84 | UIView *hitTestView = [window hitTest:point withEvent:nil]; 85 | 86 | NSOperatingSystemVersion iOS14 = {14, 0, 0}; 87 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS14]) { 88 | [self _setIsTapToClick:NO]; 89 | } else { 90 | [self _setIsFirstTouchForView:YES]; 91 | [self setIsTap:NO]; 92 | } 93 | 94 | [self setView:hitTestView]; 95 | [self setPhase:UITouchPhaseBegan]; 96 | //DLog(@"resetTouch setPhase 0"); 97 | [self setTimestamp:[[NSProcessInfo processInfo] systemUptime]]; 98 | if ([self respondsToSelector:@selector(setGestureView:)]) { 99 | [self setGestureView:hitTestView]; 100 | } 101 | 102 | // Starting with iOS 9, internal IOHIDEvent must be set for UITouch object 103 | NSOperatingSystemVersion iOS9 = {9, 0, 0}; 104 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS9]) { 105 | [self kif_setHidEvent]; 106 | } 107 | } 108 | 109 | - (id)initTouch; 110 | { 111 | //DLog(@"init...touch..."); 112 | self = [super init]; 113 | if (self == nil) { 114 | return nil; 115 | } 116 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 117 | CGPoint point = CGPointMake(0, 0); 118 | [self setWindow:window]; // Wipes out some values. Needs to be first. 119 | 120 | [self _setLocationInWindow:point resetPrevious:YES]; 121 | 122 | UIView *hitTestView = [window hitTest:point withEvent:nil]; 123 | 124 | [self setView:hitTestView]; 125 | [self setPhase:UITouchPhaseEnded]; 126 | //DLog(@"init...touch...setPhase 3"); 127 | NSOperatingSystemVersion iOS14 = {14, 0, 0}; 128 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS14]) { 129 | [self _setIsTapToClick:NO]; 130 | } else { 131 | [self _setIsFirstTouchForView:YES]; 132 | [self setIsTap:NO]; 133 | } 134 | [self setTimestamp:[[NSProcessInfo processInfo] systemUptime]]; 135 | if ([self respondsToSelector:@selector(setGestureView:)]) { 136 | [self setGestureView:hitTestView]; 137 | } 138 | 139 | // Starting with iOS 9, internal IOHIDEvent must be set for UITouch object 140 | NSOperatingSystemVersion iOS9 = {9, 0, 0}; 141 | if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] && [[NSProcessInfo new] isOperatingSystemAtLeastVersion:iOS9]) { 142 | [self kif_setHidEvent]; 143 | } 144 | return self; 145 | } 146 | 147 | - (id)initAtPoint:(CGPoint)point inView:(UIView *)view; 148 | { 149 | return [self initAtPoint:[view.window convertPoint:point fromView:view] inWindow:view.window]; 150 | } 151 | 152 | // 153 | // setLocationInWindow: 154 | // 155 | // Setter to allow access to the _locationInWindow member. 156 | // 157 | - (void)setLocationInWindow:(CGPoint)location 158 | { 159 | [self setTimestamp:[[NSProcessInfo processInfo] systemUptime]]; 160 | [self _setLocationInWindow:location resetPrevious:NO]; 161 | } 162 | 163 | - (void)setPhaseAndUpdateTimestamp:(UITouchPhase)phase 164 | { 165 | //DLog(@"setPhaseAndUpdateTimestamp : %ld",(long)phase); 166 | [self setTimestamp:[[NSProcessInfo processInfo] systemUptime]]; 167 | [self setPhase:phase]; 168 | } 169 | 170 | - (void)kif_setHidEvent { 171 | IOHIDEventRef event = kif_IOHIDEventWithTouches(@[self]); 172 | [self _setHidEvent:event]; 173 | CFRelease(event); 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIView-Debugging.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Debugging.m 3 | // KIF 4 | // 5 | // Created by Graeme Arthur on 02/05/15. 6 | // 7 | 8 | #import "UIView-Debugging.h" 9 | 10 | @implementation UIView (Debugging) 11 | 12 | +(void)printViewHierarchy { 13 | NSArray* windows = [UIApplication sharedApplication].windows; 14 | if(windows.count == 1) { 15 | [windows[0] printViewHierarchy]; 16 | } else { 17 | //more than one window, also print some information about each window 18 | for (UIWindow* window in windows) { 19 | printf("Window level %f", window.windowLevel); 20 | if(window.isKeyWindow) printf(" (key window)"); 21 | printf("\n"); 22 | [window printViewHierarchy]; 23 | printf("\n"); 24 | } 25 | } 26 | } 27 | 28 | - (void)printViewHierarchy { 29 | [self printViewHierarchyWithIndentation:0]; 30 | } 31 | 32 | - (void)printViewHierarchyWithIndentation:(int)indent { 33 | [self printIndentation:indent]; 34 | [self printClassName]; 35 | 36 | [self printAccessibilityInfo]; 37 | 38 | if(self.hidden) { 39 | printf(" (invisible)"); 40 | } 41 | 42 | if([self isKindOfClass:[UIImageView class]]) { 43 | [self printImageHighlightedState]; 44 | } 45 | 46 | if([self isKindOfClass:[UIControl class]]) { 47 | [self printControlState]; 48 | } 49 | printf("\n"); 50 | 51 | [self printAccessibilityElementsWithIndentation:indent]; 52 | 53 | for (UIView *subview in self.subviews) { 54 | [subview printViewHierarchyWithIndentation:indent+1]; 55 | } 56 | } 57 | 58 | - (void)printIndentation:(int)indent { 59 | for(int i = 0; i < indent; ++i) { 60 | printf("|\t"); 61 | } 62 | } 63 | 64 | - (void)printClassName { 65 | NSString* name = NSStringFromClass([self class]); 66 | printf("%s", name.UTF8String); 67 | } 68 | 69 | - (void)printAccessibilityInfo { 70 | NSString* label = self.accessibilityLabel; 71 | NSString* identifier = self.accessibilityIdentifier; 72 | if(label != nil) { 73 | printf(", label: %s", label.UTF8String); 74 | } else if(identifier != nil) { 75 | printf(", identifier: %s", identifier.UTF8String); 76 | } 77 | } 78 | 79 | - (void)printImageHighlightedState { 80 | if(((UIImageView*)self).highlighted) { 81 | printf(" (highlighted)"); 82 | } else { 83 | printf(" (not highlighted)"); 84 | } 85 | } 86 | 87 | - (void)printControlState { 88 | UIControl* ctrl = (UIControl*)self; 89 | ctrl.enabled ? printf(" (enabled)") : printf(" (not enabled)"); 90 | ctrl.selected ? printf(" (selected)") : printf(" (not selected)"); 91 | ctrl.highlighted ? printf(" (highlighted)") : printf(" (not highlighted)"); 92 | } 93 | 94 | - (void)printAccessibilityElementsWithIndentation:(int)indent { 95 | NSInteger numOfAccElements = self.accessibilityElementCount; 96 | if(numOfAccElements != NSNotFound) { 97 | for (NSInteger i = 0; i < numOfAccElements; ++i) { 98 | [self printIndentation:indent]; 99 | UIAccessibilityElement *e = [(UIAccessibilityElement*)self accessibilityElementAtIndex:i]; 100 | printf("%s, label: %s", NSStringFromClass([e class]).UTF8String, e.accessibilityLabel.UTF8String); 101 | if(e.accessibilityValue && e.accessibilityValue.length > 0) { 102 | printf(", value: %s", e.accessibilityValue.UTF8String); 103 | } 104 | if(e.accessibilityHint && e.accessibilityHint.length > 0) { 105 | printf(", hint: %s", e.accessibilityHint.UTF8String); 106 | } 107 | printf(", "); 108 | [self printAccessibilityTraits:e.accessibilityTraits]; 109 | printf("\n"); 110 | } 111 | } 112 | } 113 | 114 | - (void)printAccessibilityTraits:(UIAccessibilityTraits)traits { 115 | 116 | printf("traits: "); 117 | bool didPrintOne = false; 118 | if(traits == UIAccessibilityTraitNone) { 119 | printf("none"); 120 | didPrintOne = true; 121 | } 122 | if(traits & UIAccessibilityTraitButton) { 123 | if(didPrintOne) printf(", "); 124 | printf("button"); 125 | didPrintOne = true; 126 | } 127 | if(traits & UIAccessibilityTraitLink) { 128 | if(didPrintOne) printf(", "); 129 | printf("link"); 130 | didPrintOne = true; 131 | } 132 | if(traits & UIAccessibilityTraitHeader) { 133 | if(didPrintOne) printf(", "); 134 | printf("header"); 135 | didPrintOne = true; 136 | } 137 | if(traits & UIAccessibilityTraitSearchField) { 138 | if(didPrintOne) printf(", "); 139 | printf("search field"); 140 | didPrintOne = true; 141 | } 142 | if(traits & UIAccessibilityTraitImage) { 143 | if(didPrintOne) printf(", "); 144 | printf("image"); 145 | didPrintOne = true; 146 | } 147 | if(traits & UIAccessibilityTraitSelected) { 148 | if(didPrintOne) printf(", "); 149 | printf("selected"); 150 | didPrintOne = true; 151 | } 152 | if(traits & UIAccessibilityTraitPlaysSound) { 153 | if(didPrintOne) printf(", "); 154 | printf("plays sound"); 155 | didPrintOne = true; 156 | } 157 | if(traits & UIAccessibilityTraitKeyboardKey) { 158 | if(didPrintOne) printf(", "); 159 | printf("keyboard key"); 160 | didPrintOne = true; 161 | } 162 | if(traits & UIAccessibilityTraitStaticText) { 163 | if(didPrintOne) printf(", "); 164 | printf("static text"); 165 | didPrintOne = true; 166 | } 167 | if(traits & UIAccessibilityTraitSummaryElement) { 168 | if(didPrintOne) printf(", "); 169 | printf("summary element"); 170 | didPrintOne = true; 171 | } 172 | if(traits & UIAccessibilityTraitNotEnabled) { 173 | if(didPrintOne) printf(", "); 174 | printf("not enabled"); 175 | didPrintOne = true; 176 | } 177 | if(traits & UIAccessibilityTraitUpdatesFrequently) { 178 | if(didPrintOne) printf(", "); 179 | printf("updates frequently"); 180 | didPrintOne = true; 181 | } 182 | if(traits & UIAccessibilityTraitStartsMediaSession) { 183 | if(didPrintOne) printf(", "); 184 | printf("starts media session"); 185 | didPrintOne = true; 186 | } 187 | if(traits & UIAccessibilityTraitAdjustable) { 188 | if(didPrintOne) printf(", "); 189 | printf("adjustable"); 190 | didPrintOne = true; 191 | } 192 | if(traits & UIAccessibilityTraitAllowsDirectInteraction) { 193 | if(didPrintOne) printf(", "); 194 | printf("allows direct interaction"); 195 | didPrintOne = true; 196 | } 197 | if(traits & UIAccessibilityTraitCausesPageTurn) { 198 | if(didPrintOne) printf(", "); 199 | printf("causes page turn"); 200 | didPrintOne = true; 201 | } 202 | if(!didPrintOne) { 203 | printf("unknown flags (0x%llx)", traits); 204 | } 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/IOHIDEvent+KIF.m: -------------------------------------------------------------------------------- 1 | // 2 | // IOHIDEvent+KIF.m 3 | // testAnything 4 | // 5 | // Created by PugaTang on 16/4/1. 6 | // Copyright © 2016年 PugaTang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IOHIDEvent+KIF.h" 11 | #import 12 | #define IOHIDEventFieldBase(type) (type << 16) 13 | #ifdef __LP64__ 14 | typedef double IOHIDFloat; 15 | #else 16 | typedef float IOHIDFloat; 17 | #endif 18 | typedef UInt32 IOOptionBits; 19 | typedef uint32_t IOHIDDigitizerTransducerType; 20 | typedef uint32_t IOHIDEventField; 21 | typedef uint32_t IOHIDEventType; 22 | 23 | void IOHIDEventAppendEvent(IOHIDEventRef event, IOHIDEventRef childEvent); 24 | void IOHIDEventSetIntegerValue(IOHIDEventRef event, IOHIDEventField field, int value); 25 | void IOHIDEventSetSenderID(IOHIDEventRef event, uint64_t sender); 26 | 27 | enum { 28 | kIOHIDDigitizerTransducerTypeStylus = 0, 29 | kIOHIDDigitizerTransducerTypePuck, 30 | kIOHIDDigitizerTransducerTypeFinger, 31 | kIOHIDDigitizerTransducerTypeHand 32 | }; 33 | enum { 34 | kIOHIDEventTypeNULL, // 0 35 | kIOHIDEventTypeVendorDefined, 36 | kIOHIDEventTypeButton, 37 | kIOHIDEventTypeKeyboard, 38 | kIOHIDEventTypeTranslation, 39 | kIOHIDEventTypeRotation, // 5 40 | kIOHIDEventTypeScroll, 41 | kIOHIDEventTypeScale, 42 | kIOHIDEventTypeZoom, 43 | kIOHIDEventTypeVelocity, 44 | kIOHIDEventTypeOrientation, // 10 45 | kIOHIDEventTypeDigitizer, 46 | kIOHIDEventTypeAmbientLightSensor, 47 | kIOHIDEventTypeAccelerometer, 48 | kIOHIDEventTypeProximity, 49 | kIOHIDEventTypeTemperature, // 15 50 | kIOHIDEventTypeNavigationSwipe, 51 | kIOHIDEventTypePointer, 52 | kIOHIDEventTypeProgress, 53 | kIOHIDEventTypeMultiAxisPointer, 54 | kIOHIDEventTypeGyro, // 20 55 | kIOHIDEventTypeCompass, 56 | kIOHIDEventTypeZoomToggle, 57 | kIOHIDEventTypeDockSwipe, // just like kIOHIDEventTypeNavigationSwipe, but intended for consumption by Dock 58 | kIOHIDEventTypeSymbolicHotKey, 59 | kIOHIDEventTypePower, // 25 60 | kIOHIDEventTypeLED, 61 | kIOHIDEventTypeFluidTouchGesture, // This will eventually superseed Navagation and Dock swipes 62 | kIOHIDEventTypeBoundaryScroll, 63 | kIOHIDEventTypeBiometric, 64 | kIOHIDEventTypeUnicode, // 30 65 | kIOHIDEventTypeAtmosphericPressure, 66 | kIOHIDEventTypeUndefined, 67 | kIOHIDEventTypeCount, // This should always be last 68 | // DEPRECATED: 69 | kIOHIDEventTypeSwipe = kIOHIDEventTypeNavigationSwipe, 70 | kIOHIDEventTypeMouse = kIOHIDEventTypePointer 71 | }; 72 | enum { 73 | kIOHIDDigitizerEventRange = 0x00000001, 74 | kIOHIDDigitizerEventTouch = 0x00000002, 75 | kIOHIDDigitizerEventPosition = 0x00000004, 76 | kIOHIDDigitizerEventStop = 0x00000008, 77 | kIOHIDDigitizerEventPeak = 0x00000010, 78 | kIOHIDDigitizerEventIdentity = 0x00000020, 79 | kIOHIDDigitizerEventAttribute = 0x00000040, 80 | kIOHIDDigitizerEventCancel = 0x00000080, 81 | kIOHIDDigitizerEventStart = 0x00000100, 82 | kIOHIDDigitizerEventResting = 0x00000200, 83 | kIOHIDDigitizerEventSwipeUp = 0x01000000, 84 | kIOHIDDigitizerEventSwipeDown = 0x02000000, 85 | kIOHIDDigitizerEventSwipeLeft = 0x04000000, 86 | kIOHIDDigitizerEventSwipeRight = 0x08000000, 87 | kIOHIDDigitizerEventSwipeMask = 0xFF000000, 88 | }; 89 | enum { 90 | kIOHIDEventFieldDigitizerX = IOHIDEventFieldBase(kIOHIDEventTypeDigitizer), 91 | kIOHIDEventFieldDigitizerY, 92 | kIOHIDEventFieldDigitizerZ, 93 | kIOHIDEventFieldDigitizerButtonMask, 94 | kIOHIDEventFieldDigitizerType, 95 | kIOHIDEventFieldDigitizerIndex, 96 | kIOHIDEventFieldDigitizerIdentity, 97 | kIOHIDEventFieldDigitizerEventMask, 98 | kIOHIDEventFieldDigitizerRange, 99 | kIOHIDEventFieldDigitizerTouch, 100 | kIOHIDEventFieldDigitizerPressure, 101 | kIOHIDEventFieldDigitizerAuxiliaryPressure, //BarrelPressure 102 | kIOHIDEventFieldDigitizerTwist, 103 | kIOHIDEventFieldDigitizerTiltX, 104 | kIOHIDEventFieldDigitizerTiltY, 105 | kIOHIDEventFieldDigitizerAltitude, 106 | kIOHIDEventFieldDigitizerAzimuth, 107 | kIOHIDEventFieldDigitizerQuality, 108 | kIOHIDEventFieldDigitizerDensity, 109 | kIOHIDEventFieldDigitizerIrregularity, 110 | kIOHIDEventFieldDigitizerMajorRadius, 111 | kIOHIDEventFieldDigitizerMinorRadius, 112 | kIOHIDEventFieldDigitizerCollection, 113 | kIOHIDEventFieldDigitizerCollectionChord, 114 | kIOHIDEventFieldDigitizerChildEventMask, 115 | kIOHIDEventFieldDigitizerIsDisplayIntegrated, 116 | kIOHIDEventFieldDigitizerQualityRadiiAccuracy, 117 | }; 118 | IOHIDEventRef IOHIDEventCreateDigitizerEvent(CFAllocatorRef allocator, AbsoluteTime timeStamp, IOHIDDigitizerTransducerType type, 119 | uint32_t index, uint32_t identity, uint32_t eventMask, uint32_t buttonMask, 120 | IOHIDFloat x, IOHIDFloat y, IOHIDFloat z, IOHIDFloat tipPressure, IOHIDFloat barrelPressure, 121 | Boolean range, Boolean touch, IOOptionBits options); 122 | IOHIDEventRef IOHIDEventCreateDigitizerFingerEventWithQuality(CFAllocatorRef allocator, AbsoluteTime timeStamp, 123 | uint32_t index, uint32_t identity, uint32_t eventMask, 124 | IOHIDFloat x, IOHIDFloat y, IOHIDFloat z, IOHIDFloat tipPressure, IOHIDFloat twist, 125 | IOHIDFloat minorRadius, IOHIDFloat majorRadius, IOHIDFloat quality, IOHIDFloat density, IOHIDFloat irregularity, 126 | Boolean range, Boolean touch, IOOptionBits options); 127 | 128 | 129 | 130 | IOHIDEventRef kif_IOHIDEventWithTouches(NSArray *touches) { 131 | uint64_t abTime = mach_absolute_time(); 132 | AbsoluteTime timeStamp; 133 | timeStamp.hi = (UInt32)(abTime >> 32); 134 | timeStamp.lo = (UInt32)(abTime); 135 | IOHIDEventRef handEvent = IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, // allocator 内存分配器 136 | timeStamp, // timestamp 时间戳 137 | kIOHIDDigitizerTransducerTypeHand, // type 138 | 0, // index 139 | 0, // identity 140 | kIOHIDDigitizerEventTouch, // eventMask 141 | 0, // buttonMask 142 | 0, // x 143 | 0, // y 144 | 0, // z 145 | 0, // tipPressure 146 | 0, // barrelPressure 147 | 0, // range 148 | true, // touch 149 | 0); // options 150 | IOHIDEventSetIntegerValue(handEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, true); 151 | for (UITouch *touch in touches) 152 | { 153 | uint32_t eventMask = (touch.phase == UITouchPhaseMoved) ? kIOHIDDigitizerEventPosition : (kIOHIDDigitizerEventRange | kIOHIDDigitizerEventTouch); 154 | uint32_t isTouching = (touch.phase == UITouchPhaseEnded) ? 0 : 1; 155 | CGPoint touchLocation = [touch locationInView:touch.window]; 156 | IOHIDEventRef fingerEvent = IOHIDEventCreateDigitizerFingerEventWithQuality(kCFAllocatorDefault, // allocator 157 | timeStamp, // timestamp 158 | (UInt32)[touches indexOfObject:touch] + 1, //index 159 | 2, // identity 160 | eventMask, // eventMask 161 | (IOHIDFloat)touchLocation.x, // x 162 | (IOHIDFloat)touchLocation.y, // y 163 | 0.0, // z 164 | 0, // tipPressure 165 | 0, // twist 166 | 5.0, // minor radius 167 | 5.0, // major radius 168 | 1.0, // quality 169 | 1.0, // density 170 | 1.0, // irregularity 171 | (IOHIDFloat)isTouching, // range 172 | (IOHIDFloat)isTouching, // touch 173 | 0); // options 174 | IOHIDEventSetIntegerValue(fingerEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1); 175 | IOHIDEventAppendEvent(handEvent, fingerEvent); 176 | CFRelease(fingerEvent); 177 | } 178 | return handEvent; 179 | } 180 | 181 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIApplication-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UIApplication-KIFAdditions.h" 11 | #import "LoadableCategory.h" 12 | #import "UIView-KIFAdditions.h" 13 | #import "NSError-KIFAdditions.h" 14 | #import 15 | #import 16 | #import 17 | 18 | MAKE_CATEGORIES_LOADABLE(UIApplication_KIFAdditions) 19 | 20 | static BOOL _KIF_UIApplicationMockOpenURL = NO; 21 | static BOOL _KIF_UIApplicationMockOpenURL_returnValue = NO; 22 | 23 | @interface UIApplication (Undocumented) 24 | - (void)pushRunLoopMode:(id)arg1; 25 | - (void)pushRunLoopMode:(id)arg1 requester:(id)requester; 26 | - (void)popRunLoopMode:(id)arg1; 27 | - (void)popRunLoopMode:(id)arg1 requester:(id)requester; 28 | @end 29 | 30 | NSString *const UIApplicationDidMockOpenURLNotification = @"UIApplicationDidMockOpenURLNotification"; 31 | NSString *const UIApplicationDidMockCanOpenURLNotification = @"UIApplicationDidMockCanOpenURLNotification"; 32 | NSString *const UIApplicationOpenedURLKey = @"UIApplicationOpenedURL"; 33 | static const void *KIFRunLoopModesKey = &KIFRunLoopModesKey; 34 | 35 | @implementation UIApplication (KIFAdditions) 36 | 37 | #pragma mark - Finding elements 38 | 39 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value traits:(UIAccessibilityTraits)traits; 40 | { 41 | // Go through the array of windows in reverse order to process the frontmost window first. 42 | // When several elements with the same accessibilitylabel are present the one in front will be picked. 43 | for (UIWindow *window in [self.windowsWithKeyWindow reverseObjectEnumerator]) { 44 | UIAccessibilityElement *element = [window accessibilityElementWithLabel:label accessibilityValue:value traits:traits]; 45 | if (element) { 46 | return element; 47 | } 48 | } 49 | 50 | return nil; 51 | } 52 | 53 | - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessibilityElement *))matchBlock; 54 | { 55 | for (UIWindow *window in [self.windowsWithKeyWindow reverseObjectEnumerator]) { 56 | UIAccessibilityElement *element = [window accessibilityElementMatchingBlock:matchBlock]; 57 | if (element) { 58 | return element; 59 | } 60 | } 61 | 62 | return nil; 63 | } 64 | 65 | #pragma mark - Interesting windows 66 | 67 | - (UIWindow *)keyboardWindow; 68 | { 69 | for (UIWindow *window in self.windowsWithKeyWindow) { 70 | if ([NSStringFromClass([window class]) isEqual:@"UITextEffectsWindow"]) { 71 | return window; 72 | } 73 | } 74 | 75 | return nil; 76 | } 77 | 78 | - (UIWindow *)datePickerWindow; 79 | { 80 | return [self getWindowForSubviewClass:@"UIDatePicker"]; 81 | } 82 | 83 | - (UIWindow *)pickerViewWindow; 84 | { 85 | return [self getWindowForSubviewClass:@"UIPickerView"]; 86 | } 87 | 88 | - (UIWindow *)dimmingViewWindow; 89 | { 90 | return [self getWindowForSubviewClass:@"UIDimmingView"]; 91 | } 92 | 93 | - (UIWindow *)getWindowForSubviewClass:(NSString*)className; 94 | { 95 | for (UIWindow *window in self.windowsWithKeyWindow) { 96 | NSArray *subViews = [window subviewsWithClassNameOrSuperClassNamePrefix:className]; 97 | if (subViews.count > 0) { 98 | return window; 99 | } 100 | } 101 | 102 | return nil; 103 | } 104 | 105 | - (NSArray *)windowsWithKeyWindow 106 | { 107 | NSMutableArray *windows = self.windows.mutableCopy; 108 | UIWindow *keyWindow = self.keyWindow; 109 | if (![windows containsObject:keyWindow]) { 110 | [windows addObject:keyWindow]; 111 | } 112 | return windows; 113 | } 114 | 115 | #pragma mark - Screenshotting 116 | 117 | - (BOOL)writeScreenshotForLine:(NSUInteger)lineNumber inFile:(NSString *)filename description:(NSString *)description error:(NSError **)error; 118 | { 119 | NSString *outputPath = [[[NSProcessInfo processInfo] environment] objectForKey:@"KIF_SCREENSHOTS"]; 120 | if (!outputPath) { 121 | if (error) { 122 | *error = [NSError KIFErrorWithFormat:@"Screenshot path not defined. Please set KIF_SCREENSHOTS environment variable."]; 123 | } 124 | return NO; 125 | } 126 | 127 | NSArray *windows = [self windowsWithKeyWindow]; 128 | if (windows.count == 0) { 129 | if (error) { 130 | *error = [NSError KIFErrorWithFormat:@"Could not take screenshot. No windows were available."]; 131 | } 132 | return NO; 133 | } 134 | 135 | if (!filename.length) { 136 | if (error) { 137 | *error = [NSError KIFErrorWithFormat:@"Missing screenshot filename."]; 138 | } 139 | return NO; 140 | } 141 | 142 | UIGraphicsBeginImageContextWithOptions([[windows objectAtIndex:0] bounds].size, YES, 0); 143 | for (UIWindow *window in windows) { 144 | //avoid https://github.com/kif-framework/KIF/issues/679 145 | if (window.hidden) { 146 | continue; 147 | } 148 | 149 | if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { 150 | [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; 151 | } else { 152 | [window.layer renderInContext:UIGraphicsGetCurrentContext()]; 153 | } 154 | } 155 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 156 | UIGraphicsEndImageContext(); 157 | 158 | outputPath = [outputPath stringByExpandingTildeInPath]; 159 | 160 | NSError *directoryCreationError = nil; 161 | if (![[NSFileManager defaultManager] createDirectoryAtPath:outputPath withIntermediateDirectories:YES attributes:nil error:&directoryCreationError]) { 162 | if (error) { 163 | *error = [NSError KIFErrorWithFormat:@"Couldn't create directory at path %@ (details: %@)", outputPath, directoryCreationError]; 164 | } 165 | return NO; 166 | } 167 | 168 | NSString *imageName = [self imageNameForFile:filename lineNumber:lineNumber description:description]; 169 | 170 | outputPath = [outputPath stringByAppendingPathComponent:imageName]; 171 | outputPath = [outputPath stringByAppendingPathExtension:@"png"]; 172 | 173 | if (![UIImagePNGRepresentation(image) writeToFile:outputPath atomically:YES]) { 174 | if (error) { 175 | *error = [NSError KIFErrorWithFormat:@"Could not write file at path %@", outputPath]; 176 | } 177 | return NO; 178 | } 179 | 180 | return YES; 181 | } 182 | 183 | - (NSString *)imageNameForFile:(NSString *)filename lineNumber:(NSUInteger)lineNumber description:(NSString *)description { 184 | if (!filename.length) { 185 | return nil; 186 | } 187 | 188 | NSString *imageName = [filename lastPathComponent]; 189 | 190 | if (lineNumber > 0) { 191 | imageName = [imageName stringByAppendingFormat:@", line %lu", (unsigned long)lineNumber]; 192 | } 193 | 194 | if (description.length) { 195 | imageName = [imageName stringByAppendingFormat:@", %@", description]; 196 | } 197 | 198 | return imageName; 199 | } 200 | 201 | #pragma mark - Run loop monitoring 202 | 203 | - (NSMutableArray *)KIF_runLoopModes; 204 | { 205 | NSMutableArray *modes = objc_getAssociatedObject(self, KIFRunLoopModesKey); 206 | if (!modes) { 207 | modes = [NSMutableArray arrayWithObject:(id)kCFRunLoopDefaultMode]; 208 | objc_setAssociatedObject(self, KIFRunLoopModesKey, modes, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 209 | } 210 | return modes; 211 | } 212 | 213 | - (CFStringRef)currentRunLoopMode; 214 | { 215 | return (__bridge CFStringRef)[self KIF_runLoopModes].lastObject; 216 | } 217 | 218 | - (void)KIF_pushRunLoopMode:(NSString *)mode; 219 | { 220 | [[self KIF_runLoopModes] addObject:mode]; 221 | [self KIF_pushRunLoopMode:mode]; 222 | } 223 | 224 | - (void)KIF_pushRunLoopMode:(NSString *)mode requester:(id)requester; 225 | { 226 | [[self KIF_runLoopModes] addObject:mode]; 227 | [self KIF_pushRunLoopMode:mode requester:requester]; 228 | } 229 | 230 | - (void)KIF_popRunLoopMode:(NSString *)mode; 231 | { 232 | [[self KIF_runLoopModes] removeLastObject]; 233 | [self KIF_popRunLoopMode:mode]; 234 | } 235 | 236 | 237 | - (void)KIF_popRunLoopMode:(NSString *)mode requester:(id)requester; 238 | { 239 | [[self KIF_runLoopModes] removeLastObject]; 240 | [self KIF_popRunLoopMode:mode requester:requester]; 241 | } 242 | 243 | - (BOOL)KIF_openURL:(NSURL *)URL; 244 | { 245 | if (_KIF_UIApplicationMockOpenURL) { 246 | [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidMockOpenURLNotification object:self userInfo:@{UIApplicationOpenedURLKey: URL}]; 247 | return _KIF_UIApplicationMockOpenURL_returnValue; 248 | } else { 249 | return [self KIF_openURL:URL]; 250 | } 251 | } 252 | 253 | - (BOOL)KIF_canOpenURL:(NSURL *)URL; 254 | { 255 | if (_KIF_UIApplicationMockOpenURL) { 256 | [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidMockCanOpenURLNotification object:self userInfo:@{UIApplicationOpenedURLKey: URL}]; 257 | return _KIF_UIApplicationMockOpenURL_returnValue; 258 | } else { 259 | return [self KIF_canOpenURL:URL]; 260 | } 261 | } 262 | 263 | static inline void Swizzle(Class c, SEL orig, SEL new) 264 | { 265 | Method origMethod = class_getInstanceMethod(c, orig); 266 | Method newMethod = class_getInstanceMethod(c, new); 267 | if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 268 | class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 269 | else 270 | method_exchangeImplementations(origMethod, newMethod); 271 | } 272 | 273 | + (void)swizzleRunLoop; 274 | { 275 | static dispatch_once_t onceToken; 276 | dispatch_once(&onceToken, ^{ 277 | Swizzle(self, @selector(pushRunLoopMode:), @selector(KIF_pushRunLoopMode:)); 278 | Swizzle(self, @selector(pushRunLoopMode:requester:), @selector(KIF_pushRunLoopMode:requester:)); 279 | Swizzle(self, @selector(popRunLoopMode:), @selector(KIF_popRunLoopMode:)); 280 | Swizzle(self, @selector(popRunLoopMode:requester:), @selector(KIF_popRunLoopMode:requester:)); 281 | }); 282 | } 283 | 284 | #pragma mark - openURL mocking 285 | 286 | + (void)startMockingOpenURLWithReturnValue:(BOOL)returnValue; 287 | { 288 | static dispatch_once_t onceToken; 289 | dispatch_once(&onceToken, ^{ 290 | Swizzle(self, @selector(openURL:), @selector(KIF_openURL:)); 291 | Swizzle(self, @selector(canOpenURL:), @selector(KIF_canOpenURL:)); 292 | }); 293 | 294 | _KIF_UIApplicationMockOpenURL = YES; 295 | _KIF_UIApplicationMockOpenURL_returnValue = returnValue; 296 | } 297 | 298 | + (void)stopMockingOpenURL; 299 | { 300 | _KIF_UIApplicationMockOpenURL = NO; 301 | } 302 | 303 | @end 304 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIAccessibilityElement-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccessibilityElement-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/23/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "NSError-KIFAdditions.h" 11 | #import "NSPredicate+KIFAdditions.h" 12 | #import "UIAccessibilityElement-KIFAdditions.h" 13 | #import "UIApplication-KIFAdditions.h" 14 | #import "UIScrollView-KIFAdditions.h" 15 | #import "UIView-KIFAdditions.h" 16 | #import "LoadableCategory.h" 17 | #import "KIFTestActor.h" 18 | 19 | MAKE_CATEGORIES_LOADABLE(UIAccessibilityElement_KIFAdditions) 20 | 21 | 22 | @implementation UIAccessibilityElement (KIFAdditions) 23 | 24 | + (UIView *)viewContainingAccessibilityElement:(UIAccessibilityElement *)element; 25 | { 26 | while (element && ![element isKindOfClass:[UIView class]]) { 27 | // Sometimes accessibilityContainer will return a view that's too far up the view hierarchy 28 | // UIAccessibilityElement instances will sometimes respond to view, so try to use that and then fall back to accessibilityContainer 29 | id view = [element respondsToSelector:@selector(view)] ? [(id)element view] : nil; 30 | 31 | if (view) { 32 | element = view; 33 | } else { 34 | element = [element accessibilityContainer]; 35 | } 36 | } 37 | 38 | return (UIView *)element; 39 | } 40 | 41 | + (BOOL)accessibilityElement:(out UIAccessibilityElement **)foundElement view:(out UIView **)foundView withLabel:(NSString *)label value:(NSString *)value traits:(UIAccessibilityTraits)traits tappable:(BOOL)mustBeTappable error:(out NSError **)error; 42 | { 43 | UIAccessibilityElement *element = [self accessibilityElementWithLabel:label value:value traits:traits error:error]; 44 | if (!element) { 45 | return NO; 46 | } 47 | 48 | UIView *view = [self viewContainingAccessibilityElement:element tappable:mustBeTappable error:error]; 49 | if (!view) { 50 | return NO; 51 | } 52 | 53 | if (foundElement) { *foundElement = element; } 54 | if (foundView) { *foundView = view; } 55 | return YES; 56 | } 57 | 58 | + (BOOL)accessibilityElement:(out UIAccessibilityElement **)foundElement view:(out UIView **)foundView withElementMatchingPredicate:(NSPredicate *)predicate tappable:(BOOL)mustBeTappable error:(out NSError **)error; 59 | { 60 | UIAccessibilityElement *element = [[UIApplication sharedApplication] accessibilityElementMatchingBlock:^BOOL(UIAccessibilityElement *element) { 61 | return [predicate evaluateWithObject:element]; 62 | }]; 63 | 64 | if (!element) { 65 | if (error) { 66 | *error = [self errorForFailingPredicate:predicate]; 67 | } 68 | return NO; 69 | } 70 | 71 | UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element tappable:mustBeTappable error:error]; 72 | if (!view) { 73 | return NO; 74 | } 75 | 76 | if (foundElement) { *foundElement = element; } 77 | if (foundView) { *foundView = view; } 78 | return YES; 79 | } 80 | 81 | + (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label value:(NSString *)value traits:(UIAccessibilityTraits)traits error:(out NSError **)error; 82 | { 83 | UIAccessibilityElement *element = [[UIApplication sharedApplication] accessibilityElementWithLabel:label accessibilityValue:value traits:traits]; 84 | if (element || !error) { 85 | return element; 86 | } 87 | 88 | element = [[UIApplication sharedApplication] accessibilityElementWithLabel:label accessibilityValue:nil traits:traits]; 89 | // For purposes of a better error message, see if we can find the view, just not a view with the specified value. 90 | if (value && element) { 91 | *error = [NSError KIFErrorWithFormat:@"Found an accessibility element with the label \"%@\", but with the value \"%@\", not \"%@\"", label, element.accessibilityValue, value]; 92 | return nil; 93 | } 94 | 95 | // Check the traits, too. 96 | element = [[UIApplication sharedApplication] accessibilityElementWithLabel:label accessibilityValue:nil traits:UIAccessibilityTraitNone]; 97 | if (traits != UIAccessibilityTraitNone && element) { 98 | *error = [NSError KIFErrorWithFormat:@"Found an accessibility element with the label \"%@\", but not with the traits \"%llu\"", label, traits]; 99 | return nil; 100 | } 101 | 102 | *error = [NSError KIFErrorWithFormat:@"Failed to find accessibility element with the label \"%@\"", label]; 103 | return nil; 104 | } 105 | 106 | + (UIView *)viewContainingAccessibilityElement:(UIAccessibilityElement *)element tappable:(BOOL)mustBeTappable error:(NSError **)error; 107 | { 108 | // Small safety mechanism. If someone calls this method after a failing call to accessibilityElementWithLabel:..., we don't want to wipe out the error message. 109 | if (!element && error && *error) { 110 | return nil; 111 | } 112 | 113 | // Make sure the element is visible 114 | UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element]; 115 | if (!view) { 116 | if (error) { 117 | *error = [NSError KIFErrorWithFormat:@"Cannot find view containing accessibility element with the label \"%@\"", element.accessibilityLabel]; 118 | } 119 | return nil; 120 | } 121 | 122 | // Scroll the view (and superviews) to be visible if necessary 123 | UIView *superview = (UIScrollView *)view; 124 | while (superview) { 125 | // Fix for iOS7 table view cells containing scroll views 126 | if ([superview.superview isKindOfClass:[UITableViewCell class]]) { 127 | break; 128 | } 129 | 130 | if ([superview isKindOfClass:[UIScrollView class]]) { 131 | UIScrollView *scrollView = (UIScrollView *)superview; 132 | 133 | if (((UIAccessibilityElement *)view == element) && ![view isKindOfClass:[UITableViewCell class]]) { 134 | [scrollView scrollViewToVisible:view animated:YES]; 135 | } else { 136 | CGRect elementFrame = [view.window convertRect:element.accessibilityFrame toView:scrollView]; 137 | CGRect visibleRect = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, CGRectGetWidth(scrollView.bounds), CGRectGetHeight(scrollView.bounds)); 138 | 139 | // Only call scrollRectToVisible if the element isn't already visible 140 | // iOS 8 will sometimes incorrectly scroll table views so the element scrolls out of view 141 | if (!CGRectContainsRect(visibleRect, elementFrame)) { 142 | [scrollView scrollRectToVisible:elementFrame animated:YES]; 143 | } 144 | } 145 | 146 | // Give the scroll view a small amount of time to perform the scroll. 147 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.3, false); 148 | } 149 | 150 | superview = superview.superview; 151 | } 152 | 153 | if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]) { 154 | if (error) { 155 | *error = [NSError KIFErrorWithFormat:@"Application is ignoring interaction events"]; 156 | } 157 | return nil; 158 | } 159 | 160 | // If we don't require tappability, at least make sure it's not hidden 161 | if ([view isHidden]) { 162 | if (error) { 163 | *error = [NSError KIFErrorWithFormat:@"Accessibility element with label \"%@\" is hidden.", element.accessibilityLabel]; 164 | } 165 | return nil; 166 | } 167 | 168 | if (mustBeTappable && !view.isProbablyTappable) { 169 | if (error) { 170 | *error = [NSError KIFErrorWithFormat:@"Accessibility element with label \"%@\" is not tappable. It may be blocked by other views.", element.accessibilityLabel]; 171 | } 172 | return nil; 173 | } 174 | 175 | return view; 176 | } 177 | 178 | + (NSError *)errorForFailingPredicate:(NSPredicate*)failingPredicate; 179 | { 180 | NSPredicate *closestMatchingPredicate = [self findClosestMatchingPredicate:failingPredicate]; 181 | if (closestMatchingPredicate) { 182 | return [NSError KIFErrorWithFormat:@"Found element with %@ but not %@", \ 183 | closestMatchingPredicate.kifPredicateDescription, \ 184 | [failingPredicate minusSubpredicatesFrom:closestMatchingPredicate].kifPredicateDescription]; 185 | } 186 | return [NSError KIFErrorWithFormat:@"Could not find element with %@", failingPredicate.kifPredicateDescription]; 187 | } 188 | 189 | + (NSPredicate *)findClosestMatchingPredicate:(NSPredicate *)aPredicate; 190 | { 191 | if (!aPredicate) { 192 | return nil; 193 | } 194 | 195 | UIAccessibilityElement *match = [[UIApplication sharedApplication] accessibilityElementMatchingBlock:^BOOL (UIAccessibilityElement *element) { 196 | return [aPredicate evaluateWithObject:element]; 197 | }]; 198 | if (match) { 199 | return aPredicate; 200 | } 201 | 202 | // Breadth-First algorithm to match as many subpredicates as possible 203 | NSMutableArray *queue = [NSMutableArray arrayWithObject:aPredicate]; 204 | while (queue.count > 0) { 205 | // Dequeuing 206 | NSPredicate *predicate = [queue firstObject]; 207 | [queue removeObject:predicate]; 208 | 209 | // Remove one subpredicate at a time an then check if an element would match this resulting predicate 210 | for (NSPredicate *subpredicate in [predicate flatten]) { 211 | NSPredicate *predicateMinusOneCondition = [predicate minusSubpredicatesFrom:subpredicate]; 212 | if (predicateMinusOneCondition) { 213 | UIAccessibilityElement *match = [[UIApplication sharedApplication] accessibilityElementMatchingBlock:^BOOL (UIAccessibilityElement *element) { 214 | return [predicateMinusOneCondition evaluateWithObject:element]; 215 | }]; 216 | if (match) { 217 | return predicateMinusOneCondition; 218 | } 219 | [queue addObject:predicateMinusOneCondition]; 220 | } 221 | } 222 | } 223 | return nil; 224 | } 225 | 226 | + (NSString *)stringFromAccessibilityTraits:(UIAccessibilityTraits)traits; 227 | { 228 | if (traits == UIAccessibilityTraitNone) { 229 | return @"UIAccessibilityTraitNone"; 230 | } 231 | 232 | NSString *string = @""; 233 | 234 | NSArray *allTraits = @[ 235 | @(UIAccessibilityTraitButton), 236 | @(UIAccessibilityTraitLink), 237 | @(UIAccessibilityTraitHeader), 238 | @(UIAccessibilityTraitSearchField), 239 | @(UIAccessibilityTraitImage), 240 | @(UIAccessibilityTraitSelected), 241 | @(UIAccessibilityTraitPlaysSound), 242 | @(UIAccessibilityTraitKeyboardKey), 243 | @(UIAccessibilityTraitStaticText), 244 | @(UIAccessibilityTraitSummaryElement), 245 | @(UIAccessibilityTraitNotEnabled), 246 | @(UIAccessibilityTraitUpdatesFrequently), 247 | @(UIAccessibilityTraitStartsMediaSession), 248 | @(UIAccessibilityTraitAdjustable), 249 | @(UIAccessibilityTraitAllowsDirectInteraction), 250 | @(UIAccessibilityTraitCausesPageTurn) 251 | ]; 252 | 253 | NSArray *traitNames = @[ 254 | @"UIAccessibilityTraitButton", 255 | @"UIAccessibilityTraitLink", 256 | @"UIAccessibilityTraitHeader", 257 | @"UIAccessibilityTraitSearchField", 258 | @"UIAccessibilityTraitImage", 259 | @"UIAccessibilityTraitSelected", 260 | @"UIAccessibilityTraitPlaysSound", 261 | @"UIAccessibilityTraitKeyboardKey", 262 | @"UIAccessibilityTraitStaticText", 263 | @"UIAccessibilityTraitSummaryElement", 264 | @"UIAccessibilityTraitNotEnabled", 265 | @"UIAccessibilityTraitUpdatesFrequently", 266 | @"UIAccessibilityTraitStartsMediaSession", 267 | @"UIAccessibilityTraitAdjustable", 268 | @"UIAccessibilityTraitAllowsDirectInteraction", 269 | @"UIAccessibilityTraitCausesPageTurn" 270 | ]; 271 | 272 | 273 | for (NSNumber *trait in allTraits) { 274 | if ((traits & trait.longLongValue) == trait.longLongValue) { 275 | NSString *name = [traitNames objectAtIndex:[allTraits indexOfObject:trait]]; 276 | if (string.length > 0) { 277 | string = [string stringByAppendingString:@", "]; 278 | } 279 | string = [string stringByAppendingString:name]; 280 | traits &= ~trait.longLongValue; 281 | } 282 | } 283 | if (traits != UIAccessibilityTraitNone) { 284 | if (string.length > 0) { 285 | string = [string stringByAppendingString:@", "]; 286 | } 287 | string = [string stringByAppendingFormat:@"UNKNOWN ACCESSIBILITY TRAIT: %llu", traits]; 288 | } 289 | return string; 290 | } 291 | 292 | @end 293 | -------------------------------------------------------------------------------- /PTFakeTouch.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 15BBDF041D61AD5B0098330F /* PTFakeTouch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15BBDEF91D61AD5B0098330F /* PTFakeTouch.framework */; }; 11 | 15BBDF091D61AD5B0098330F /* PTFakeTouchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF081D61AD5B0098330F /* PTFakeTouchTests.m */; }; 12 | 15BBDF161D61AD920098330F /* PTFakeMetaTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF131D61AD920098330F /* PTFakeMetaTouch.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 15BBDF171D61AD920098330F /* PTFakeMetaTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF141D61AD920098330F /* PTFakeMetaTouch.m */; }; 14 | 15BBDF1A1D61ADE00098330F /* PTFakeTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF191D61ADE00098330F /* PTFakeTouch.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 15BBDF1D1D61AE470098330F /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15BBDF1C1D61AE470098330F /* IOKit.framework */; }; 16 | 15BBDF291D61AECA0098330F /* UIApplication-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF1F1D61AECA0098330F /* UIApplication-KIFAdditions.h */; }; 17 | 15BBDF2A1D61AECA0098330F /* UIApplication-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF201D61AECA0098330F /* UIApplication-KIFAdditions.m */; }; 18 | 15BBDF2B1D61AECA0098330F /* UIEvent+KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF211D61AECA0098330F /* UIEvent+KIFAdditions.h */; }; 19 | 15BBDF2C1D61AECA0098330F /* UIEvent+KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF221D61AECA0098330F /* UIEvent+KIFAdditions.m */; }; 20 | 15BBDF2D1D61AECA0098330F /* UITouch-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF231D61AECA0098330F /* UITouch-KIFAdditions.h */; }; 21 | 15BBDF2E1D61AECA0098330F /* UITouch-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF241D61AECA0098330F /* UITouch-KIFAdditions.m */; }; 22 | 15BBDF2F1D61AECA0098330F /* UIView-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF251D61AECA0098330F /* UIView-KIFAdditions.h */; }; 23 | 15BBDF301D61AECA0098330F /* UIView-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF261D61AECA0098330F /* UIView-KIFAdditions.m */; }; 24 | 15BBDF311D61AECA0098330F /* UIWindow-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF271D61AECA0098330F /* UIWindow-KIFAdditions.h */; }; 25 | 15BBDF321D61AECA0098330F /* UIWindow-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF281D61AECA0098330F /* UIWindow-KIFAdditions.m */; }; 26 | 15BBDF351D61AF010098330F /* IOHIDEvent+KIF.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF331D61AF010098330F /* IOHIDEvent+KIF.h */; }; 27 | 15BBDF361D61AF010098330F /* IOHIDEvent+KIF.m in Sources */ = {isa = PBXBuildFile; fileRef = 15BBDF341D61AF010098330F /* IOHIDEvent+KIF.m */; }; 28 | 15BBDF381D61AF540098330F /* FixCategoryBug.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF371D61AF540098330F /* FixCategoryBug.h */; }; 29 | 15BBDF3A1D61AFC50098330F /* LoadableCategory.h in Headers */ = {isa = PBXBuildFile; fileRef = 15BBDF391D61AFC50098330F /* LoadableCategory.h */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 15BBDF051D61AD5B0098330F /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 15BBDEF01D61AD5B0098330F /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 15BBDEF81D61AD5B0098330F; 38 | remoteInfo = PTFakeTouch; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 15BBDEF91D61AD5B0098330F /* PTFakeTouch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PTFakeTouch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 15BBDEFE1D61AD5B0098330F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 15BBDF031D61AD5B0098330F /* PTFakeTouchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PTFakeTouchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 15BBDF081D61AD5B0098330F /* PTFakeTouchTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTFakeTouchTests.m; sourceTree = ""; }; 47 | 15BBDF0A1D61AD5B0098330F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 15BBDF131D61AD920098330F /* PTFakeMetaTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTFakeMetaTouch.h; sourceTree = ""; }; 49 | 15BBDF141D61AD920098330F /* PTFakeMetaTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTFakeMetaTouch.m; sourceTree = ""; }; 50 | 15BBDF191D61ADE00098330F /* PTFakeTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTFakeTouch.h; sourceTree = ""; }; 51 | 15BBDF1C1D61AE470098330F /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = IOKit.framework; sourceTree = ""; }; 52 | 15BBDF1F1D61AECA0098330F /* UIApplication-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIApplication-KIFAdditions.h"; path = "addition/UIApplication-KIFAdditions.h"; sourceTree = ""; }; 53 | 15BBDF201D61AECA0098330F /* UIApplication-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIApplication-KIFAdditions.m"; path = "addition/UIApplication-KIFAdditions.m"; sourceTree = ""; }; 54 | 15BBDF211D61AECA0098330F /* UIEvent+KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIEvent+KIFAdditions.h"; path = "addition/UIEvent+KIFAdditions.h"; sourceTree = ""; }; 55 | 15BBDF221D61AECA0098330F /* UIEvent+KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIEvent+KIFAdditions.m"; path = "addition/UIEvent+KIFAdditions.m"; sourceTree = ""; }; 56 | 15BBDF231D61AECA0098330F /* UITouch-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UITouch-KIFAdditions.h"; path = "addition/UITouch-KIFAdditions.h"; sourceTree = ""; }; 57 | 15BBDF241D61AECA0098330F /* UITouch-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UITouch-KIFAdditions.m"; path = "addition/UITouch-KIFAdditions.m"; sourceTree = ""; }; 58 | 15BBDF251D61AECA0098330F /* UIView-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView-KIFAdditions.h"; path = "addition/UIView-KIFAdditions.h"; sourceTree = ""; }; 59 | 15BBDF261D61AECA0098330F /* UIView-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView-KIFAdditions.m"; path = "addition/UIView-KIFAdditions.m"; sourceTree = ""; }; 60 | 15BBDF271D61AECA0098330F /* UIWindow-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIWindow-KIFAdditions.h"; path = "addition/UIWindow-KIFAdditions.h"; sourceTree = ""; }; 61 | 15BBDF281D61AECA0098330F /* UIWindow-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIWindow-KIFAdditions.m"; path = "addition/UIWindow-KIFAdditions.m"; sourceTree = ""; }; 62 | 15BBDF331D61AF010098330F /* IOHIDEvent+KIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "IOHIDEvent+KIF.h"; path = "addition/IOHIDEvent+KIF.h"; sourceTree = ""; }; 63 | 15BBDF341D61AF010098330F /* IOHIDEvent+KIF.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "IOHIDEvent+KIF.m"; path = "addition/IOHIDEvent+KIF.m"; sourceTree = ""; }; 64 | 15BBDF371D61AF540098330F /* FixCategoryBug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FixCategoryBug.h; sourceTree = ""; }; 65 | 15BBDF391D61AFC50098330F /* LoadableCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LoadableCategory.h; path = addition/LoadableCategory.h; sourceTree = ""; }; 66 | 6D69E1C920FC9D7A006C0F74 /* PTFakeTouch copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PTFakeTouch copy-Info.plist"; path = "/Users/pt/Desktop/git-work/PTFakeTouch/PTFakeTouch copy-Info.plist"; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 15BBDEF51D61AD5B0098330F /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 15BBDF1D1D61AE470098330F /* IOKit.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 15BBDF001D61AD5B0098330F /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 15BBDF041D61AD5B0098330F /* PTFakeTouch.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 15BBDEEF1D61AD5B0098330F = { 90 | isa = PBXGroup; 91 | children = ( 92 | 15BBDEFB1D61AD5B0098330F /* PTFakeTouch */, 93 | 15BBDF071D61AD5B0098330F /* PTFakeTouchTests */, 94 | 15BBDEFA1D61AD5B0098330F /* Products */, 95 | 15BBDF1B1D61AE310098330F /* Frameworks */, 96 | 6D69E1C920FC9D7A006C0F74 /* PTFakeTouch copy-Info.plist */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 15BBDEFA1D61AD5B0098330F /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 15BBDEF91D61AD5B0098330F /* PTFakeTouch.framework */, 104 | 15BBDF031D61AD5B0098330F /* PTFakeTouchTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 15BBDEFB1D61AD5B0098330F /* PTFakeTouch */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 15BBDF131D61AD920098330F /* PTFakeMetaTouch.h */, 113 | 15BBDF141D61AD920098330F /* PTFakeMetaTouch.m */, 114 | 15BBDF191D61ADE00098330F /* PTFakeTouch.h */, 115 | 15BBDF371D61AF540098330F /* FixCategoryBug.h */, 116 | 15BBDF391D61AFC50098330F /* LoadableCategory.h */, 117 | 15BBDEFE1D61AD5B0098330F /* Info.plist */, 118 | 15BBDF1E1D61AE580098330F /* Addition */, 119 | ); 120 | path = PTFakeTouch; 121 | sourceTree = ""; 122 | }; 123 | 15BBDF071D61AD5B0098330F /* PTFakeTouchTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 15BBDF081D61AD5B0098330F /* PTFakeTouchTests.m */, 127 | 15BBDF0A1D61AD5B0098330F /* Info.plist */, 128 | ); 129 | path = PTFakeTouchTests; 130 | sourceTree = ""; 131 | }; 132 | 15BBDF1B1D61AE310098330F /* Frameworks */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 15BBDF1C1D61AE470098330F /* IOKit.framework */, 136 | ); 137 | name = Frameworks; 138 | sourceTree = ""; 139 | }; 140 | 15BBDF1E1D61AE580098330F /* Addition */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 15BBDF331D61AF010098330F /* IOHIDEvent+KIF.h */, 144 | 15BBDF341D61AF010098330F /* IOHIDEvent+KIF.m */, 145 | 15BBDF1F1D61AECA0098330F /* UIApplication-KIFAdditions.h */, 146 | 15BBDF201D61AECA0098330F /* UIApplication-KIFAdditions.m */, 147 | 15BBDF211D61AECA0098330F /* UIEvent+KIFAdditions.h */, 148 | 15BBDF221D61AECA0098330F /* UIEvent+KIFAdditions.m */, 149 | 15BBDF231D61AECA0098330F /* UITouch-KIFAdditions.h */, 150 | 15BBDF241D61AECA0098330F /* UITouch-KIFAdditions.m */, 151 | 15BBDF251D61AECA0098330F /* UIView-KIFAdditions.h */, 152 | 15BBDF261D61AECA0098330F /* UIView-KIFAdditions.m */, 153 | 15BBDF271D61AECA0098330F /* UIWindow-KIFAdditions.h */, 154 | 15BBDF281D61AECA0098330F /* UIWindow-KIFAdditions.m */, 155 | ); 156 | name = Addition; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXHeadersBuildPhase section */ 162 | 15BBDEF61D61AD5B0098330F /* Headers */ = { 163 | isa = PBXHeadersBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 15BBDF1A1D61ADE00098330F /* PTFakeTouch.h in Headers */, 167 | 15BBDF161D61AD920098330F /* PTFakeMetaTouch.h in Headers */, 168 | 15BBDF381D61AF540098330F /* FixCategoryBug.h in Headers */, 169 | 15BBDF2F1D61AECA0098330F /* UIView-KIFAdditions.h in Headers */, 170 | 15BBDF311D61AECA0098330F /* UIWindow-KIFAdditions.h in Headers */, 171 | 15BBDF351D61AF010098330F /* IOHIDEvent+KIF.h in Headers */, 172 | 15BBDF3A1D61AFC50098330F /* LoadableCategory.h in Headers */, 173 | 15BBDF2B1D61AECA0098330F /* UIEvent+KIFAdditions.h in Headers */, 174 | 15BBDF291D61AECA0098330F /* UIApplication-KIFAdditions.h in Headers */, 175 | 15BBDF2D1D61AECA0098330F /* UITouch-KIFAdditions.h in Headers */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXHeadersBuildPhase section */ 180 | 181 | /* Begin PBXNativeTarget section */ 182 | 15BBDEF81D61AD5B0098330F /* PTFakeTouch */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 15BBDF0D1D61AD5B0098330F /* Build configuration list for PBXNativeTarget "PTFakeTouch" */; 185 | buildPhases = ( 186 | 15BBDEF41D61AD5B0098330F /* Sources */, 187 | 15BBDEF51D61AD5B0098330F /* Frameworks */, 188 | 15BBDEF61D61AD5B0098330F /* Headers */, 189 | 15BBDEF71D61AD5B0098330F /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = PTFakeTouch; 196 | productName = PTFakeTouch; 197 | productReference = 15BBDEF91D61AD5B0098330F /* PTFakeTouch.framework */; 198 | productType = "com.apple.product-type.framework"; 199 | }; 200 | 15BBDF021D61AD5B0098330F /* PTFakeTouchTests */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 15BBDF101D61AD5B0098330F /* Build configuration list for PBXNativeTarget "PTFakeTouchTests" */; 203 | buildPhases = ( 204 | 15BBDEFF1D61AD5B0098330F /* Sources */, 205 | 15BBDF001D61AD5B0098330F /* Frameworks */, 206 | 15BBDF011D61AD5B0098330F /* Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | 15BBDF061D61AD5B0098330F /* PBXTargetDependency */, 212 | ); 213 | name = PTFakeTouchTests; 214 | productName = PTFakeTouchTests; 215 | productReference = 15BBDF031D61AD5B0098330F /* PTFakeTouchTests.xctest */; 216 | productType = "com.apple.product-type.bundle.unit-test"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | 15BBDEF01D61AD5B0098330F /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastUpgradeCheck = 0710; 225 | ORGANIZATIONNAME = tangxuan; 226 | TargetAttributes = { 227 | 15BBDEF81D61AD5B0098330F = { 228 | CreatedOnToolsVersion = 7.1; 229 | }; 230 | 15BBDF021D61AD5B0098330F = { 231 | CreatedOnToolsVersion = 7.1; 232 | }; 233 | }; 234 | }; 235 | buildConfigurationList = 15BBDEF31D61AD5B0098330F /* Build configuration list for PBXProject "PTFakeTouch" */; 236 | compatibilityVersion = "Xcode 3.2"; 237 | developmentRegion = English; 238 | hasScannedForEncodings = 0; 239 | knownRegions = ( 240 | en, 241 | ); 242 | mainGroup = 15BBDEEF1D61AD5B0098330F; 243 | productRefGroup = 15BBDEFA1D61AD5B0098330F /* Products */; 244 | projectDirPath = ""; 245 | projectRoot = ""; 246 | targets = ( 247 | 15BBDEF81D61AD5B0098330F /* PTFakeTouch */, 248 | 15BBDF021D61AD5B0098330F /* PTFakeTouchTests */, 249 | ); 250 | }; 251 | /* End PBXProject section */ 252 | 253 | /* Begin PBXResourcesBuildPhase section */ 254 | 15BBDEF71D61AD5B0098330F /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 15BBDF011D61AD5B0098330F /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | 15BBDEF41D61AD5B0098330F /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 15BBDF2E1D61AECA0098330F /* UITouch-KIFAdditions.m in Sources */, 276 | 15BBDF361D61AF010098330F /* IOHIDEvent+KIF.m in Sources */, 277 | 15BBDF2C1D61AECA0098330F /* UIEvent+KIFAdditions.m in Sources */, 278 | 15BBDF301D61AECA0098330F /* UIView-KIFAdditions.m in Sources */, 279 | 15BBDF171D61AD920098330F /* PTFakeMetaTouch.m in Sources */, 280 | 15BBDF321D61AECA0098330F /* UIWindow-KIFAdditions.m in Sources */, 281 | 15BBDF2A1D61AECA0098330F /* UIApplication-KIFAdditions.m in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 15BBDEFF1D61AD5B0098330F /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 15BBDF091D61AD5B0098330F /* PTFakeTouchTests.m in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXSourcesBuildPhase section */ 294 | 295 | /* Begin PBXTargetDependency section */ 296 | 15BBDF061D61AD5B0098330F /* PBXTargetDependency */ = { 297 | isa = PBXTargetDependency; 298 | target = 15BBDEF81D61AD5B0098330F /* PTFakeTouch */; 299 | targetProxy = 15BBDF051D61AD5B0098330F /* PBXContainerItemProxy */; 300 | }; 301 | /* End PBXTargetDependency section */ 302 | 303 | /* Begin XCBuildConfiguration section */ 304 | 15BBDF0B1D61AD5B0098330F /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 309 | CLANG_CXX_LIBRARY = "libc++"; 310 | CLANG_ENABLE_MODULES = YES; 311 | CLANG_ENABLE_OBJC_ARC = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_UNREACHABLE_CODE = YES; 320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 321 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 322 | COPY_PHASE_STRIP = NO; 323 | CURRENT_PROJECT_VERSION = 1; 324 | DEBUG_INFORMATION_FORMAT = dwarf; 325 | ENABLE_STRICT_OBJC_MSGSEND = YES; 326 | ENABLE_TESTABILITY = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_DYNAMIC_NO_PIC = NO; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_OPTIMIZATION_LEVEL = 0; 331 | GCC_PREPROCESSOR_DEFINITIONS = ( 332 | "DEBUG=1", 333 | "$(inherited)", 334 | ); 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 342 | MTL_ENABLE_DEBUG_INFO = YES; 343 | ONLY_ACTIVE_ARCH = YES; 344 | SDKROOT = iphoneos; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | VERSIONING_SYSTEM = "apple-generic"; 347 | VERSION_INFO_PREFIX = ""; 348 | }; 349 | name = Debug; 350 | }; 351 | 15BBDF0C1D61AD5B0098330F /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 356 | CLANG_CXX_LIBRARY = "libc++"; 357 | CLANG_ENABLE_MODULES = YES; 358 | CLANG_ENABLE_OBJC_ARC = YES; 359 | CLANG_WARN_BOOL_CONVERSION = YES; 360 | CLANG_WARN_CONSTANT_CONVERSION = YES; 361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 362 | CLANG_WARN_EMPTY_BODY = YES; 363 | CLANG_WARN_ENUM_CONVERSION = YES; 364 | CLANG_WARN_INT_CONVERSION = YES; 365 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | CURRENT_PROJECT_VERSION = 1; 371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 372 | ENABLE_NS_ASSERTIONS = NO; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu99; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 383 | MTL_ENABLE_DEBUG_INFO = NO; 384 | SDKROOT = iphoneos; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | VALIDATE_PRODUCT = YES; 387 | VERSIONING_SYSTEM = "apple-generic"; 388 | VERSION_INFO_PREFIX = ""; 389 | }; 390 | name = Release; 391 | }; 392 | 15BBDF0E1D61AD5B0098330F /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | DEFINES_MODULE = YES; 396 | DYLIB_COMPATIBILITY_VERSION = 1; 397 | DYLIB_CURRENT_VERSION = 1; 398 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 399 | FRAMEWORK_SEARCH_PATHS = ( 400 | "$(inherited)", 401 | "$(PROJECT_DIR)", 402 | ); 403 | GCC_PREFIX_HEADER = PTFakeTouch/PTFakeTouch.h; 404 | INFOPLIST_FILE = PTFakeTouch/Info.plist; 405 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 406 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 408 | MACH_O_TYPE = staticlib; 409 | PRODUCT_BUNDLE_IDENTIFIER = nilname.PTFakeTouch; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | SKIP_INSTALL = YES; 412 | }; 413 | name = Debug; 414 | }; 415 | 15BBDF0F1D61AD5B0098330F /* Release */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | DEFINES_MODULE = YES; 419 | DYLIB_COMPATIBILITY_VERSION = 1; 420 | DYLIB_CURRENT_VERSION = 1; 421 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 422 | FRAMEWORK_SEARCH_PATHS = ( 423 | "$(inherited)", 424 | "$(PROJECT_DIR)", 425 | ); 426 | GCC_PREFIX_HEADER = PTFakeTouch/PTFakeTouch.h; 427 | INFOPLIST_FILE = PTFakeTouch/Info.plist; 428 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 429 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 431 | MACH_O_TYPE = staticlib; 432 | PRODUCT_BUNDLE_IDENTIFIER = nilname.PTFakeTouch; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | SKIP_INSTALL = YES; 435 | }; 436 | name = Release; 437 | }; 438 | 15BBDF111D61AD5B0098330F /* Debug */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | INFOPLIST_FILE = PTFakeTouchTests/Info.plist; 442 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 443 | PRODUCT_BUNDLE_IDENTIFIER = nilname.PTFakeTouchTests; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | }; 446 | name = Debug; 447 | }; 448 | 15BBDF121D61AD5B0098330F /* Release */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | INFOPLIST_FILE = PTFakeTouchTests/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 453 | PRODUCT_BUNDLE_IDENTIFIER = nilname.PTFakeTouchTests; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | }; 456 | name = Release; 457 | }; 458 | /* End XCBuildConfiguration section */ 459 | 460 | /* Begin XCConfigurationList section */ 461 | 15BBDEF31D61AD5B0098330F /* Build configuration list for PBXProject "PTFakeTouch" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 15BBDF0B1D61AD5B0098330F /* Debug */, 465 | 15BBDF0C1D61AD5B0098330F /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 15BBDF0D1D61AD5B0098330F /* Build configuration list for PBXNativeTarget "PTFakeTouch" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 15BBDF0E1D61AD5B0098330F /* Debug */, 474 | 15BBDF0F1D61AD5B0098330F /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | 15BBDF101D61AD5B0098330F /* Build configuration list for PBXNativeTarget "PTFakeTouchTests" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | 15BBDF111D61AD5B0098330F /* Debug */, 483 | 15BBDF121D61AD5B0098330F /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | /* End XCConfigurationList section */ 489 | }; 490 | rootObject = 15BBDEF01D61AD5B0098330F /* Project object */; 491 | } 492 | -------------------------------------------------------------------------------- /PTFakeTouch/addition/UIView-KIFAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView-KIFAdditions.m 3 | // KIF 4 | // 5 | // Created by Eric Firestone on 5/20/11. 6 | // Licensed to Square, Inc. under one or more contributor license agreements. 7 | // See the LICENSE file distributed with this work for the terms under 8 | // which Square, Inc. licenses this file to you. 9 | 10 | #import "UIView-KIFAdditions.h" 11 | #import "CGGeometry-KIFAdditions.h" 12 | #import "UIAccessibilityElement-KIFAdditions.h" 13 | #import "UIApplication-KIFAdditions.h" 14 | #import "UITouch-KIFAdditions.h" 15 | #import 16 | #import "UIEvent+KIFAdditions.h" 17 | 18 | double KIFDegreesToRadians(double deg) { 19 | return (deg) / 180.0 * M_PI; 20 | } 21 | 22 | double KIFRadiansToDegrees(double rad) { 23 | return ((rad) * (180.0 / M_PI)); 24 | } 25 | 26 | static CGFloat const kTwoFingerConstantWidth = 40; 27 | 28 | 29 | @interface NSObject (UIWebDocumentViewInternal) 30 | 31 | - (void)tapInteractionWithLocation:(CGPoint)point; 32 | 33 | @end 34 | 35 | // On iOS 6 the accessibility label may contain line breaks, so when trying to find the 36 | // element, these line breaks are necessary. But on iOS 7 the system replaces them with 37 | // spaces. So the same test breaks on either iOS 6 or iOS 7. iOS8 befuddles this again by 38 | //limiting replacement to spaces in between strings. To work around this replace 39 | // the line breaks in both and try again. 40 | NS_INLINE BOOL StringsMatchExceptLineBreaks(NSString *expected, NSString *actual) { 41 | if (expected == actual) { 42 | return YES; 43 | } 44 | 45 | if (expected.length != actual.length) { 46 | return NO; 47 | } 48 | 49 | if ([expected isEqualToString:actual]) { 50 | return YES; 51 | } 52 | 53 | if ([expected rangeOfString:@"\n"].location == NSNotFound && 54 | [actual rangeOfString:@"\n"].location == NSNotFound) { 55 | return NO; 56 | } 57 | 58 | for (NSUInteger i = 0; i < expected.length; i ++) { 59 | unichar expectedChar = [expected characterAtIndex:i]; 60 | unichar actualChar = [actual characterAtIndex:i]; 61 | if (expectedChar != actualChar && 62 | !(expectedChar == '\n' && actualChar == ' ') && 63 | !(expectedChar == ' ' && actualChar == '\n')) { 64 | return NO; 65 | } 66 | } 67 | 68 | return YES; 69 | } 70 | 71 | 72 | @implementation UIView (KIFAdditions) 73 | 74 | + (NSSet *)classesToSkipAccessibilitySearchRecursion 75 | { 76 | static NSSet *classesToSkip; 77 | static dispatch_once_t onceToken; 78 | dispatch_once(&onceToken, ^{ 79 | // UIDatePicker contains hundreds of thousands of placeholder accessibility elements that aren't useful to KIF, 80 | // so don't recurse into a date picker when searching for matching accessibility elements 81 | classesToSkip = [[NSSet alloc] initWithObjects:[UIDatePicker class], nil]; 82 | }); 83 | 84 | return classesToSkip; 85 | } 86 | 87 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label 88 | { 89 | return [self accessibilityElementWithLabel:label traits:UIAccessibilityTraitNone]; 90 | } 91 | 92 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label traits:(UIAccessibilityTraits)traits; 93 | { 94 | return [self accessibilityElementWithLabel:label accessibilityValue:nil traits:traits]; 95 | } 96 | 97 | - (UIAccessibilityElement *)accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value traits:(UIAccessibilityTraits)traits; 98 | { 99 | return [self accessibilityElementMatchingBlock:^(UIAccessibilityElement *element) { 100 | 101 | // TODO: This is a temporary fix for an SDK defect. 102 | NSString *accessibilityValue = nil; 103 | @try { 104 | accessibilityValue = element.accessibilityValue; 105 | } 106 | @catch (NSException *exception) { 107 | DLog(@"KIF: Unable to access accessibilityValue for element %@ because of exception: %@", element, exception.reason); 108 | } 109 | 110 | if ([accessibilityValue isKindOfClass:[NSAttributedString class]]) { 111 | accessibilityValue = [(NSAttributedString *)accessibilityValue string]; 112 | } 113 | 114 | BOOL labelsMatch = StringsMatchExceptLineBreaks(label, element.accessibilityLabel); 115 | BOOL traitsMatch = ((element.accessibilityTraits) & traits) == traits; 116 | BOOL valuesMatch = !value || [value isEqual:accessibilityValue]; 117 | 118 | return (BOOL)(labelsMatch && traitsMatch && valuesMatch); 119 | }]; 120 | } 121 | 122 | - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessibilityElement *))matchBlock; 123 | { 124 | return [self accessibilityElementMatchingBlock:matchBlock notHidden:YES]; 125 | } 126 | 127 | - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessibilityElement *))matchBlock notHidden:(BOOL)notHidden; 128 | { 129 | if (notHidden && self.hidden) { 130 | return nil; 131 | } 132 | 133 | // In case multiple elements with the same label exist, prefer ones that are currently visible 134 | UIAccessibilityElement *matchingButOccludedElement = nil; 135 | 136 | BOOL elementMatches = matchBlock((UIAccessibilityElement *)self); 137 | 138 | if (elementMatches) { 139 | if (self.isTappable) { 140 | return (UIAccessibilityElement *)self; 141 | } else { 142 | matchingButOccludedElement = (UIAccessibilityElement *)self; 143 | } 144 | } 145 | 146 | if ([[[self class] classesToSkipAccessibilitySearchRecursion] containsObject:[self class]]) { 147 | return matchingButOccludedElement; 148 | } 149 | 150 | // Check the subviews first. Even if the receiver says it's an accessibility container, 151 | // the returned objects are UIAccessibilityElementMockViews (which aren't actually views) 152 | // rather than the real subviews it contains. We want the real views if possible. 153 | // UITableViewCell is such an offender. 154 | for (UIView *view in [self.subviews reverseObjectEnumerator]) { 155 | UIAccessibilityElement *element = [view accessibilityElementMatchingBlock:matchBlock]; 156 | if (!element) { 157 | continue; 158 | } 159 | 160 | UIView *viewForElement = [UIAccessibilityElement viewContainingAccessibilityElement:element]; 161 | CGRect accessibilityFrame = [viewForElement.window convertRect:element.accessibilityFrame toView:viewForElement]; 162 | 163 | if ([viewForElement isTappableInRect:accessibilityFrame]) { 164 | return element; 165 | } else { 166 | matchingButOccludedElement = element; 167 | } 168 | } 169 | 170 | NSMutableArray *elementStack = [NSMutableArray arrayWithObject:self]; 171 | 172 | while (elementStack.count) { 173 | UIAccessibilityElement *element = [elementStack lastObject]; 174 | [elementStack removeLastObject]; 175 | 176 | BOOL elementMatches = matchBlock(element); 177 | 178 | if (elementMatches) { 179 | UIView *viewForElement = [UIAccessibilityElement viewContainingAccessibilityElement:element]; 180 | CGRect accessibilityFrame = [viewForElement.window convertRect:element.accessibilityFrame toView:viewForElement]; 181 | 182 | if ([viewForElement isTappableInRect:accessibilityFrame]) { 183 | return element; 184 | } else { 185 | matchingButOccludedElement = element; 186 | continue; 187 | } 188 | } 189 | 190 | // If the view is an accessibility container, and we didn't find a matching subview, 191 | // then check the actual accessibility elements 192 | NSInteger accessibilityElementCount = element.accessibilityElementCount; 193 | if (accessibilityElementCount == 0 || accessibilityElementCount == NSNotFound) { 194 | continue; 195 | } 196 | 197 | for (NSInteger accessibilityElementIndex = 0; accessibilityElementIndex < accessibilityElementCount; accessibilityElementIndex++) { 198 | UIAccessibilityElement *subelement = [element accessibilityElementAtIndex:accessibilityElementIndex]; 199 | 200 | if (subelement) { 201 | // Skip table view cell accessibility elements, they're handled below 202 | if ([subelement isKindOfClass:NSClassFromString(@"UITableViewCellAccessibilityElement")]) { 203 | continue; 204 | } 205 | 206 | [elementStack addObject:subelement]; 207 | } 208 | } 209 | } 210 | 211 | if (!matchingButOccludedElement) { 212 | if ([self isKindOfClass:[UITableView class]]) { 213 | UITableView *tableView = (UITableView *)self; 214 | 215 | // Because of a bug in [UITableView indexPathsForVisibleRows] http://openradar.appspot.com/radar?id=5191284490764288 216 | // We use [UITableView visibleCells] to determine the index path of the visible cells 217 | NSMutableArray *indexPathsForVisibleRows = [[NSMutableArray alloc] init]; 218 | [[tableView visibleCells] enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) { 219 | NSIndexPath *indexPath = [tableView indexPathForCell:cell]; 220 | if (indexPath) { 221 | [indexPathsForVisibleRows addObject:indexPath]; 222 | } 223 | }]; 224 | 225 | for (NSUInteger section = 0, numberOfSections = [tableView numberOfSections]; section < numberOfSections; section++) { 226 | for (NSUInteger row = 0, numberOfRows = [tableView numberOfRowsInSection:section]; row < numberOfRows; row++) { 227 | // Skip visible rows because they are already handled 228 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; 229 | if ([indexPathsForVisibleRows containsObject:indexPath]) { 230 | continue; 231 | } 232 | 233 | @autoreleasepool { 234 | // Get the cell directly from the dataSource because UITableView will only vend visible cells 235 | UITableViewCell *cell = [tableView.dataSource tableView:tableView cellForRowAtIndexPath:indexPath]; 236 | 237 | UIAccessibilityElement *element = [cell accessibilityElementMatchingBlock:matchBlock notHidden:NO]; 238 | 239 | // Remove the cell from the table view so that it doesn't stick around 240 | [cell removeFromSuperview]; 241 | 242 | // Skip this cell if it isn't the one we're looking for 243 | if (!element) { 244 | continue; 245 | } 246 | } 247 | 248 | // Scroll to the cell and wait for the animation to complete 249 | [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES]; 250 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.5, false); 251 | 252 | // Now try finding the element again 253 | return [self accessibilityElementMatchingBlock:matchBlock]; 254 | } 255 | } 256 | } else if ([self isKindOfClass:[UICollectionView class]]) { 257 | UICollectionView *collectionView = (UICollectionView *)self; 258 | 259 | NSArray *indexPathsForVisibleItems = [collectionView indexPathsForVisibleItems]; 260 | 261 | for (NSUInteger section = 0, numberOfSections = [collectionView numberOfSections]; section < numberOfSections; section++) { 262 | for (NSUInteger item = 0, numberOfItems = [collectionView numberOfItemsInSection:section]; item < numberOfItems; item++) { 263 | // Skip visible items because they are already handled 264 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; 265 | if ([indexPathsForVisibleItems containsObject:indexPath]) { 266 | continue; 267 | } 268 | 269 | @autoreleasepool { 270 | // Get the cell directly from the dataSource because UICollectionView will only vend visible cells 271 | UICollectionViewCell *cell = [collectionView.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; 272 | 273 | UIAccessibilityElement *element = [cell accessibilityElementMatchingBlock:matchBlock notHidden:NO]; 274 | 275 | // Remove the cell from the collection view so that it doesn't stick around 276 | [cell removeFromSuperview]; 277 | 278 | // Skip this cell if it isn't the one we're looking for 279 | // Sometimes we get cells with no size here which can cause an endless loop, so we ignore those 280 | if (!element || CGSizeEqualToSize(cell.frame.size, CGSizeZero)) { 281 | continue; 282 | } 283 | } 284 | 285 | // Scroll to the cell and wait for the animation to complete 286 | [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES]; 287 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.5, false); 288 | 289 | // Now try finding the element again 290 | return [self accessibilityElementMatchingBlock:matchBlock]; 291 | } 292 | } 293 | } 294 | } 295 | 296 | return matchingButOccludedElement; 297 | } 298 | 299 | - (UIView *)subviewWithClassNamePrefix:(NSString *)prefix; 300 | { 301 | NSArray *subviews = [self subviewsWithClassNamePrefix:prefix]; 302 | if ([subviews count] == 0) { 303 | return nil; 304 | } 305 | 306 | return subviews[0]; 307 | } 308 | 309 | - (NSArray *)subviewsWithClassNamePrefix:(NSString *)prefix; 310 | { 311 | NSMutableArray *result = [NSMutableArray array]; 312 | 313 | // Breadth-first population of matching subviews 314 | // First traverse the next level of subviews, adding matches. 315 | for (UIView *view in self.subviews) { 316 | if ([NSStringFromClass([view class]) hasPrefix:prefix]) { 317 | [result addObject:view]; 318 | } 319 | } 320 | 321 | // Now traverse the subviews of the subviews, adding matches. 322 | for (UIView *view in self.subviews) { 323 | NSArray *matchingSubviews = [view subviewsWithClassNamePrefix:prefix]; 324 | [result addObjectsFromArray:matchingSubviews]; 325 | } 326 | 327 | return result; 328 | } 329 | 330 | - (UIView *)subviewWithClassNameOrSuperClassNamePrefix:(NSString *)prefix; 331 | { 332 | NSArray *subviews = [self subviewsWithClassNameOrSuperClassNamePrefix:prefix]; 333 | if ([subviews count] == 0) { 334 | return nil; 335 | } 336 | 337 | return subviews[0]; 338 | } 339 | 340 | - (NSArray *)subviewsWithClassNameOrSuperClassNamePrefix:(NSString *)prefix; 341 | { 342 | NSMutableArray * result = [NSMutableArray array]; 343 | 344 | // Breadth-first population of matching subviews 345 | // First traverse the next level of subviews, adding matches 346 | for (UIView *view in self.subviews) { 347 | Class klass = [view class]; 348 | while (klass) { 349 | if ([NSStringFromClass(klass) hasPrefix:prefix]) { 350 | [result addObject:view]; 351 | break; 352 | } 353 | 354 | klass = [klass superclass]; 355 | } 356 | } 357 | 358 | // Now traverse the subviews of the subviews, adding matches 359 | for (UIView *view in self.subviews) { 360 | NSArray * matchingSubviews = [view subviewsWithClassNameOrSuperClassNamePrefix:prefix]; 361 | [result addObjectsFromArray:matchingSubviews]; 362 | } 363 | 364 | return result; 365 | } 366 | 367 | 368 | - (BOOL)isDescendantOfFirstResponder; 369 | { 370 | if ([self isFirstResponder]) { 371 | return YES; 372 | } 373 | return [self.superview isDescendantOfFirstResponder]; 374 | } 375 | 376 | - (void)flash; 377 | { 378 | UIColor *originalBackgroundColor = self.backgroundColor; 379 | for (NSUInteger i = 0; i < 5; i++) { 380 | self.backgroundColor = [UIColor yellowColor]; 381 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, .05, false); 382 | self.backgroundColor = [UIColor blueColor]; 383 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, .05, false); 384 | } 385 | self.backgroundColor = originalBackgroundColor; 386 | } 387 | 388 | - (void)tap; 389 | { 390 | CGPoint centerPoint = CGPointMake(self.frame.size.width * 0.5f, self.frame.size.height * 0.5f); 391 | 392 | [self tapAtPoint:centerPoint]; 393 | } 394 | 395 | - (void)tapAtPoint:(CGPoint)point; 396 | { 397 | // Web views don't handle touches in a normal fashion, but they do have a method we can call to tap them 398 | // This may not be necessary anymore. We didn't properly support controls that used gesture recognizers 399 | // when this was added, but we now do. It needs to be tested before we can get rid of it. 400 | id /*UIWebBrowserView*/ webBrowserView = nil; 401 | 402 | if ([NSStringFromClass([self class]) isEqual:@"UIWebBrowserView"]) { 403 | webBrowserView = self; 404 | } else if ([self isKindOfClass:[UIWebView class]]) { 405 | id webViewInternal = [self valueForKey:@"_internal"]; 406 | webBrowserView = [webViewInternal valueForKey:@"browserView"]; 407 | } 408 | 409 | if (webBrowserView) { 410 | [webBrowserView tapInteractionWithLocation:point]; 411 | return; 412 | } 413 | 414 | // Handle touches in the normal way for other views 415 | UITouch *touch = [[UITouch alloc] initAtPoint:point inView:self]; 416 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 417 | 418 | UIEvent *event = [self eventWithTouch:touch]; 419 | 420 | [[UIApplication sharedApplication] sendEvent:event]; 421 | 422 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 423 | [[UIApplication sharedApplication] sendEvent:event]; 424 | 425 | // Dispatching the event doesn't actually update the first responder, so fake it 426 | if ([touch.view isDescendantOfView:self] && [self canBecomeFirstResponder]) { 427 | [self becomeFirstResponder]; 428 | } 429 | 430 | } 431 | 432 | - (void)twoFingerTapAtPoint:(CGPoint)point { 433 | CGPoint finger1 = CGPointMake(point.x - kTwoFingerConstantWidth, point.y - kTwoFingerConstantWidth); 434 | CGPoint finger2 = CGPointMake(point.x + kTwoFingerConstantWidth, point.y + kTwoFingerConstantWidth); 435 | UITouch *touch1 = [[UITouch alloc] initAtPoint:finger1 inView:self]; 436 | UITouch *touch2 = [[UITouch alloc] initAtPoint:finger2 inView:self]; 437 | [touch1 setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 438 | [touch2 setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 439 | 440 | UIEvent *event = [self eventWithTouches:@[touch1, touch2]]; 441 | [[UIApplication sharedApplication] sendEvent:event]; 442 | 443 | [touch1 setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 444 | [touch2 setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 445 | 446 | [[UIApplication sharedApplication] sendEvent:event]; 447 | } 448 | 449 | #define DRAG_TOUCH_DELAY 0.01 450 | 451 | - (void)longPressAtPoint:(CGPoint)point duration:(NSTimeInterval)duration 452 | { 453 | UITouch *touch = [[UITouch alloc] initAtPoint:point inView:self]; 454 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 455 | 456 | UIEvent *eventDown = [self eventWithTouch:touch]; 457 | [[UIApplication sharedApplication] sendEvent:eventDown]; 458 | 459 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, DRAG_TOUCH_DELAY, false); 460 | 461 | for (NSTimeInterval timeSpent = DRAG_TOUCH_DELAY; timeSpent < duration; timeSpent += DRAG_TOUCH_DELAY) 462 | { 463 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; 464 | 465 | UIEvent *eventStillDown = [self eventWithTouch:touch]; 466 | [[UIApplication sharedApplication] sendEvent:eventStillDown]; 467 | 468 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, DRAG_TOUCH_DELAY, false); 469 | } 470 | 471 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 472 | UIEvent *eventUp = [self eventWithTouch:touch]; 473 | [[UIApplication sharedApplication] sendEvent:eventUp]; 474 | 475 | // Dispatching the event doesn't actually update the first responder, so fake it 476 | if ([touch.view isDescendantOfView:self] && [self canBecomeFirstResponder]) { 477 | [self becomeFirstResponder]; 478 | } 479 | 480 | } 481 | 482 | - (void)dragFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint; 483 | { 484 | [self dragFromPoint:startPoint toPoint:endPoint steps:3]; 485 | } 486 | 487 | 488 | - (void)dragFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint steps:(NSUInteger)stepCount; 489 | { 490 | KIFDisplacement displacement = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y); 491 | [self dragFromPoint:startPoint displacement:displacement steps:stepCount]; 492 | } 493 | 494 | - (void)dragFromPoint:(CGPoint)startPoint displacement:(KIFDisplacement)displacement steps:(NSUInteger)stepCount; 495 | { 496 | CGPoint endPoint = CGPointMake(startPoint.x + displacement.x, startPoint.y + displacement.y); 497 | NSArray *path = [self pointsFromStartPoint:startPoint toPoint:endPoint steps:stepCount]; 498 | [self dragPointsAlongPaths:@[path]]; 499 | } 500 | 501 | - (void)dragAlongPathWithPoints:(CGPoint *)points count:(NSInteger)count; 502 | { 503 | // convert point array into NSArray with NSValue 504 | NSMutableArray *array = [NSMutableArray array]; 505 | for (int i = 0; i < count; i++) 506 | { 507 | [array addObject:[NSValue valueWithCGPoint:points[i]]]; 508 | } 509 | [self dragPointsAlongPaths:@[[array copy]]]; 510 | } 511 | 512 | - (void)dragPointsAlongPaths:(NSArray *)arrayOfPaths { 513 | // must have at least one path, and each path must have the same number of points 514 | if (arrayOfPaths.count == 0) 515 | { 516 | return; 517 | } 518 | 519 | // all paths must have similar number of points 520 | NSUInteger pointsInPath = [arrayOfPaths[0] count]; 521 | for (NSArray *path in arrayOfPaths) 522 | { 523 | if (path.count != pointsInPath) 524 | { 525 | return; 526 | } 527 | } 528 | 529 | NSMutableArray *touches = [NSMutableArray array]; 530 | 531 | for (NSUInteger pointIndex = 0; pointIndex < pointsInPath; pointIndex++) { 532 | // create initial touch event and send touch down event 533 | if (pointIndex == 0) 534 | { 535 | for (NSArray *path in arrayOfPaths) 536 | { 537 | CGPoint point = [path[pointIndex] CGPointValue]; 538 | UITouch *touch = [[UITouch alloc] initAtPoint:point inView:self]; 539 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseBegan]; 540 | [touches addObject:touch]; 541 | } 542 | UIEvent *eventDown = [self eventWithTouches:[NSArray arrayWithArray:touches]]; 543 | [[UIApplication sharedApplication] sendEvent:eventDown]; 544 | 545 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, DRAG_TOUCH_DELAY, false); 546 | } 547 | else 548 | { 549 | UITouch *touch; 550 | for (NSUInteger pathIndex = 0; pathIndex < arrayOfPaths.count; pathIndex++) 551 | { 552 | NSArray *path = arrayOfPaths[pathIndex]; 553 | CGPoint point = [path[pointIndex] CGPointValue]; 554 | touch = touches[pathIndex]; 555 | [touch setLocationInWindow:[self.window convertPoint:point fromView:self]]; 556 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseMoved]; 557 | } 558 | UIEvent *event = [self eventWithTouches:[NSArray arrayWithArray:touches]]; 559 | [[UIApplication sharedApplication] sendEvent:event]; 560 | 561 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, DRAG_TOUCH_DELAY, false); 562 | 563 | // The last point needs to also send a phase ended touch. 564 | if (pointIndex == pointsInPath - 1) { 565 | for (UITouch * touch in touches) { 566 | [touch setPhaseAndUpdateTimestamp:UITouchPhaseEnded]; 567 | UIEvent *eventUp = [self eventWithTouch:touch]; 568 | [[UIApplication sharedApplication] sendEvent:eventUp]; 569 | 570 | } 571 | 572 | } 573 | } 574 | } 575 | 576 | // Dispatching the event doesn't actually update the first responder, so fake it 577 | if ([touches[0] view] == self && [self canBecomeFirstResponder]) { 578 | [self becomeFirstResponder]; 579 | } 580 | 581 | while (UIApplicationCurrentRunMode != kCFRunLoopDefaultMode) { 582 | CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.1, false); 583 | } 584 | } 585 | 586 | - (void)twoFingerPanFromPoint:(CGPoint)startPoint toPoint:(CGPoint)toPoint steps:(NSUInteger)stepCount { 587 | //estimate the first finger to be diagonally up and left from the center 588 | CGPoint finger1Start = CGPointMake(startPoint.x - kTwoFingerConstantWidth, 589 | startPoint.y - kTwoFingerConstantWidth); 590 | CGPoint finger1End = CGPointMake(toPoint.x - kTwoFingerConstantWidth, 591 | toPoint.y - kTwoFingerConstantWidth); 592 | //estimate the second finger to be diagonally down and right from the center 593 | CGPoint finger2Start = CGPointMake(startPoint.x + kTwoFingerConstantWidth, 594 | startPoint.y + kTwoFingerConstantWidth); 595 | CGPoint finger2End = CGPointMake(toPoint.x + kTwoFingerConstantWidth, 596 | toPoint.y + kTwoFingerConstantWidth); 597 | NSArray *finger1Path = [self pointsFromStartPoint:finger1Start toPoint:finger1End steps:stepCount]; 598 | NSArray *finger2Path = [self pointsFromStartPoint:finger2Start toPoint:finger2End steps:stepCount]; 599 | NSArray *paths = @[finger1Path, finger2Path]; 600 | 601 | [self dragPointsAlongPaths:paths]; 602 | } 603 | 604 | - (void)pinchAtPoint:(CGPoint)centerPoint distance:(CGFloat)distance steps:(NSUInteger)stepCount { 605 | //estimate the first finger to be on the left 606 | CGPoint finger1Start = CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance, centerPoint.y); 607 | CGPoint finger1End = CGPointMake(centerPoint.x - kTwoFingerConstantWidth, centerPoint.y); 608 | //estimate the second finger to be on the right 609 | CGPoint finger2Start = CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance, centerPoint.y); 610 | CGPoint finger2End = CGPointMake(centerPoint.x + kTwoFingerConstantWidth, centerPoint.y); 611 | NSArray *finger1Path = [self pointsFromStartPoint:finger1Start toPoint:finger1End steps:stepCount]; 612 | NSArray *finger2Path = [self pointsFromStartPoint:finger2Start toPoint:finger2End steps:stepCount]; 613 | NSArray *paths = @[finger1Path, finger2Path]; 614 | 615 | [self dragPointsAlongPaths:paths]; 616 | } 617 | 618 | - (void)zoomAtPoint:(CGPoint)centerPoint distance:(CGFloat)distance steps:(NSUInteger)stepCount { 619 | //estimate the first finger to be on the left 620 | CGPoint finger1Start = CGPointMake(centerPoint.x - kTwoFingerConstantWidth, centerPoint.y); 621 | CGPoint finger1End = CGPointMake(centerPoint.x - kTwoFingerConstantWidth - distance, centerPoint.y); 622 | //estimate the second finger to be on the right 623 | CGPoint finger2Start = CGPointMake(centerPoint.x + kTwoFingerConstantWidth, centerPoint.y); 624 | CGPoint finger2End = CGPointMake(centerPoint.x + kTwoFingerConstantWidth + distance, centerPoint.y); 625 | NSArray *finger1Path = [self pointsFromStartPoint:finger1Start toPoint:finger1End steps:stepCount]; 626 | NSArray *finger2Path = [self pointsFromStartPoint:finger2Start toPoint:finger2End steps:stepCount]; 627 | NSArray *paths = @[finger1Path, finger2Path]; 628 | 629 | [self dragPointsAlongPaths:paths]; 630 | } 631 | 632 | - (void)twoFingerRotateAtPoint:(CGPoint)centerPoint angle:(CGFloat)angleInDegrees { 633 | NSInteger stepCount = ABS(angleInDegrees)/2; // very rough approximation. 90deg = ~45 steps, 360 deg = ~180 steps 634 | CGFloat radius = kTwoFingerConstantWidth*2; 635 | double angleInRadians = KIFDegreesToRadians(angleInDegrees); 636 | 637 | NSMutableArray *finger1Path = [NSMutableArray array]; 638 | NSMutableArray *finger2Path = [NSMutableArray array]; 639 | for (NSUInteger i = 0; i < stepCount; i++) { 640 | double currentAngle = 0; 641 | if (i == stepCount - 1) { 642 | currentAngle = angleInRadians; // do not interpolate for the last step for maximum accuracy 643 | } 644 | else { 645 | double interpolation = i/(double)stepCount; 646 | currentAngle = interpolation * angleInRadians; 647 | } 648 | // interpolate betwen 0 and the target rotation 649 | CGPoint offset1 = CGPointMake(radius * cos(currentAngle), radius * sin(currentAngle)); 650 | CGPoint offset2 = CGPointMake(-offset1.x, -offset1.y); // second finger is just opposite of the first 651 | 652 | CGPoint finger1 = CGPointMake(centerPoint.x + offset1.x, centerPoint.y + offset1.y); 653 | CGPoint finger2 = CGPointMake(centerPoint.x + offset2.x, centerPoint.y + offset2.y); 654 | 655 | [finger1Path addObject:[NSValue valueWithCGPoint:finger1]]; 656 | [finger2Path addObject:[NSValue valueWithCGPoint:finger2]]; 657 | } 658 | [self dragPointsAlongPaths:@[[finger1Path copy], [finger2Path copy]]]; 659 | } 660 | 661 | - (NSArray *)pointsFromStartPoint:(CGPoint)startPoint toPoint:(CGPoint)toPoint steps:(NSUInteger)stepCount { 662 | 663 | CGPoint displacement = CGPointMake(toPoint.x - startPoint.x, toPoint.y - startPoint.y); 664 | NSMutableArray *points = [NSMutableArray array]; 665 | 666 | for (NSUInteger i = 0; i < stepCount; i++) { 667 | CGFloat progress = ((CGFloat)i)/(stepCount - 1); 668 | CGPoint point = CGPointMake(startPoint.x + (progress * displacement.x), 669 | startPoint.y + (progress * displacement.y)); 670 | [points addObject:[NSValue valueWithCGPoint:point]]; 671 | } 672 | return [NSArray arrayWithArray:points]; 673 | } 674 | 675 | - (BOOL)isProbablyTappable 676 | { 677 | // There are some issues with the tappability check in UIWebViews, so if the view is a UIWebView we will just skip the check. 678 | return [NSStringFromClass([self class]) isEqualToString:@"UIWebBrowserView"] || self.isTappable; 679 | } 680 | 681 | // Is this view currently on screen? 682 | - (BOOL)isTappable; 683 | { 684 | return ([self hasTapGestureRecognizer] || 685 | [self isTappableInRect:self.bounds]); 686 | } 687 | 688 | - (BOOL)hasTapGestureRecognizer 689 | { 690 | __block BOOL hasTapGestureRecognizer = NO; 691 | 692 | [self.gestureRecognizers enumerateObjectsUsingBlock:^(id obj, 693 | NSUInteger idx, 694 | BOOL *stop) { 695 | if ([obj isKindOfClass:[UITapGestureRecognizer class]]) { 696 | hasTapGestureRecognizer = YES; 697 | 698 | if (stop != NULL) { 699 | *stop = YES; 700 | } 701 | } 702 | }]; 703 | 704 | return hasTapGestureRecognizer; 705 | } 706 | 707 | - (BOOL)isTappableInRect:(CGRect)rect; 708 | { 709 | CGPoint tappablePoint = [self tappablePointInRect:rect]; 710 | 711 | return !isnan(tappablePoint.x); 712 | } 713 | 714 | - (BOOL)isTappableWithHitTestResultView:(UIView *)hitView; 715 | { 716 | // Special case for UIControls, which may have subviews which don't respond to -hitTest:, 717 | // but which are tappable. In this case the hit view will be the containing 718 | // UIControl, and it will forward the tap to the appropriate subview. 719 | // This applies with UISegmentedControl which contains UISegment views (a private UIView 720 | // representing a single segment). 721 | if ([hitView isKindOfClass:[UIControl class]] && [self isDescendantOfView:hitView]) { 722 | return YES; 723 | } 724 | 725 | // Button views in the nav bar (a private class derived from UINavigationItemView), do not return 726 | // themselves in a -hitTest:. Instead they return the nav bar. 727 | if ([hitView isKindOfClass:[UINavigationBar class]] && [self isNavigationItemView] && [self isDescendantOfView:hitView]) { 728 | return YES; 729 | } 730 | 731 | return [hitView isDescendantOfView:self]; 732 | } 733 | 734 | - (CGPoint)tappablePointInRect:(CGRect)rect; 735 | { 736 | // Start at the top and recurse down 737 | CGRect frame = [self.window convertRect:rect fromView:self]; 738 | 739 | UIView *hitView = nil; 740 | CGPoint tapPoint = CGPointZero; 741 | 742 | // Mid point 743 | tapPoint = CGPointCenteredInRect(frame); 744 | hitView = [self.window hitTest:tapPoint withEvent:nil]; 745 | if ([self isTappableWithHitTestResultView:hitView]) { 746 | return [self.window convertPoint:tapPoint toView:self]; 747 | } 748 | 749 | // Top left 750 | tapPoint = CGPointMake(frame.origin.x + 1.0f, frame.origin.y + 1.0f); 751 | hitView = [self.window hitTest:tapPoint withEvent:nil]; 752 | if ([self isTappableWithHitTestResultView:hitView]) { 753 | return [self.window convertPoint:tapPoint toView:self]; 754 | } 755 | 756 | // Top right 757 | tapPoint = CGPointMake(frame.origin.x + frame.size.width - 1.0f, frame.origin.y + 1.0f); 758 | hitView = [self.window hitTest:tapPoint withEvent:nil]; 759 | if ([self isTappableWithHitTestResultView:hitView]) { 760 | return [self.window convertPoint:tapPoint toView:self]; 761 | } 762 | 763 | // Bottom left 764 | tapPoint = CGPointMake(frame.origin.x + 1.0f, frame.origin.y + frame.size.height - 1.0f); 765 | hitView = [self.window hitTest:tapPoint withEvent:nil]; 766 | if ([self isTappableWithHitTestResultView:hitView]) { 767 | return [self.window convertPoint:tapPoint toView:self]; 768 | } 769 | 770 | // Bottom right 771 | tapPoint = CGPointMake(frame.origin.x + frame.size.width - 1.0f, frame.origin.y + frame.size.height - 1.0f); 772 | hitView = [self.window hitTest:tapPoint withEvent:nil]; 773 | if ([self isTappableWithHitTestResultView:hitView]) { 774 | return [self.window convertPoint:tapPoint toView:self]; 775 | } 776 | 777 | return CGPointMake(NAN, NAN); 778 | } 779 | 780 | - (UIEvent *)eventWithTouches:(NSArray *)touches 781 | { 782 | // _touchesEvent is a private selector, interface is exposed in UIApplication(KIFAdditionsPrivate) 783 | UIEvent *event = [[UIApplication sharedApplication] _touchesEvent]; 784 | 785 | [event _clearTouches]; 786 | [event kif_setEventWithTouches:touches]; 787 | 788 | for (UITouch *aTouch in touches) { 789 | [event _addTouch:aTouch forDelayedDelivery:NO]; 790 | } 791 | 792 | return event; 793 | } 794 | 795 | - (UIEvent *)eventWithTouch:(UITouch *)touch; 796 | { 797 | NSArray *touches = touch ? @[touch] : nil; 798 | return [self eventWithTouches:touches]; 799 | } 800 | 801 | - (BOOL)isUserInteractionActuallyEnabled; 802 | { 803 | BOOL isUserInteractionEnabled = self.userInteractionEnabled; 804 | 805 | // Navigation item views don't have user interaction enabled, but their parent nav bar does and will forward the event 806 | if (!isUserInteractionEnabled && [self isNavigationItemView]) { 807 | // If this view is inside a nav bar, and the nav bar is enabled, then consider it enabled 808 | UIView *navBar = [self superview]; 809 | while (navBar && ![navBar isKindOfClass:[UINavigationBar class]]) { 810 | navBar = [navBar superview]; 811 | } 812 | if (navBar && navBar.userInteractionEnabled) { 813 | isUserInteractionEnabled = YES; 814 | } 815 | } 816 | 817 | // UIActionsheet Buttons have UIButtonLabels with userInteractionEnabled=NO inside, 818 | // grab the superview UINavigationButton instead. 819 | if (!isUserInteractionEnabled && [self isKindOfClass:NSClassFromString(@"UIButtonLabel")]) { 820 | UIView *button = [self superview]; 821 | while (button && ![button isKindOfClass:NSClassFromString(@"UINavigationButton")]) { 822 | button = [button superview]; 823 | } 824 | if (button && button.userInteractionEnabled) { 825 | isUserInteractionEnabled = YES; 826 | } 827 | } 828 | 829 | // Somtimes views are inside a UIControl and don't have user interaction enabled. 830 | // Walk up the hierarchary evaluating the parent UIControl subclass and use that instead. 831 | if (!isUserInteractionEnabled && [self.superview isKindOfClass:[UIControl class]]) { 832 | // If this view is inside a UIControl, and it is enabled, then consider the view enabled 833 | UIControl *control = (UIControl *)[self superview]; 834 | while (control && [control isKindOfClass:[UIControl class]]) { 835 | if (control.isUserInteractionEnabled) { 836 | isUserInteractionEnabled = YES; 837 | break; 838 | } 839 | control = (UIControl *)[control superview]; 840 | } 841 | } 842 | 843 | return isUserInteractionEnabled; 844 | } 845 | 846 | - (BOOL)isNavigationItemView; 847 | { 848 | return [self isKindOfClass:NSClassFromString(@"UINavigationItemView")] || [self isKindOfClass:NSClassFromString(@"_UINavigationBarBackIndicatorView")]; 849 | } 850 | 851 | - (UIWindow *)windowOrIdentityWindow 852 | { 853 | if (CGAffineTransformIsIdentity(self.window.transform)) { 854 | return self.window; 855 | } 856 | 857 | for (UIWindow *window in [[UIApplication sharedApplication] windowsWithKeyWindow]) { 858 | if (CGAffineTransformIsIdentity(window.transform)) { 859 | return window; 860 | } 861 | } 862 | 863 | return nil; 864 | } 865 | 866 | - (BOOL)isVisibleInViewHierarchy 867 | { 868 | __block BOOL result = YES; 869 | [self performBlockOnAscendentViews:^(UIView *view, BOOL *stop) { 870 | if (view.isHidden) { 871 | result = NO; 872 | if (stop != NULL) { 873 | *stop = YES; 874 | } 875 | } 876 | }]; 877 | return result; 878 | } 879 | 880 | - (void)performBlockOnDescendentViews:(void (^)(UIView *view, BOOL *stop))block 881 | { 882 | BOOL stop = NO; 883 | [self performBlockOnDescendentViews:block stop:&stop]; 884 | } 885 | 886 | - (void)performBlockOnDescendentViews:(void (^)(UIView *view, BOOL *stop))block stop:(BOOL *)stop 887 | { 888 | block(self, stop); 889 | if (*stop) { 890 | return; 891 | } 892 | 893 | for (UIView *view in self.subviews) { 894 | [view performBlockOnDescendentViews:block stop:stop]; 895 | if (*stop) { 896 | return; 897 | } 898 | } 899 | } 900 | 901 | - (void)performBlockOnAscendentViews:(void (^)(UIView *view, BOOL *stop))block 902 | { 903 | BOOL stop = NO; 904 | UIView *checkedView = self; 905 | while(checkedView && stop == NO) { 906 | block(checkedView, &stop); 907 | checkedView = checkedView.superview; 908 | } 909 | } 910 | 911 | 912 | @end 913 | --------------------------------------------------------------------------------