UIGestureRecognizer chặn lượt xem phụ để xử lý các sự kiện chạm


82

Tôi đang cố gắng tìm ra cách điều này được thực hiện đúng cách . Tôi đã cố gắng mô tả tình huống: nhập mô tả hình ảnh ở đây

Tôi đang thêm a UITableViewdưới dạng một lượt xem phụ của a UIView. Phản hồi UIViewvới một lần chạm và pinchGestureRecognizer, nhưng khi làm như vậy, chế độ xem bảng ngừng phản ứng với hai cử chỉ đó (nó vẫn phản ứng với các lần vuốt).

Tôi đã làm cho nó hoạt động với đoạn mã sau, nhưng rõ ràng đó không phải là một giải pháp hay và tôi chắc chắn rằng có một cách tốt hơn. Điều này được đưa vào UIView(chế độ xem siêu tốc):

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if([super hitTest:point withEvent:event] == self) {
        for (id gesture in self.gestureRecognizers) {
            [gesture setEnabled:YES];
        }
        return self;
    }
    for (id gesture in self.gestureRecognizers) {
        [gesture setEnabled:NO];
    }
    return [self.subviews lastObject];
}

Câu trả lời:


182

Tôi đã gặp một vấn đề tương tự và tìm thấy giải pháp của mình trong câu hỏi SO này . Tóm lại, hãy tự đặt mình làm người đại diện cho của bạn UIGestureRecognizervà sau đó kiểm tra chế độ xem được nhắm mục tiêu trước khi cho phép trình nhận dạng của bạn xử lý thao tác chạm. Phương pháp đại biểu có liên quan là:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch

3
Tôi thích giải pháp này nhất vì nó không liên quan đến việc rắc rối với các thao tác chạm hitTest:withEvent:hoặc pointInside:withEvent:.
DarkDust

6
Giải pháp sạch, như bạn có thể kiểm tra, ví dụ: return !(touch.view == givenView);nếu bạn chỉ muốn loại trừ một chế độ xem nhất định hoặc return !(touch.view.tag == kTagNumReservedForExcludingViews);khi bạn muốn dừng xử lý trình nhận dạng của mình trên một loạt các chế độ xem phụ khác nhau.
Cate

6
Tôi sẽ làm bài kiểm tra hit với - (BOOL)isDescendantOfView:(UIView *)view. Điều này cũng hoạt động tốt - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)eventkhi phân lớp UIGestureRecognizer.
Christoph

1
@Justin, điều gì sẽ xảy ra nếu trình nhận dạng cử chỉ của chúng tôi thuộc về uiscrollview và chúng tôi không thể ủy quyền cho trình nhận dạng cử chỉ?
Gökhan Barış Aker

108

Việc chặn các sự kiện chạm tới các lượt xem phụ là hành vi mặc định. Bạn có thể thay đổi hành vi này:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];

5
Thao tác này sẽ gửi các sự kiện chạm tới các lượt xem phụ, nhưng nó cũng sẽ được gửi tới trình nhận dạng cử chỉ. Vì vậy, điều này sẽ ngăn chặn việc chặn các lượt xem phụ nhưng trình nhận dạng cử chỉ sẽ vẫn được nhận dạng trên các lượt xem phụ.
Jonathan.

@Jonathan. Vâng tôi đồng ý với bạn. Những gì tôi đã làm trong phương pháp xử lý cử chỉ của mình là kiểm tra xem vị trí cử chỉ có xảy ra bên trong chế độ xem phụ có liên quan hay không, trong trường hợp đó tôi không cần thực thi phần còn lại của mã. Ngoài ra, một lý do khiến tôi chọn cách giải quyết này là UITapGestureRecognizer không khai báo translationInViewphương thức. Vì vậy, việc triển khai phương thức UIGestureRecognizerDelegate được đề cập ở trên sẽ chỉ dẫn đến việc bị lỗi ... unrecognized selector sent to blah. Để kiểm tra, sử dụng một cái gì đó như: CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview]).
MkVal

Theo câu hỏi của OP, câu trả lời này nên được chọn làm câu trả lời chính.
farzadshbfn

5

Tôi đang hiển thị một chế độ xem phụ thả xuống có chế độ xem bảng riêng. Kết quả là, touch.viewđôi khi sẽ trả về các lớp như UITableViewCell. Tôi đã phải bước qua (các) lớp cha để đảm bảo rằng đó là lớp con mà tôi nghĩ là:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    UIView *view = touch.view;
    while (view.class != UIView.class) {
        // Check if superclass is of type dropdown
        if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
            NSLog(@"Is of type dropdown; returning NO");
            return NO;
        } else {
            view = view.superview;
        }
    }

    return YES;
}

Vòng lặp while giải thích cho điều đó
joslinm

5

Dựa trên câu trả lời @Pin Shih Wang . Chúng tôi bỏ qua tất cả các lần nhấn khác với các lần nhấn trên chế độ xem chứa trình nhận dạng cử chỉ nhấn. Tất cả các lần nhấn đều được chuyển tiếp đến phân cấp chế độ xem bình thường như chúng tôi đã thiết lập tapGestureRecognizer.cancelsTouchesInView = false. Đây là mã trong Swift3 / 4:

func ensureBackgroundTapDismissesKeyboard() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tapGestureRecognizer.cancelsTouchesInView = false
    self.view.addGestureRecognizer(tapGestureRecognizer)
}

@objc func handleTap(recognizer: UIGestureRecognizer) {
    let location = recognizer.location(in: self.view)
    let hitTestView = self.view.hitTest(location, with: UIEvent())
    if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
        // I dismiss the keyboard on a tap on the scroll view
        // REPLACE with own logic
        self.view.endEditing(true)
    }
}

Làm cách nào để so sánh trình nhận dạng cử chỉ với một UISwipeActionStandardButton? Tôi đã thử với.some(UISwipeActionStandardButton)
Jalil

4

Một khả năng là phân lớp phụ trình nhận dạng cử chỉ của bạn (nếu bạn chưa có) và ghi đè -touchesBegan:withEvent:để nó xác định xem mỗi lần chạm có bắt đầu trong một lần xem phụ bị loại trừ hay không và gọi -ignoreTouch:forEvent:cho lần chạm đó nếu có.

Rõ ràng, bạn cũng sẽ cần thêm một thuộc tính để theo dõi lượt xem phụ bị loại trừ, hoặc có lẽ tốt hơn, một loạt lượt xem phụ bị loại trừ.


Có rất nhiều mã ở đây github.com/…
johndpope

2

Có thể làm mà không cần kế thừa bất kỳ lớp nào.

bạn có thể kiểm tra cử chỉ cử chỉ trong bộ chọn gọi lại của cử chỉ

nếu view.gestureRecognizers không chứa bộ cử chỉ của bạn, chỉ cần bỏ qua nó

ví dụ

- (void)viewDidLoad
{
    UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self     action:@selector(handleSingleTap:)];
    singleTapGesture.numberOfTapsRequired = 1;
}

kiểm tra view.gestureRecognizers tại đây

- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
    UIEvent *event = [[UIEvent alloc] init];
    CGPoint location = [gestureRecognizer locationInView:self.view];

    //check actually view you hit via hitTest
    UIView *view = [self.view hitTest:location withEvent:event];

    if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
        //your UIView
        //do something
    }
    else {
        //your UITableView or some thing else...
        //ignore
    }
}

Như đã đề cập trong câu trả lời khác, nếu sử dụng phương pháp này đảm bảo vòi cử chỉ nhận dạng chuyển tiếp vòi nước để phân cấp tầm nhìn của bạn với: singleTapGesture.cancelsTouchesInView = NO;thêm vào viewDidLoadở trên
Nick Ager

1

Tôi đã tạo một lớp con UIGestureRecognizer được thiết kế để chặn tất cả các trình nhận dạng cử chỉ được gắn vào siêu thị của một chế độ xem cụ thể.

Đó là một phần của dự án WEPopover của tôi. Bạn có thể tìm thấy nó ở đây .


1

triển khai một ủy quyền cho tất cả các trình nhận dạng của parentView và đặt phương thức certRecognizer trong ủy quyền chịu trách nhiệm kích hoạt đồng thời các trình nhận dạng:

func gestureRecognizer(UIGestureRecognizer,       shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
    return true
    } else {
    return false
}

}

Bạn có thể sử dụng các phương pháp fail nếu bạn muốn kích hoạt con cái nhưng không phải là trình nhận dạng cha mẹ:

https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate


0

Tôi cũng đã làm một popover và đây là cách tôi đã làm điều đó

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}

0

Bạn có thể tắt và bật nó .... trong mã của tôi, tôi đã làm điều gì đó như thế này vì tôi cần tắt nó khi bàn phím không hiển thị, bạn có thể áp dụng nó cho tình huống của mình:

gọi đây là viewdidload, v.v.:

NSNotificationCenter    *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:@selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];

sau đó tạo hai phương thức:

-(void) notifyShowKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=true;  // turn the gesture on
}

-(void) notifyHideKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=false;  //turn the gesture off so it wont consume the touch event
}

Điều này làm là vô hiệu hóa vòi. Mặc dù vậy, tôi đã phải biến tap thành một biến instance và giải phóng nó trong dealloc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.