iOS - ส่งต่อทุกสัมผัสผ่านมุมมอง


145

ฉันมีมุมมองที่ซ้อนทับอยู่ด้านบนของมุมมองอื่น ๆ ฉันใช้เพียงการใช้ค่าที่เกินจริงเพื่อตรวจจับจำนวนการสัมผัสบนหน้าจอ แต่นอกเหนือจากนั้นฉันไม่ต้องการให้มุมมองหยุดพฤติกรรมของมุมมองอื่น ๆ ที่อยู่ด้านล่างซึ่งก็คือมุมมองแบบเลื่อนเป็นต้นฉันจะส่งต่อการสัมผัสทั้งหมดผ่านได้อย่างไร มุมมองภาพซ้อนทับนี้? มันเป็นส่วนย่อยของ UIView


2
คุณแก้ปัญหาของคุณแล้วหรือยัง? ถ้าใช่โปรดยอมรับคำตอบเพื่อให้คนอื่นหาวิธีแก้ปัญหาได้ง่ายขึ้นเพราะฉันกำลังประสบปัญหาเดียวกัน
Khushbu Desai

คำตอบนี้ใช้งานได้ดีstackoverflow.com/a/4010809/4833705
Lance Samaria

คำตอบ:


124

ฉันต้องการปิดการใช้งานการโต้ตอบกับผู้ใช้!

วัตถุประสงค์ -C:

myWebView.userInteractionEnabled = NO;

รวดเร็ว:

myWebView.isUserInteractionEnabled = false

สิ่งนี้ใช้ได้ผลในสถานการณ์ของฉันที่ฉันมีมุมมองย่อยของปุ่ม ฉันปิดการใช้งานการโต้ตอบในมุมมองย่อยและปุ่มใช้งานได้
simple_code

2
ใน Swift 4myWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont

120

สำหรับการส่งผ่านสัมผัสจากมุมมองซ้อนทับไปยังมุมมองด้านล่างให้ใช้วิธีการต่อไปนี้ใน UIView:

วัตถุประสงค์ -C:

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

สวิฟต์ 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
}

4
ฉันมีปัญหาเดียวกัน แต่สิ่งที่ฉันต้องการคือถ้าฉันกำลังซูม / หมุน / ย้ายมุมมองด้วยท่าทางมันควรจะกลับมาใช่และสำหรับเหตุการณ์ที่เหลือควรคืนค่าไม่ใช่ฉันจะทำสิ่งนี้ได้อย่างไร
Minakshi

@ มินัคชิเป็นคำถามที่แตกต่างไปจากเดิมโดยสิ้นเชิงถามคำถามใหม่สำหรับสิ่งนั้น
Fattie

แต่ระวังวิธีง่ายๆนี้ไม่ให้คุณมีปุ่มและอื่น ๆ ที่ด้านบนของเลเยอร์ที่เป็นปัญหา!
Fattie

@Minakshi สวัสดีคุณพบคำตอบสำหรับคำถามของคุณหรือไม่?
Lance Samaria

57

นี่เป็นกระทู้เก่า แต่มาจากการค้นหาฉันจึงคิดว่าจะเพิ่ม 2c ของฉัน ฉันมี UIView ที่ครอบคลุมพร้อมมุมมองย่อยและต้องการสกัดกั้นการสัมผัสที่กดหนึ่งในมุมมองย่อยเท่านั้นดังนั้นฉันจึงแก้ไขคำตอบของ PixelCloudSt เป็น

-(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
คุณต้องสร้างคลาสย่อย UIView ที่กำหนดเอง (หรือคลาสย่อยของคลาสย่อย UIView อื่น ๆ เช่น UIImageView หรืออะไรก็ตาม) และแทนที่ฟังก์ชันนี้ โดยพื้นฐานแล้วคลาสย่อยสามารถมี "@interface" ที่ว่างเปล่าทั้งหมดในไฟล์. h และฟังก์ชันด้านบนเป็นเนื้อหาทั้งหมดของ "@implementation"
fresidue

ขอบคุณนี่คือสิ่งที่ฉันต้องการในการสัมผัสผ่านพื้นที่ว่างของ UIView เพื่อจัดการกับมุมมองด้านหลัง +100
Danny

45

คำตอบ @fresidue เวอร์ชันปรับปรุง คุณสามารถใช้UIViewคลาสย่อยนี้เป็นมุมมองโปร่งใสที่ผ่านการสัมผัสภายนอกมุมมองย่อยได้ การนำไปใช้ในObjective-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

.

และใน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)
    })
  }
}

เคล็ดลับ:

สมมติว่าคุณมีแผง "ที่ยึด" ขนาดใหญ่ซึ่งอาจมีมุมมองตารางอยู่ด้านหลัง คุณทำให้ "ผู้ถือ" PassthroughViewแผง ตอนนี้ใช้งานได้คุณสามารถเลื่อนตาราง "ผ่าน" ตัวยึด "ได้

แต่!

  1. ด้านบนของแผง "ตัวยึด" คุณมีป้ายกำกับหรือไอคอน อย่าลืมแน่นอนว่าสิ่งเหล่านี้จะต้องถูกทำเครื่องหมายว่าเปิดใช้งานการโต้ตอบกับผู้ใช้เป็นปิด!

  2. ด้านบนของแผง "ตัวยึด" คุณมีปุ่มบางปุ่ม อย่าลืมแน่นอนว่าสิ่งเหล่านี้จะต้องถูกทำเครื่องหมายว่าเปิดใช้งานการโต้ตอบกับผู้ใช้!

  3. หมายเหตุที่ค่อนข้างสับสนว่า "ผู้ถือ" ตัวเอง - มุมมองที่คุณใช้PassthroughViewบน - โต้ตอบกับผู้ใช้จะต้องทำเครื่องหมายที่เปิดใช้งานON ! นั่นเป็นON !! (มิฉะนั้นโค้ดใน PassthroughView ก็จะไม่ถูกเรียกเลย)


1
ฉันใช้วิธีแก้ปัญหาสำหรับ Swift แล้ว มันทำงานบน ios 11 แต่เกิดข้อขัดข้องทุกครั้งบน iOS 10 แสดงการเข้าถึงที่ไม่ถูกต้อง ความคิดใด ๆ ?
Soumen

คุณช่วยชีวิตฉันไว้มันได้ผลในการส่งผ่านการสัมผัสไปยัง UIViews ด้านล่าง
AaoIi

1
อย่าลืมตรวจสอบด้วย$0.isUserInteractionEnabled
Sean Thielen

8

ฉันมีปัญหาคล้ายกันกับ UIStackView (แต่อาจเป็นมุมมองอื่นก็ได้) การกำหนดค่าของฉันมีดังต่อไปนี้: ดูคอนโทรลเลอร์ด้วย containerView และ StackView

เป็นกรณีคลาสสิกที่ฉันมีคอนเทนเนอร์ที่ต้องวางไว้ด้านหลังโดยมีปุ่มอยู่ด้านข้าง เพื่อวัตถุประสงค์ในการจัดวางฉันรวมปุ่มไว้ใน UIStackView แต่ตอนนี้ส่วนตรงกลาง (ว่าง) ของการสกัดกั้น stackView สัมผัส :-(

สิ่งที่ฉันทำคือสร้างคลาสย่อยของ UIStackView ด้วยคุณสมบัติที่กำหนดมุมมองย่อยที่ควรสัมผัสได้ ตอนนี้การสัมผัสใด ๆ ที่ปุ่มด้านข้าง (รวมอยู่ในอาร์เรย์ * viewsWithActiveTouch *) จะถูกมอบให้กับปุ่มในขณะที่การสัมผัสบน stackview ที่ใดก็ได้นอกเหนือจากมุมมองเหล่านี้จะไม่ถูกดักจับดังนั้นจึงส่งผ่านไปยังสิ่งที่อยู่ด้านล่างสแต็ก ดู.

/** 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

หากมุมมองที่คุณต้องการส่งต่อการสัมผัสไม่ได้เป็นมุมมองย่อย / ซูเปอร์วิวคุณสามารถตั้งค่าคุณสมบัติที่กำหนดเองในคลาสย่อย UIView ของคุณได้ดังนี้:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

อย่าลืมสังเคราะห์ใน. m ของคุณ:

@synthesize forwardableTouchee;

จากนั้นรวมสิ่งต่อไปนี้ในวิธีการ UIResponder ของคุณเช่น:

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

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

}

เมื่อใดก็ตามที่คุณสร้างอินสแตนซ์ UIView ของคุณให้ตั้งค่าคุณสมบัติ forwardableTouchee เป็นมุมมองใดก็ได้ที่คุณต้องการให้ส่งต่อเหตุการณ์ไปที่:

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

5

ฉันต้องการสัมผัสผ่าน UIStackView ภายใน UIView นั้นโปร่งใส แต่ UIStackView ใช้การสัมผัสทั้งหมด สิ่งนี้ใช้ได้ผลสำหรับฉัน:

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

การจัดเรียงทั้งหมดยังคงได้รับการสัมผัส แต่การสัมผัสบน UIStackView นั้นผ่านไปยังมุมมองด้านล่าง (สำหรับฉัน mapView)


เพียงแค่ต้องการเพิ่มคุณสามารถใช้สิ่งนี้ได้โดยการขยาย UIView เช่นกัน นี่คือบทความที่ใช้วิธีการเดียวกันนี้ซึ่งจะให้รายละเอียดเพิ่มเติมเล็กน้อยรวมถึงกรณีการใช้งานอื่น ๆ : medium.com/@nguyenminhphuc/…
Kacy

4

ใน 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
    }
}

2

ดูเหมือนว่าคุณจะมีคำตอบมากมายที่นี่ แต่ก็ไม่มีใครทำความสะอาดได้อย่างรวดเร็วอย่างที่ฉันต้องการ ดังนั้นฉันจึงรับคำตอบจาก @fresidue ที่นี่และแปลงให้รวดเร็วเพราะตอนนี้นักพัฒนาส่วนใหญ่ต้องการใช้ที่นี่

มันแก้ปัญหาของฉันที่ฉันมีแถบเครื่องมือโปร่งใสพร้อมปุ่ม แต่ฉันต้องการให้แถบเครื่องมือมองไม่เห็นสำหรับผู้ใช้และเหตุการณ์การสัมผัสควรผ่านไป

isUserInteractionEnabled = false เนื่องจากบางส่วนระบุว่าไม่ใช่ตัวเลือกตามการทดสอบของฉัน

 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

ฉันมีป้ายกำกับสองสามป้ายใน StackView และฉันไม่ประสบความสำเร็จมากนักกับวิธีแก้ปัญหาข้างต้นฉันแก้ปัญหาโดยใช้รหัสด้านล่างแทน:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

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
    }

}

จากนั้นคุณสามารถทำสิ่งต่างๆเช่น:

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

0

ลองอะไรแบบนี้ ...

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

โค้ดด้านบนในวิธีการ touchBegan ของคุณเช่นจะส่งต่อการสัมผัสไปยังมุมมองย่อยทั้งหมดของมุมมอง


6
ปัญหาเกี่ยวกับแนวทางนี้ AFAIK คือการพยายามส่งต่อการสัมผัสไปยังคลาสของ UIKit framework ส่วนใหญ่มักจะไม่มีผล ตามเอกสารของ Apple คลาส UIKit framework ไม่ได้ออกแบบมาเพื่อรับสัมผัสที่มีการตั้งค่าคุณสมบัติมุมมองเป็นมุมมองอื่น ดังนั้นแนวทางนี้จึงใช้ได้กับคลาสย่อยที่กำหนดเองของ UIView เป็นส่วนใหญ่
maciejs

0

สถานการณ์ที่ฉันพยายามทำคือสร้างแผงควบคุมโดยใช้ตัวควบคุมภายใน UIStackView ที่ซ้อนกัน การควบคุมบางอย่างมี UITextField อื่น ๆ ที่มี UIButton นอกจากนี้ยังมีป้ายกำกับเพื่อระบุการควบคุม สิ่งที่ฉันต้องการทำคือวางปุ่ม "มองไม่เห็น" ขนาดใหญ่ไว้ด้านหลังแผงควบคุมเพื่อที่ว่าหากผู้ใช้แตะบนพื้นที่นอกปุ่มหรือช่องข้อความฉันก็จะสามารถจับสิ่งนั้นและดำเนินการได้โดยหลักแล้วจะปิดแป้นพิมพ์ใด ๆ หากมีข้อความ ฟิลด์ทำงานอยู่ (ลาออกเฟิร์สเรสปอนเดอร์) อย่างไรก็ตามการแตะที่ฉลากหรือพื้นที่ว่างอื่น ๆ ในแผงควบคุมจะไม่ผ่านสิ่งต่างๆ การสนทนาข้างต้นเป็นประโยชน์ในการหาคำตอบของฉันด้านล่าง

โดยพื้นฐานแล้วฉันจัดประเภท UIStackView ย่อยและเขียนทับรูทีน“ point (inside: with) เพื่อค้นหาประเภทของการควบคุมที่ต้องใช้การสัมผัสและ“ ละเว้น” สิ่งต่างๆเช่นป้ายกำกับที่ฉันต้องการละเว้น นอกจากนี้ยังตรวจสอบภายใน UIStackView เพื่อให้สิ่งต่างๆสามารถกลับคืนสู่โครงสร้างแผงควบคุมได้

รหัสอาจมีรายละเอียดมากกว่าที่ควรจะเป็นเล็กน้อย แต่จะมีประโยชน์ในการแก้ไขข้อบกพร่องและหวังว่าจะให้ความชัดเจนมากขึ้นในสิ่งที่กิจวัตรกำลังทำ ตรวจสอบให้แน่ใจว่าใน Interface Builder เปลี่ยนคลาสของ UIStackView เป็น 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

ตามที่แนะนำโดย @PixelCloudStv หากคุณต้องการโยนสัมผัสจากมุมมองหนึ่งไปยังอีกมุมมองหนึ่ง แต่มีการควบคุมเพิ่มเติมเกี่ยวกับกระบวนการนี้ - คลาสย่อย UIView

//หัวข้อ

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

// การใช้งาน

#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

หลังจากใน interfaceBuilder เพียงแค่ตั้งค่าคลาสของ View เป็น TouchView และตั้งค่า rect ที่ใช้งานอยู่ด้วย rect ของคุณ นอกจากนี้คุณสามารถเปลี่ยนและใช้ตรรกะอื่น ๆ

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