2012-12-30 75 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的,將可能引起排斥反應,如果使用的測試套件外...

相關問題