UIButton ภายในมุมมองที่มี UITapGestureRecognizer


184

UITapGestureRecognizerผมมีมุมมองกับ ดังนั้นเมื่อฉันแตะที่มุมมองมุมมองอื่นจะปรากฏขึ้นเหนือมุมมองนี้ มุมมองใหม่นี้มีสามปุ่ม เมื่อฉันกดปุ่มเหล่านี้ปุ่มใดปุ่มหนึ่งฉันไม่ได้รับการกระทำของปุ่มฉันจะได้รับการกระทำด้วยการแตะเท่านั้น ดังนั้นฉันจึงไม่สามารถใช้ปุ่มเหล่านี้ได้อีก ฉันจะทำอย่างไรเพื่อให้กิจกรรมผ่านไปยังปุ่มเหล่านี้ สิ่งที่แปลกคือปุ่มยังคงได้รับการเน้น

ฉันไม่สามารถลบ UITapGestureRecognizer หลังจากที่ฉันได้รับมัน เพราะด้วยมุมมองใหม่สามารถลบออกได้ หมายถึงฉันต้องการพฤติกรรมเช่นการควบคุมแบบเต็มหน้าจอ Vide

คำตอบ:


256

คุณสามารถตั้งค่าการควบคุมหรือมุมมองของคุณ (แล้วแต่จำนวนใดจะสร้างจดจำท่าทาง) UITapGestureRecognizerเป็นตัวแทนของ จากนั้นในตัวแทนคุณสามารถดำเนินการ-gestureRecognizer:shouldReceiveTouch:ได้ ในการปรับใช้ของคุณคุณสามารถทดสอบว่าการสัมผัสเป็นของมุมมองย่อยใหม่ของคุณหรือไม่และหากเป็นเช่นนั้น สิ่งต่อไปนี้:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}

ในไฟล์ส่วนหัวของฉันฉันมีมุมมองของฉันใช้ UIGestoreRegognizerDelegate และใน. mi ของฉันเพิ่มรหัสของคุณด้านบน แตะไม่เคยเข้าสู่วิธีนี้มันจะตรงไปที่ตัวจัดการของฉัน ความคิดใด ๆ
kmehta

1
@kmehta คุณมักจะลืมตั้งค่าคุณสมบัติผู้รับมอบสิทธิ์ของ UIGestureRecognizer
จนถึง

คำตอบนี้สามารถพูดคุยกันทั่วไปเพื่อจัดการกับทุกกรณีที่มี IBAction ได้หรือไม่?
Martin Wickman

@Martin IBAction คอมไพล์ลงไปvoidดังนั้นจึงไม่ทิ้งข้อมูลใด ๆ ไว้ที่ runtime เพื่อใช้ในการตรวจจับ แต่สิ่งที่คุณสามารถทำได้คือเดินตามลำดับชั้นของมุมมองทดสอบUIControlและส่งคืนNOหากคุณพบการควบคุมใด ๆ
Lily Ballard

ขอบคุณสำหรับสิ่งนี้มันช่วยฉัน หากปุ่มของคุณอยู่ใน UIImageView ตรวจสอบให้แน่ใจว่า userInteractionEnabled ของ imageView ถูกตั้งค่าไว้ที่ YES
james075

156

จากการติดตามถึง Casey ติดตามคำตอบของ Kevin Ballard:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

สิ่งนี้ทำให้ผู้ใช้ทุกประเภทสามารถควบคุมเช่นปุ่มตัวเลื่อนและอื่น ๆ ได้


1
นั่นเป็นโซลูชันที่ยืดหยุ่นที่สามารถใช้ร่วมกับsetCancelsTouchesInView = NOการไม่เรียกใช้ท่าทางสัมผัสกับการโต้ตอบกับตัวควบคุมได้ อย่างไรก็ตามคุณสามารถเขียนได้ดีกว่า:return ![touch.view isKindOfClass:[UIControl class]];
griga13

วิธีการแก้ปัญหา Swift 4+: return! (touch.view is UIControl)
nteissler

103

พบคำตอบที่นี่: ลิงค์

คุณยังสามารถใช้

tapRecognizer.cancelsTouchesInView = NO;

ซึ่งป้องกันไม่ให้ตัวจำแนกการแตะเป็นเครื่องมือเดียวที่จะตรวจจับการแตะทั้งหมด

อัปเดต - ไมเคิลพูดถึงลิงก์ไปยังเอกสารอธิบายคุณสมบัตินี้: cancelsTouchesInView


8
นี่ควรเป็นคำตอบที่ IMO ยอมรับ ซึ่งช่วยให้ทุกมุมมองย่อยจัดการกับการแตะด้วยตัวเองและป้องกันมุมมองที่มี tabrecognizer ติดอยู่กับมันจาก 'highjacking' แตะ ไม่จำเป็นต้องติดตั้งผู้รับมอบสิทธิ์หากสิ่งที่คุณต้องการทำคือให้คลิกได้ (เพื่อลาออกจากการตอบกลับครั้งแรก / ซ่อนแป้นพิมพ์บน textfields ฯลฯ ) สุดยอด!
EeKay

4
นี่ควรเป็นคำตอบที่ได้รับการยอมรับอย่างแน่นอน คำตอบนี้ทำให้ฉันอ่านเอกสารของ Appleอย่างถูกต้องซึ่งทำให้ชัดเจนว่าตัวจดจำท่าทางจะป้องกันไม่ให้ภาพรวมได้รับเหตุการณ์ที่เป็นที่รู้จักเว้นแต่คุณจะทำสิ่งนี้
Michael van der Westhuizen

1
สิ่งนี้เคยทำงาน แต่ตอนนี้มันไม่ทำงานสำหรับฉัน .. บางทีอาจเป็นเพราะฉันมี scrollView ใต้ปุ่ม? ฉันต้องใช้ผู้รับมอบสิทธิ์เหมือนในคำตอบข้างต้น
xissburg

7
หลังจากทดลองเล็กน้อยฉันสังเกตว่าสิ่งนี้จะใช้ได้เฉพาะเมื่อมุมมองที่มีตัวจดจำท่าทางเป็นพี่น้องของ UIControl และจะไม่เกิดขึ้นถ้ามุมมองเป็นต้นกำเนิดของ UIControl
xissburg

6
ใช้งานไม่ได้ตามที่ตั้งใจจริง ๆ ... ใช่มันป้องกันตัวจดจำการแตะเพื่อกินเหตุการณ์ใน UIControl แต่มันยังคงเรียกใช้การกระทำของตัวจำแนกลายมือซึ่งเรียกใช้การกระทำ 2 พร้อมกัน
Tek Yin

71

ตามคำตอบของ Kevin Ballard ฉันมีปัญหาเดียวกันนี้และจบลงด้วยการใช้รหัสนี้:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIButton class]]){
        return NO;
    }
    return YES;
}

มันมีผลเหมือนกัน แต่จะทำงานกับ UIButton ใด ๆ ที่ความลึกของมุมมองใด ๆ (UIButton ของฉันมีหลายมุมมองที่ลึกและผู้รับมอบสิทธิ์ของ UIGestureRecognizer ไม่มีการอ้างอิงถึง)


6
อย่าอยากเป็นดิ๊กที่นี่ แต่จริงๆแล้วใช่และไม่ใช่ โปรดใช้การประชุมโกโก้ TRUE / FALSE / NULL ใช้สำหรับ CoreFoundation-Layer
steipete

จากสิ่งที่ฉันอ่าน YES และ NO ถูกสร้างขึ้นเพื่อให้อ่านได้จริง ความสามารถในการอ่านเป็นเรื่องส่วนตัว มันเกิดจากวิธีการและการตั้งชื่อคุณสมบัติการประชุมที่ทุกคนไม่ได้กังวลมากเกินไป ถ้าคุณต้องการยึดมั่นอย่างเคร่งครัดในการตั้งชื่อการประชุมแล้วใช่ใช้ใช่และไม่ใช่สำหรับ NSWhething ใด ๆ อย่างไรก็ตามฉันจะบอกว่าถ้าคุณสำรวจนักพัฒนาคุณจะได้รับความคิดเห็นที่หลากหลายซึ่งหนึ่งในนี้สามารถอ่านได้มากขึ้น [ปุ่ม setHidden: YES]; หรือ [ปุ่ม setHidden: TRUE];
Pimp Juice McJones

1
ฉันเดาว่าสิ่งที่ฉันพยายามจะพูดคือถ้าหลักฐานของการตั้งชื่อเป็นแบบอ่านได้ฉันคิดว่ามันยากที่จะปฏิเสธว่ามันเป็นอัตนัยในบางกรณี หากคุณเป็นคนเจ้าระเบียบแล้วคุณจะไม่เข้าใจคำแถลงนั้น
Pimp Juice McJones

2
มันอาจดูเป็นเรื่องส่วนตัว แต่หลังจากผ่านไประยะหนึ่งของการเขียนโปรแกรม Cocoa คุณได้เข้าสู่การไหลของรหัสที่แม่นยำยิ่งขึ้น ... "TRUE" ดูเหมือนผิดจริง ๆ หลังจากผ่านไปหลายปีของ Objective-C เพราะมันไม่ตรงกับ "ซ่อน" มาก ดี.
Kendall Helmstetter Gelner

ตั้งแต่เมื่อไหร่ที่ / คุณสามารถส่งรูปแบบการแตะไปยัง UIButton เป็น Touch Up Inside UITouch Event (ซึ่งภายหลังเป็นเหตุการณ์ที่สร้างขึ้นโดยการแตะที่ UIButton)? ดูเหมือนว่าซ้ำและไม่ถูกต้องในการสร้างตัวรู้จำท่าทางการแตะสำหรับปุ่มที่มี 15 กิจกรรมการสัมผัสที่เชื่อมโยงกับท่าทางการแตะแล้ว ฉันต้องการดูโค้ดไม่ต้องเดา
James Bush

10

ใน iOS 6.0 และใหม่กว่าการดำเนินการควบคุมเริ่มต้นจะป้องกันพฤติกรรมการจดจำท่าทางสัมผัสที่ทับซ้อนกัน ตัวอย่างเช่นการกระทำเริ่มต้นสำหรับปุ่มคือการแตะครั้งเดียว หากคุณมีระบบจดจำลายนิ้วมือแตะเพียงครั้งเดียวแนบกับมุมมองพาเรนต์ของปุ่มและผู้ใช้แตะที่ปุ่มวิธีการกระทำของปุ่มจะได้รับเหตุการณ์การสัมผัสแทนการใช้ท่าทางสัมผัส สิ่งนี้ใช้เฉพาะกับการจดจำท่าทางที่ทับซ้อนการกระทำเริ่มต้นสำหรับตัวควบคุมซึ่งรวมถึง: .....

จากเอกสาร API ของ Apple


8

คำตอบเหล่านี้ไม่สมบูรณ์ ฉันต้องอ่านหลาย ๆ โพสต์เกี่ยวกับวิธีใช้การดำเนินการบูลีนนี้

ในไฟล์ * .h ของคุณเพิ่มสิ่งนี้

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

ในไฟล์ * .m ของคุณเพิ่มสิ่งนี้

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

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}

7

พบวิธีที่จะทำมันได้จากที่อื่นที่นี่ มันตรวจจับการสัมผัสไม่ว่าจะอยู่ในแต่ละปุ่มหรือไม่

(1) pointInside: withEvent: (2) locationInView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}

ฉันลองใช้วิธีแก้ไขปัญหาอื่นทั้งหมดสำหรับเรื่องนี้ แต่ -pointInside: withEvent: แนวทางเป็นวิธีเดียวที่ได้ผลสำหรับฉัน
Kenny Wyland

3

นี่คือคำตอบของLily Ballardรุ่น Swift ที่เหมาะกับฉัน:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}

3

สวิฟท์ 5

ปุ่มบน superview ด้วย tapgesture

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

ในกรณีของฉันการใช้hitTest นั้นเหมาะกับฉัน ฉันมีมุมมองคอลเลกชันด้วยปุ่ม

เมธอดนี้สำรวจลำดับชั้นการดูโดยเรียกpoint(inside:with:)เมธอดของแต่ละมุมมองย่อยเพื่อพิจารณาว่ามุมมองย่อยใดควรรับเหตุการณ์การสัมผัส หากpoint(inside:with:)ส่งคืนจริงลำดับชั้นของมุมมองย่อยจะถูกสำรวจในทำนองเดียวกันจนกว่าจะพบมุมมองด้านหน้าที่มีจุดที่ระบุ

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}

1

คุณสามารถหยุด UITapGestureRecognizer จากการยกเลิกกิจกรรมอื่น ๆ (เช่นแตะที่ปุ่มของคุณ) โดยการตั้งค่าบูลีนต่อไปนี้:

    [tapRecognizer setCancelsTouchesInView:NO];

1

หากสถานการณ์ของคุณเป็นเช่นนี้:

คุณมีมุมมองที่เรียบง่ายและมีการเพิ่มตัวควบคุม UITextField เป็น UITextField เป็นมุมมองย่อยในมุมมองนั้น ตอนนี้คุณต้องการยกเลิกแป้นพิมพ์เมื่อคุณแตะที่ใดก็ได้อื่นในมุมมองยกเว้นบนส่วนควบคุม (ตัวอย่างที่คุณเพิ่ม)

วิธีแก้ไขคือ:

เพิ่มวิธีการต่อไปนี้ไปยัง XYZViewController.m ของคุณ (ซึ่งมีมุมมองของคุณ)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

ครั้งแรกที่ฉันลองด้วยคำตอบ@cdasherแต่ในกรณีของฉันแม้เมื่อคลิกที่ปุ่ม Iam ได้รับtouch.viewเป็น "มุมมอง" ของฉันใน ViewController แต่ไม่ใช่การควบคุม "UIButton" ของฉัน คนที่สามารถบอกว่าทำไมคุณสมบัติมุมมองสัมผัสที่ไม่ได้ส่งคืนมุมมองเดิมที่แตะที่เกิดขึ้น
NaveenRaghuveer

1
จะไม่ทำงานหากคุณมี scrollview หรือมุมมองอื่น ๆ ที่อ้างถึงเหตุการณ์การสัมผัส
lkraider

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.