2012-12-30 45 views
13

编辑:更新,使问题更加明显UITapGestureRecognizer编程触发一个水龙头在我看来

编辑2:制造问题更准确的到我的现实世界的问题。我实际上希望采取行动,如果他们轻触任何地方,除了在屏幕上的文本字段。因此,我不能简单地听取文本框内的事件,我需要知道他们是否在视图中的任何地方点击

我编写单元测试断言,当一个手势识别器识别出我的观点的特定坐标内的自来水一定采取行动。我想知道是否可以通过编程方式创建将由UITapGestureRecognizer处理的触摸(在特定坐标处)。 我试图在单元测试期间模拟用户交互。

的UITapGestureRecognizer是在Interface Builder配置

//MYUIViewControllerSubclass.m 

-(IBAction)viewTapped:(UITapGestureRecognizer*)gesture { 
    CGPoint tapPoint = [gesture locationInView:self.view]; 
    if (!CGRectContainsPoint(self.textField, tapPoint)) { 
    // Do stuff if they tapped anywhere outside the text field 
    } 
} 

//MYUIViewControllerSubclassTests.m 
//What I'm trying to accomplish in my unit test: 

-(void)testThatTappingInNoteworthyAreaTriggersStuff { 
    // Create fake gesture recognizer and ViewController 
    MYUIViewControllerSubclass *vc = [[MYUIViewControllersSubclass alloc] init]; 
    UITapGestureRecognizer *tgr = [[UITapGestureRecognizer initWithView: vc.view]; 

    // What I want to do: 
    [[ Simulate A Tap anywhere outside vc.textField ]] 
    [[ Assert that "Stuff" occured ]] 
} 
+1

有什么问题吗? – tiguero

+0

更新的问题 –

+1

将录制一个特定的视图或按钮就足够了? – tiguero

回答

9

我认为你有多种选择这里:

  1. 可能是最简单的将是一个push event action发送到您的看法,但我不不要以为你想要选择点击动作发生的位置,就是你真正想要的东西。

    [yourView sendActionsForControlEvents:UIControlEventTouchUpInside];

  2. 你可以使用UI automation tool所提供的XCode仪器。这blog解释了如何使用脚本自动化您的UI测试。

  3. 还有这个solution也解释了如何在iPhone上综合触摸事件,但确保你只用于单元测试。这听起来更像是对我的破解,如果以上两点不能满足您的需求,我会认为此解决方案是最后的解决方案。

+0

也看看[MonkeyTalk](http://cloudmonkeymobile.com/monkeytalk) –

+14

选项1无效。 “sendActionsForControlEvents”仅适用于UIControl对象;不UIView的。 –

1

你试图做的是很辛苦(但不是完全不可能的),同时保持了(有iTunes)法律路径。


让我先起草正确方式;

这样做的正确的出路就是用UIAutomation。 UIAutomation完全符合你的要求,它模拟了各种测试的用户行为。


现在很难过;

到自己的问题归结为一个问题是实例化一个新的UIEvent。 (Un)幸运的是,由于安全原因明显,UIKit不提供任何此类事件的构造函数。然而,过去有效的解决方法不能确定它们是否仍然有效。

看看Matt Galagher真棒博客起草solution on how to synthesise touch events

1

我正面临着同样的问题,试图模拟表格单元上的一个龙头,以自动化测试视图控制器,该控制器处理在表格上敲击。

该控制器具有创建如下面的私人UITapGestureRecognizer:

gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 
    action:@selector(didRecognizeTapOnTableView)]; 

单元测试应该模拟的触摸,使得因为它起源于用户交互gestureRecognizer将触发动作。

没有提出的解决方案在这种情况下工作,所以我解决了它装饰UITapGestureRecognizer,伪造控制器调用的确切方法。所以我添加了一个“performTap”方法,以一种控制器本身不知道动作起源于何处的方式来调用动作。这样,我就可以独立于手势识别器为控制器创建一个测试单元,只需要触发该操作。

这是我的类别,希望它可以帮助别人。

CGPoint mockTappedPoint; 
UIView *mockTappedView = nil; 
id mockTarget = nil; 
SEL mockAction; 

@implementation UITapGestureRecognizer (MockedGesture) 
-(id)initWithTarget:(id)target action:(SEL)action { 
    mockTarget = target; 
    mockAction = action; 
    return [super initWithTarget:target action:action]; 
    // code above calls UIGestureRecognizer init..., but it doesn't matters 
} 
-(UIView *)view { 
    return mockTappedView; 
} 
-(CGPoint)locationInView:(UIView *)view { 
    return [view convertPoint:mockTappedPoint fromView:mockTappedView]; 
} 
-(UIGestureRecognizerState)state { 
    return UIGestureRecognizerStateEnded; 
} 
-(void)performTapWithView:(UIView *)view andPoint:(CGPoint)point { 
    mockTappedView = view; 
    mockTappedPoint = point; 
    [mockTarget performSelector:mockAction]; 
} 

@end 
+1

这是一个绝妙的主意。 不幸的是,无法在类别中添加成员变量。这些变量将是有效的静态变量,当您的视图中有> 1个手势识别器时,您将无法依赖它们。如果Apple有内部使用它们的情况下,会出现其他字段等情况。 该问题的解决方案是使用关联的对象来伪装属性。如果有兴趣,我会发布该解决方案。 – tooluser

2

好吧,我已经把上面的内容变成了一个可行的范畴。

有趣的位:

  • 分类不能添加成员变量。您添加的任何内容对于该类都是静态的,因此被Apple的许多UITapGestureRecognizers破坏。
    • 因此,使用associated_object使魔术发生。
    • NSValue用于存储非对象
  • 苹果init方法包含重要的配置的逻辑;我们可以猜测哪一个设置(水龙头,触摸次数,还有什么?
    • 数不过这注定了。所以,我们在保留了嘲笑我们的init方法拌和。

头文件是微不足道的;这里的执行

#import "UITapGestureRecognizer+Spec.h" 
#import "objc/runtime.h" 

/* 
* With great contributions from Matt Gallagher (http://www.cocoawithlove.com/2008/10/synthesizing-touch-event-on-iphone.html) 
* And Glauco Aquino (http://stackoverflow.com/users/2276639/glauco-aquino) 
* And Codeshaker (http://codeshaker.blogspot.com/2012/01/calling-original-overridden-method-from.html) 
*/ 
@interface UITapGestureRecognizer (SpecPrivate) 

@property (strong, nonatomic, readwrite) UIView *mockTappedView_; 
@property (assign, nonatomic, readwrite) CGPoint mockTappedPoint_; 
@property (strong, nonatomic, readwrite) id mockTarget_; 
@property (assign, nonatomic, readwrite) SEL mockAction_; 

@end 

NSString const *MockTappedViewKey = @"MockTappedViewKey"; 
NSString const *MockTappedPointKey = @"MockTappedPointKey"; 
NSString const *MockTargetKey = @"MockTargetKey"; 
NSString const *MockActionKey = @"MockActionKey"; 

@implementation UITapGestureRecognizer (Spec) 

// It is necessary to call the original init method; super does not set appropriate variables. 
// (eg, number of taps, number of touches, gods know what else) 
// Swizzle our own method into its place. Note that Apple misspells 'swizzle' as 'exchangeImplementation'. 
+(void)load { 
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithTarget:action:)), 
            class_getInstanceMethod(self, @selector(initWithMockTarget:mockAction:))); 
} 

-(id)initWithMockTarget:(id)target mockAction:(SEL)action { 
    self = [self initWithMockTarget:target mockAction:action]; 
    self.mockTarget_ = target; 
    self.mockAction_ = action; 
    self.mockTappedView_ = nil; 
    return self; 
} 

-(UIView *)view { 
    return self.mockTappedView_; 
} 

-(CGPoint)locationInView:(UIView *)view { 
    return [view convertPoint:self.mockTappedPoint_ fromView:self.mockTappedView_]; 
} 

//-(UIGestureRecognizerState)state { 
// return UIGestureRecognizerStateEnded; 
//} 

-(void)performTapWithView:(UIView *)view andPoint:(CGPoint)point { 
    self.mockTappedView_ = view; 
    self.mockTappedPoint_ = point; 

// warning because a leak is possible because the compiler can't tell whether this method 
// adheres to standard naming conventions and make the right behavioral decision. Suppress it. 
#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" 
    [self.mockTarget_ performSelector:self.mockAction_]; 
#pragma clang diagnostic pop 

} 

# pragma mark - Who says we can't add members in a category? 

- (void)setMockTappedView_:(UIView *)mockTappedView { 
    objc_setAssociatedObject(self, &MockTappedViewKey, mockTappedView, OBJC_ASSOCIATION_ASSIGN); 
} 

-(UIView *)mockTappedView_ { 
    return objc_getAssociatedObject(self, &MockTappedViewKey); 
} 

- (void)setMockTappedPoint_:(CGPoint)mockTappedPoint { 
    objc_setAssociatedObject(self, &MockTappedPointKey, [NSValue value:&mockTappedPoint withObjCType:@encode(CGPoint)], OBJC_ASSOCIATION_COPY); 
} 

- (CGPoint)mockTappedPoint_ { 
    NSValue *value = objc_getAssociatedObject(self, &MockTappedPointKey); 
    CGPoint aPoint; 
    [value getValue:&aPoint]; 
    return aPoint; 
} 

- (void)setMockTarget_:(id)mockTarget { 
    objc_setAssociatedObject(self, &MockTargetKey, mockTarget, OBJC_ASSOCIATION_ASSIGN); 
} 

- (id)mockTarget_ { 
    return objc_getAssociatedObject(self, &MockTargetKey); 
} 

- (void)setMockAction_:(SEL)mockAction { 
    objc_setAssociatedObject(self, &MockActionKey, NSStringFromSelector(mockAction), OBJC_ASSOCIATION_COPY); 
} 

- (SEL)mockAction_ { 
    NSString *selectorString = objc_getAssociatedObject(self, &MockActionKey); 
    return NSSelectorFromString(selectorString); 
} 

@end 
+0

我可以在AppStore应用程序上使用它? –

+1

打我。试一试!但是任何你需要在应用程序中做的事情,你应该能够做到这一点,没有这些额外的伪装东西。 – tooluser

+0

现在这个工作还在吗?我试过但没有回应 – hoangpx

2
CGPoint tapPoint = [gesture locationInView:self.view]; 

应该

CGPoint tapPoint = [gesture locationInView:gesture.view]; 

因为cgpoint应该从什么地方手势目标,而不是试图去猜测其中的观点它在

3

如果在测试中使用,你可以使用名为SpecTools测试库,所有帮助检索这一点,更或者使用它的代码直接:

// Return type alias 
public typealias TargetActionInfo = [(target: AnyObject, action: Selector)] 

// UIGestureRecognizer extension 
extension UIGestureRecognizer { 

    // MARK: Retrieving targets from gesture recognizers 

    /// Returns all actions and selectors for a gesture recognizer 
    /// This method uses private API's and will most likely cause your app to be rejected if used outside of your test target 
    /// - Returns: [(target: AnyObject, action: Selector)] Array of action/selector tuples 
    public func getTargetInfo() -> TargetActionInfo { 
     var targetsInfo: TargetActionInfo = [] 

     if let targets = self.value(forKeyPath: "_targets") as? [NSObject] { 
      for target in targets { 
       // Getting selector by parsing the description string of a UIGestureRecognizerTarget 
       let selectorString = String.init(describing: target).components(separatedBy: ", ").first!.replacingOccurrences(of: "(action=", with: "") 
       let selector = NSSelectorFromString(selectorString) 

       // Getting target from iVars 
       let targetActionPairClass: AnyClass = NSClassFromString("UIGestureRecognizerTarget")! 
       let targetIvar: Ivar = class_getInstanceVariable(targetActionPairClass, "_target") 
       let targetObject: AnyObject = object_getIvar(target, targetIvar) as! AnyObject 

       targetsInfo.append((target: targetObject, action: selector)) 
      } 
     } 

     return targetsInfo 
    } 

    /// Executes all targets on a gesture recognizer 
    public func execute() { 
     let targetsInfo = self.getTargetInfo() 
     for info in targetsInfo { 
      info.target.performSelector(onMainThread: info.action, with: nil, waitUntilDone: true) 
     } 
    } 

} 

两个,图书馆以及片断使用私有API的,将可能引起排斥反应,如果使用的测试套件外...

相关问题