2012-12-30 42 views
13

संपादित करें मेरे विचार में एक नल को गति प्रदान: मेड प्रश्न और अधिक सटीक मेरी वास्तविक दुनिया समस्या के लिए: प्रश्न अधिकUITapGestureRecognizer प्रोग्राम

संपादित 2 स्पष्ट बनाने के लिए अपडेट किया गया। मैं वास्तव में कार्रवाई करने की तलाश में हूं यदि वे ऑन-स्क्रीन टेक्स्ट-फ़ील्ड में कहीं भी टैप करते हैं। इस प्रकार, मैं केवल टेक्स्टफील्ड के भीतर की घटनाओं को नहीं सुन सकता, मुझे यह जानने की ज़रूरत है कि क्या उन्होंने को दृश्य में देखा है या नहीं।

मैं इकाई परीक्षण लिख रहा हूँ बात पर जोर देना है कि विशेष प्रकार की गतिविधि है जब एक इशारा पहचानकर्ता मेरे विचार के कुछ निर्देशांक के भीतर एक नल पहचानता लिया जाता है। मैं जानना चाहता हूं कि क्या मैं प्रोग्रामेटिक रूप से एक स्पर्श (विशिष्ट निर्देशांक पर) बना सकता हूं जिसे UITapGestureRecognizer द्वारा संभाला जाएगा। मैं एक इकाई परीक्षण के दौरान उपयोगकर्ता बातचीत को अनुकरण करने का प्रयास कर रहा हूं।

UITapGestureRecognizer इंटरफ़ेस बिल्डर में कॉन्फ़िगर किया गया है

//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 का उपयोग कर सकते हैं जो एक्सकोड उपकरणों के साथ प्रदान किया जाता है। यह blog बताता है कि स्क्रिप्ट के साथ अपने यूआई परीक्षणों को स्वचालित कैसे करें।

  3. यह solution भी है जो आईफोन पर टच इवेंट को संश्लेषित करने का तरीका बताता है लेकिन यह सुनिश्चित करता है कि आप केवल यूनिट परीक्षणों के लिए उपयोग करें। यह मेरे लिए हैक की तरह लगता है और यदि मैं पिछले दो बिंदुओं को आपकी आवश्यकता पूरी नहीं करता हूं तो मैं इस समाधान को अंतिम उपाय के रूप में मानूंगा।

+0

इसके अलावा [MonkeyTalk] (http://cloudmonkeymobile.com/monkeytalk) पर एक नज़र डालें –

+14

विकल्प 1 मान्य नहीं है। "sendActionsForControlEvents" केवल UIControl ऑब्जेक्ट्स के लिए काम करता है; UIView नहीं। –

1

(iTunes-) कानूनी पथ पर रहते हुए आप जो करना चाहते हैं वह बहुत कठिन है (लेकिन पूरी तरह असंभव नहीं है)।


मुझे पहले सही तरीका का मसौदा तैयार करते हैं;

ऐसा करने के लिए उचित तरीका UIAutomation का उपयोग कर रहा है। UIAutomation वही करता है जो आप पूछते हैं, यह सभी प्रकार के परीक्षणों के लिए उपयोगकर्ता व्यवहार को अनुकरण करता है।


अब यह कठिन तरीका है;

समस्या जो आपकी समस्याएं उबलती हैं, एक नया UIEvent को तुरंत चालू करना है। (यू) सौभाग्य से UIKit स्पष्ट सुरक्षा कारणों से ऐसी घटनाओं के लिए किसी भी रचनाकार की पेशकश नहीं करता है। हालांकि ऐसे कामकाज हैं जो अतीत में काम करते थे, यकीन नहीं कि वे अभी भी करते हैं।

मैट गैलाघर का शानदार ब्लॉग solution on how to synthesise touch events ड्राफ्टिंग पर एक नज़र डालें।

1

मुझे एक तालिका पर टैप करने के लिए एक टेस्ट सेल पर एक टैप अनुकरण करने का प्रयास करने के लिए एक ही समस्या का सामना करना पड़ रहा था, जो एक तालिका पर टैप करने वाले हैंडल नियंत्रक के लिए एक परीक्षण स्वचालित करने के लिए।

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

इकाई परीक्षण एक स्पर्श अनुकरण करना चाहिए ताकि gestureRecognizer के रूप में यह उपयोगकर्ता बातचीत से उत्पन्न किया गया था कार्यवाही प्रारंभ होगा:

नियंत्रक एक निजी UITapGestureRecognizer नीचे के रूप में बनाया गया है।

इस परिदृश्य में प्रस्तावित समाधानों में से कोई भी काम नहीं करता है, इसलिए मैंने इसे यूआईटीएपीजेस्टर रिकॉग्नाइज़र सजाया, जिससे नियंत्रक द्वारा बुलाए गए सटीक तरीकों को तैयार किया गया। इसलिए मैंने एक "प्रदर्शनटैप" विधि जोड़ा जो कि इस तरह से कार्रवाई को नियंत्रित करता है कि नियंत्रक स्वयं से अनजान है कि कार्रवाई कहां से उत्पन्न हुई है। इस तरह, मैं इशारा ट्रिगर किए गए इशारा पहचानकर्ता से स्वतंत्र नियंत्रक के लिए एक परीक्षण इकाई बना सकता था।

यह मेरी श्रेणी है, उम्मीद है कि यह किसी की मदद करेगी।

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 इशारा पहचानकर्ता होते हैं तो आप उन पर भरोसा नहीं कर पाएंगे। यदि कोई अन्य क्षेत्र है, तो यह मामला होगा, क्योंकि ऐप्पल उन्हें आंतरिक रूप से उपयोग करता है। समस्या का समाधान संबंधित वस्तुओं का उपयोग गुणों में नकली में करना है। यदि कोई दिलचस्पी है, तो मैं उस समाधान को पोस्ट करूंगा। – tooluser

2

ठीक है, मैंने उपरोक्त को एक श्रेणी में बदल दिया है जो काम करता है।

दिलचस्प बिट्स:

  • श्रेणियाँ सदस्य चर नहीं जोड़ सकते। जो कुछ भी आप जोड़ते हैं वह वर्ग के लिए स्थिर हो जाता है और इस प्रकार ऐप्पल के कई यूआईटीएपीजेस्टर रिकॉग्नाइज़र द्वारा घिरा हुआ है।
    • तो, जादू करने के लिए related_object का उपयोग करें।
    • NSValue गैर वस्तुओं
    • के भंडारण के लिए
  • एप्पल के init विधि महत्वपूर्ण विन्यास तर्क होता है; हम क्या सेट किया गया है पर (नल, स्पर्श की संख्या, और क्या?
    • की संख्या अनुमान कर सकता है लेकिन इस अभिशप्त है। तो, हम हमारे init विधि है कि mocks को बरकरार रखता है में एक प्रकार का शराबी।

हेडर फाइल तुच्छ है,। यहां कार्यान्वयन है

#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

मैं इसे ऐपस्टोर ऐप पर उपयोग कर सकता हूं? –

+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) 
     } 
    } 

} 

दोनों, पुस्तकालय के साथ-साथ टुकड़ा निजी एपीआई का उपयोग करें और शायद एक अस्वीकृति यदि आपका टेस्ट स्वीट के बाहर किया जाता का कारण होगा ...