iOS - chuyển tiếp tất cả các lần chạm thông qua chế độ xem


141

Tôi có một chế độ xem được phủ lên trên nhiều chế độ xem khác. Tôi chỉ sử dụng overaly để phát hiện một số lần chạm trên màn hình, nhưng ngoài việc tôi không muốn chế độ xem dừng hành vi của các chế độ xem khác bên dưới, đó là cuộn xem, v.v ... Làm cách nào tôi có thể chuyển tiếp tất cả các lần chạm qua xem lớp phủ này? Nó là một nhánh con của UIView.


2
Bạn đã giải quyết vấn đề của bạn? Nếu có thì xin vui lòng chấp nhận câu trả lời để người khác dễ dàng tìm ra giải pháp hơn vì tôi đang đối mặt với cùng một vấn đề.
Khushbu Desai

câu trả lời này hoạt động tốt stackoverflow.com/a/4010809/4833705
Lance Samaria

Câu trả lời:


121

Vô hiệu hóa tương tác người dùng là tất cả những gì tôi cần!

Mục tiêu-C:

myWebView.userInteractionEnabled = NO;

Nhanh:

myWebView.isUserInteractionEnabled = false

Điều này làm việc trong tình huống của tôi, nơi tôi đã có một cuộc phỏng vấn của nút. Tôi đã vô hiệu hóa tương tác trên subview và nút làm việc.
Simple_code

2
Trong Swift 4,myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont

117

Để truyền các liên lạc từ chế độ xem lớp phủ sang các chế độ xem bên dưới, hãy triển khai phương pháp sau trong UIView:

Mục tiêu-C:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

Swift 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}

3
Tôi có một vấn đề tương tự, nhưng điều tôi muốn là nếu tôi phóng to / xoay / di chuyển chế độ xem bằng cử chỉ thì nó sẽ trả về có và đối với sự kiện nghỉ ngơi thì nó sẽ trả về không, làm thế nào tôi có thể đạt được điều này?
Minakshi

@Minakshi đó là một câu hỏi hoàn toàn khác, hãy hỏi một câu hỏi mới cho điều đó.
Fattie

nhưng hãy lưu ý cách tiếp cận đơn giản này KHÔNG cho phép bạn có các nút và cứ thế TRỞ LÊN lớp trong câu hỏi!
Fattie

56

Đây là một chủ đề cũ, nhưng nó đã xuất hiện trên một tìm kiếm, vì vậy tôi nghĩ rằng tôi đã thêm 2c của mình. Tôi có một UIView bao quát với các cuộc phỏng vấn và chỉ muốn chặn các cú chạm chạm vào một trong các cuộc phỏng vấn, vì vậy tôi đã sửa đổi câu trả lời của PixelCloudSt thành

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}

1
Bạn cần tạo một lớp con UIView tùy chỉnh (hoặc một lớp con của bất kỳ lớp con UIView nào khác như UIImageView hoặc bất cứ thứ gì) và ghi đè chức năng này. Về bản chất, lớp con có thể có một '@interface' hoàn toàn trống trong tệp .h và hàm ở trên là toàn bộ nội dung của '@im THỰCation'.
fresidue

Cảm ơn, đây chính xác là những gì tôi cần để vượt qua các khoảng trống trong UIView của tôi, để được xử lý bởi chế độ xem phía sau. +100
Danny

40

Phiên bản cải tiến của câu trả lời @fresidue. Bạn có thể sử dụng UIViewlớp con này dưới dạng xem trong suốt khi chạm vào bên ngoài khung nhìn của nó. Thực hiện trong Mục tiêu-C :

@interface PassthroughView : UIView

@end

@implementation PassthroughView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

và trong Swift:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

TIỀN BOA:

Nói sau đó bạn có một bảng "giữ" lớn, có lẽ với chế độ xem bảng phía sau. Bạn làm bảng "giữ" PassthroughView. Bây giờ nó sẽ hoạt động, bạn có thể cuộn bảng "thông qua" người giữ ".

Nhưng!

  1. Trên đầu bảng "giữ", bạn có một số nhãn hoặc biểu tượng. Đừng quên, tất nhiên những thứ đó chỉ đơn giản là được đánh dấu tương tác người dùng được bật TẮT!

  2. Trên đầu bảng "giữ", bạn có một số nút. Đừng quên, tất nhiên những thứ đó chỉ đơn giản là được đánh dấu tương tác người dùng được bật ON!

  3. Lưu ý rằng hơi khó hiểu, chính "người giữ" - chế độ xem bạn sử dụng PassthroughView- phải được đánh dấu tương tác người dùng được bật ON ! Đó là ON !! (Nếu không, mã trong PassthroughView sẽ không bao giờ được gọi.)


1
Tôi đã sử dụng giải pháp được đưa ra cho Swift. Nó đang hoạt động trên ios 11 nhưng mỗi lần gặp sự cố trên iOS 10. Hiển thị quyền truy cập xấu. Bất kỳ ý tưởng?
Soumen

Bạn đã lưu lại những ngày của tôi, nó đã được giải quyết để chuyển các liên lạc đến UIView bên dưới.
AaoIi

1
Đừng quên kiểm tra$0.isUserInteractionEnabled
Sean Thielen

7

Tôi gặp vấn đề tương tự với UIStackView (nhưng có thể là bất kỳ chế độ xem nào khác). Cấu hình của tôi là như sau: Xem bộ điều khiển với containerView và StackView

Đó là một trường hợp cổ điển nơi tôi có một thùng chứa cần được đặt dưới nền, với các nút ở bên cạnh. Đối với mục đích bố trí, tôi đã bao gồm các nút trong UIStackView, nhưng bây giờ phần giữa (trống) của stackView chặn chạm :-(

Những gì tôi đã làm là tạo một lớp con của UIStackView với thuộc tính xác định subView có thể chạm được. Bây giờ, bất kỳ chạm nào trên các nút bên (bao gồm trong mảng * viewWithActiveTouch *) sẽ được cung cấp cho các nút, trong khi mọi thao tác chạm vào stackview ở bất kỳ nơi nào khác ngoài các chế độ xem này sẽ không bị chặn và do đó được chuyển đến bất kỳ vị trí nào bên dưới ngăn xếp lượt xem.

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}

6

Nếu chế độ xem bạn muốn chuyển tiếp các điểm chạm không xảy ra là một khung nhìn phụ / giám sát, bạn có thể thiết lập một thuộc tính tùy chỉnh trong lớp con UIView của mình như vậy:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Đảm bảo tổng hợp nó trong .m của bạn:

@synthesize forwardableTouchee;

Và sau đó bao gồm các mục sau trong bất kỳ phương pháp UIResponder nào của bạn, chẳng hạn như:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Bất cứ khi nào bạn khởi tạo UIView của mình, hãy đặt thuộc tính ForwardableTouchee thành bất kỳ chế độ xem nào bạn muốn các sự kiện được chuyển tiếp tới:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;

4

Trong Swift 5

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}

4

Tôi cần phải vượt qua các liên lạc thông qua một UIStackView. Một UIView bên trong là trong suốt, nhưng UIStackView tiêu thụ tất cả các chạm. Điều này làm việc cho tôi:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Tất cả các Bản xem trước được sắp xếp vẫn nhận được các lần chạm, nhưng các lần chạm vào chính UIStackView đã chuyển sang chế độ xem bên dưới (đối với tôi là mapView).


2

Có vẻ như thậm chí bạn có khá nhiều câu trả lời ở đây, không có ai trong sạch mà tôi cần. Vì vậy, tôi đã lấy câu trả lời từ @fresidue tại đây và chuyển đổi nó thành swift vì đây là điều mà hầu hết các nhà phát triển muốn sử dụng ở đây.

Nó đã giải quyết vấn đề của tôi khi tôi có một số thanh công cụ trong suốt có nút nhưng tôi muốn thanh công cụ vô hình với người dùng và các sự kiện chạm phải trải qua.

isUserInteractionEnables = false vì một số đã nêu không phải là một tùy chọn dựa trên thử nghiệm của tôi.

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}

1

Tôi đã có một vài nhãn trong StackView và tôi đã không thành công với các giải pháp ở trên, thay vào đó tôi đã giải quyết vấn đề của mình bằng cách sử dụng mã dưới đây:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Phân lớp UIStackView:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

Sau đó, bạn có thể làm một cái gì đó như:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}

0

Hãy thử một cái gì đó như thế này ...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

Mã ở trên, trong phương thức touchesBegan của bạn chẳng hạn sẽ chuyển các lần chạm cho tất cả các lượt xem.


6
Vấn đề với cách tiếp cận này AFAIK là việc cố gắng chuyển tiếp chạm vào các lớp của khung UIKit thường sẽ không có hiệu quả. Theo tài liệu của Apple, các lớp khung UIKit không được thiết kế để nhận các liên lạc có thuộc tính khung nhìn được đặt thành một số khung nhìn khác. Vì vậy, cách tiếp cận này hoạt động chủ yếu cho các lớp con tùy chỉnh của UIView.
maciejs

0

Tình huống tôi đang cố gắng làm là xây dựng bảng điều khiển bằng các điều khiển bên trong UIStackView lồng nhau. Một số điều khiển có UITextField khác với UIButton. Ngoài ra, đã có nhãn để xác định các điều khiển. Những gì tôi muốn làm là đặt một nút lớn không nhìn thấy được ở phía sau bảng điều khiển để nếu người dùng gõ vào một khu vực bên ngoài nút hoặc trường văn bản, thì tôi có thể bắt và thực hiện hành động - chủ yếu loại bỏ bất kỳ bàn phím nào nếu văn bản trường đã hoạt động (resignFirstResponder). Tuy nhiên, chạm vào nhãn hoặc vùng trống khác trong bảng điều khiển sẽ không vượt qua được. Các cuộc thảo luận ở trên rất hữu ích trong việc đưa ra câu trả lời của tôi dưới đây.

Về cơ bản, tôi đã phân loại UIStackView và ghi đè lên thói quen điểm (bên trong: với) để tìm loại điều khiển cần chạm và xóa bỏ qua những thứ như nhãn mà tôi muốn bỏ qua. Nó cũng kiểm tra bên trong UIStackView để mọi thứ có thể lặp lại vào cấu trúc bảng điều khiển.

Mã này có lẽ dài dòng hơn một chút so với nó. Nhưng nó rất hữu ích trong việc gỡ lỗi và hy vọng cung cấp rõ ràng hơn về những gì thói quen đang làm. Chỉ cần chắc chắn trong Trình tạo giao diện để thay đổi lớp của UIStackView thành PassThruStack.

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}


-1

Theo đề xuất của @PixelCloudStv nếu bạn muốn ném cảm ứng từ chế độ xem này sang chế độ xem khác nhưng với một số kiểm soát bổ sung đối với quy trình này - phân lớp UIView

// tiêu đề

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//thực hiện

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

Sau khi vào interfaceBuilder, chỉ cần đặt lớp View thành TouchView và thiết lập chỉnh lưu hoạt động với chỉnh lưu của bạn. Ngoài ra u có thể thay đổi và thực hiện logic khác.

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.