2011-02-10 20 views
125

Trong khi hầu hết các tài liệu của Apple được viết rất tốt, tôi nghĩ 'Event Handling Guide for iOS' là một ngoại lệ. Thật khó cho tôi để hiểu rõ những gì được mô tả ở đó.Xử lý sự kiện cho iOS - cách hitTest: withEvent: và pointInside: withEvent: có liên quan?

Tài liệu này cho biết,

Trong hit-thử nghiệm, một cửa sổ gọi hitTest:withEvent: trên hầu hết các-top quan điểm của hệ thống phân cấp xem; phương pháp này được tiến hành bằng cách đệ quy gọi số pointInside:withEvent: trên mỗi chế độ xem trong hệ thống phân cấp chế độ xem trả về CÓ, tiến hành phân cấp cho đến khi tìm thấy chế độ xem phụ trong phạm vi liên kết của chúng. Chế độ xem đó trở thành chế độ xem thử nghiệm lần truy cập.

Vậy là nó như thế chỉ hitTest:withEvent: của hầu hết trên các quan điểm được gọi bởi hệ thống, trong đó kêu gọi pointInside:withEvent: của tất cả các subviews, và nếu trở về từ một subview cụ thể là YES, sau đó gọi pointInside:withEvent: đó subview của lớp con?

+2

Một hướng dẫn rất tốt đã giúp tôi ra [link] (http://smnh.me/ hit-testing-in-ios) – anneblue

Trả lời

155

Dường như đây là một câu hỏi cơ bản. Nhưng tôi đồng ý với bạn tài liệu không rõ ràng như các tài liệu khác, vì vậy đây là câu trả lời của tôi.

Việc thực hiện hitTest:withEvent: trong UIResponder nào sau đây:

  • Nó kêu gọi pointInside:withEvent: của self
  • Nếu sự trở lại là NO, hitTest:withEvent: lợi nhuận nil. kết thúc câu chuyện.
  • Nếu trả lại là CÓ, nó sẽ gửi hitTest:withEvent: thư đến các bản xem phụ của nó. nó bắt đầu từ tiểu sử cấp cao nhất và tiếp tục các chế độ xem khác cho đến khi một chế độ xem phụ trả về đối tượng không phải nil hoặc tất cả các bản xem phụ nhận được thông báo.
  • Nếu lần xem phụ trả về một đối tượng không phải là nil trong lần đầu tiên, thì hitTest:withEvent: đầu tiên trả về đối tượng đó. kết thúc câu chuyện.
  • Nếu không subview trả về một đối tượng phi nil, các hitTest:withEvent: lợi nhuận đầu tiên self

Quá trình này lặp đi lặp lại một cách đệ quy, vì vậy thường là xem lá của hệ thống phân cấp view được trả về cuối cùng.

Tuy nhiên, bạn có thể ghi đè hitTest:withEvent để làm điều gì đó khác đi. Trong nhiều trường hợp, việc ghi đè pointInside:withEvent: đơn giản hơn và vẫn cung cấp đủ tùy chọn để tinh chỉnh xử lý sự kiện trong ứng dụng của bạn.

+0

Bạn có nghĩa là 'hitTest: withEvent:' của tất cả các bản xem trước được thực hiện cuối cùng? – realstuff02

+2

Có. Chỉ cần ghi đè lên 'hitTest: withEvent:' trong khung nhìn của bạn (và 'pointInside' nếu bạn muốn), in một bản ghi và gọi' [super hitTest ... 'để tìm ra' hitTest: withEvent: 'được gọi theo thứ tự nào. – MHC

+0

Tôi đã làm điều đó và nhận được hình ảnh rõ ràng! Cảm ơn rất nhiều! – realstuff02

273

Tôi nghĩ bạn đang nhầm lẫn giữa phân lớp với phân cấp chế độ xem. Những gì các tài liệu nói là như sau. Giả sử bạn có phân cấp chế độ xem này. Bằng hệ thống cấp bậc Tôi không nói về hệ thống phân cấp lớp, nhưng lượt xem trong vòng quan điểm hệ thống phân cấp, như sau:

+----------------------------+ 
|A       | 
|+--------+ +------------+ | 
||B  | |C   | | 
||  | |+----------+| | 
|+--------+ ||D   || | 
|    |+----------+| | 
|    +------------+ | 
+----------------------------+ 

Giả sử bạn đặt ngón tay của bạn bên D. Dưới đây là những gì sẽ xảy ra:

  1. hitTest:withEvent: được gọi là A, chế độ xem trên cùng của hệ thống phân cấp chế độ xem.
  2. pointInside:withEvent: được gọi là đệ quy trên mỗi chế độ xem.
    1. pointInside:withEvent: được kêu gọi A, và trả YES
    2. pointInside:withEvent: được kêu gọi B, và trả NO
    3. pointInside:withEvent: được kêu gọi C, và trả YES
    4. pointInside:withEvent: được kêu gọi D, và trả YES
  3. Trên các khung nhìn trả lại YES, nó sẽ xem xét cấu trúc phân cấp để xem subview nơi chạm được thực hiện. Trong trường hợp này, từ A, CD, nó sẽ là D.
  4. D sẽ là cái nhìn hit-test
+0

Cảm ơn bạn đã trả lời. Những gì bạn mô tả cũng là những gì trong tâm trí của tôi, nhưng @MHC nói 'hitTest: withEvent:' của B, C và D cũng được gọi. Điều gì sẽ xảy ra nếu D là một subview của C, không A? Tôi nghĩ rằng tôi đã bối rối ... – realstuff02

+2

Trong bản vẽ của tôi, D là một subview của C. – pgb

+1

Sẽ không 'A' trả về' YES' là tốt, giống như 'C' và' D' không? –

21

Cảm ơn câu trả lời, họ đã giúp tôi để giải quyết tình hình với quan điểm "overlay".

+----------------------------+ 
|A +--------+    | 
| |B +------------------+ | 
| | |C   X | | 
| | +------------------+ | 
| |  |    | 
| +--------+    | 
|       | 
+----------------------------+ 

Giả sử X - liên lạc của người dùng. pointInside:withEvent: trên B trả về NO, vì vậy hitTest:withEvent: trả về A. Tôi đã viết chuyên mục trên UIView để xử lý sự cố khi bạn cần nhận được liên lạc ở trên cùng hầu hết các chế độ xem hiển thị.

- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event { 
    // 1 
    if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0) 
     return nil; 

    // 2 
    UIView *hitView = self; 
    if (![self pointInside:point withEvent:event]) { 
     if (self.clipsToBounds) return nil; 
     else hitView = nil; 
    } 

    // 3 
    for (UIView *subview in [self.subviewsreverseObjectEnumerator]) { 
     CGPoint insideSubview = [self convertPoint:point toView:subview]; 
     UIView *sview = [subview overlapHitTest:insideSubview withEvent:event]; 
     if (sview) return sview; 
    } 

    // 4 
    return hitView; 
} 
  1. Chúng ta không nên gửi các sự kiện liên lạc cho quan điểm ẩn hoặc trong suốt, hoặc quan điểm với userInteractionEnabled thiết lập để NO;
  2. Nếu chạm vào bên trong self, self sẽ được coi là kết quả tiềm năng.
  3. Kiểm tra đệ quy tất cả các lần xem phụ cho lần truy cập. Nếu có, hãy trả lại.
  4. khác lại tự hoặc nil tùy thuộc vào kết quả từ bước 2.

Note, [self.subviewsreverseObjectEnumerator] cần thiết để làm theo quan điểm hệ thống phân cấp từ trên hầu hết xuống dưới. Và kiểm tra clipsToBounds để đảm bảo không kiểm tra các bản xem trước bị che khuất.

Cách sử dụng:

  1. nhập loại theo quan điểm subclassed của bạn.
  2. Thay hitTest:withEvent: với điều này
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 
    return [self overlapHitTest:point withEvent:event]; 
} 

Official Apple's Guide cung cấp một số hình ảnh minh họa tốt quá.

Hy vọng điều này sẽ giúp ai đó.

+0

Tuyệt vời! Cảm ơn cho logic rõ ràng và đoạn mã GREAT, giải quyết đầu trầy xước của tôi! – Thompson

+0

@Lion, Câu trả lời hay. Ngoài ra, bạn có thể kiểm tra sự bình đẳng để xóa màu trong bước đầu tiên. –

3

Nó hiển thị như đoạn mã này!

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 
{ 
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01) 
    { 
     return nil; 
    } 

    if (![self pointInside:point withEvent:event]) 
    { 
     return nil; 
    } 

    __block UIView *hitView = self; 

    [self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 

     CGPoint thePoint = [self convertPoint:point toView:obj]; 

     UIView *theSubHitView = [obj hitTest:thePoint withEvent:event]; 

     if (theSubHitView != nil) 
     { 
      hitView = theSubHitView; 

      *stop = YES; 
     } 

    }]; 

    return hitView; 
} 
+0

Tôi thấy câu trả lời dễ hiểu nhất và phù hợp với quan sát của tôi về hành vi thực tế rất chặt chẽ. Sự khác biệt duy nhất là các bài đánh giá được liệt kê theo thứ tự ngược lại, vì vậy các bản xem trước gần hơn với các điểm tiếp nhận nhận được ưu tiên cho các anh chị em phía sau chúng. –

+0

@DouglasHill nhờ chỉnh sửa của bạn. Trân trọng – hippo

31

Tôi tìm thấy điều này Hit-Testing in iOS là rất hữu ích

enter image description here

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) { 
     return nil; 
    } 
    if ([self pointInside:point withEvent:event]) { 
     for (UIView *subview in [self.subviews reverseObjectEnumerator]) { 
      CGPoint convertedPoint = [subview convertPoint:point fromView:self]; 
      UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; 
      if (hitTestView) { 
       return hitTestView; 
      } 
     } 
     return self; 
    } 
    return nil; 
} 
+2

Câu trả lời hay, cảm ơn! – Jacob

0

Đoạn @lion hoạt động giống như một nét duyên dáng. Tôi chuyển nó sang swift 2.1 và sử dụng nó như một phần mở rộng cho UIView. Tôi đăng nó ở đây trong trường hợp ai đó cần nó.

extension UIView { 
    func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { 
     // 1 
     if !self.userInteractionEnabled || self.hidden || self.alpha == 0 { 
      return nil 
     } 
     //2 
     var hitView: UIView? = self 
     if !self.pointInside(point, withEvent: event) { 
      if self.clipsToBounds { 
       return nil 
      } else { 
       hitView = nil 
      } 
     } 
     //3 
     for subview in self.subviews.reverse() { 
      let insideSubview = self.convertPoint(point, toView: subview) 
      if let sview = subview.overlapHitTest(insideSubview, withEvent: event) { 
       return sview 
      } 
     } 
     return hitView 
    } 
} 

Để sử dụng nó, chỉ cần ghi đè hitTest: Điểm: withEvent trong UIView của bạn như sau:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { 
    let uiview = super.hitTest(point, withEvent: event) 
    print("hittest",uiview) 
    return overlapHitTest(point, withEvent: event) 
} 
Các vấn đề liên quan