อนุญาตให้โต้ตอบกับ UIView ภายใต้ UIView อื่น


115

มีวิธีง่ายๆในการอนุญาตให้โต้ตอบกับปุ่มใน UIView ที่อยู่ใต้ UIView อื่นหรือไม่โดยที่ไม่มีวัตถุจริงจาก UIView ด้านบนที่ด้านบนของปุ่ม

ตัวอย่างเช่นในขณะนี้ฉันมี UIView (A) ที่มีวัตถุอยู่ด้านบนและวัตถุที่ด้านล่างของหน้าจอและไม่มีอะไรอยู่ตรงกลาง อยู่ด้านบนของ UIView อื่นที่มีปุ่มตรงกลาง (B) อย่างไรก็ตามฉันไม่สามารถโต้ตอบกับปุ่มที่อยู่ตรงกลางของ B ได้

ฉันเห็นปุ่มใน B - ฉันได้ตั้งค่าพื้นหลังของ A เป็น clearColor - แต่ปุ่มใน B ดูเหมือนจะไม่ได้รับการสัมผัสแม้ว่าจะไม่มีวัตถุจาก A อยู่ที่ด้านบนของปุ่มเหล่านั้นก็ตาม

แก้ไข - ฉันยังต้องการที่จะสามารถโต้ตอบกับวัตถุใน UIView ด้านบน

แน่นอนว่ามีวิธีง่ายๆในการทำเช่นนี้?


2
อธิบายได้ทั้งหมดที่นี่: developer.apple.com/iphone/library/documentation/iPhone/… แต่โดยพื้นฐานแล้วให้แทนที่ hitTest: withEvent: พวกเขายังจัดหาตัวอย่างโค้ด
nash

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

คำตอบ:


97

คุณควรสร้างคลาสย่อย UIView สำหรับมุมมองด้านบนของคุณและแทนที่วิธีการต่อไปนี้:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // UIView will be "transparent" for touch events if we return NO
    return (point.y < MIDDLE_Y1 || point.y > MIDDLE_Y2);
}

คุณอาจดูวิธี hitTest: event:


19
middle_y1 / y2 แสดงที่นี่คืออะไร
Jason Renaldo

ไม่แน่ใจว่าreturnคำพูดนั้นใช้ทำอะไร แต่return CGRectContainsPoint(eachSubview.frame, point)ใช้ได้กับฉัน คำตอบที่เป็นประโยชน์อย่างยิ่ง
n00neimp0rtant

ธุรกิจ MIDDLE_Y1 / Y2 เป็นเพียงตัวอย่าง ฟังก์ชันนี้จะ "โปร่งใส" สำหรับเหตุการณ์การสัมผัสในMIDDLE_Y1<=y<=MIDDLE_Y2พื้นที่
gyim

41

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

คำตอบนี้จะนำมาจากคำตอบที่ผมมอบให้คำถามที่คล้ายกันที่นี่

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self) return nil;
    return hitView;
}

[super hitTest:point withEvent:event]จะส่งคืนมุมมองที่ลึกที่สุดในลำดับชั้นของมุมมองนั้นที่ถูกสัมผัส ถ้าhitView == self(กล่าวคือหากไม่มีnilมุมมองย่อยใต้จุดสัมผัส) ให้ส่งกลับโดยระบุว่ามุมมองนี้ไม่ควรรับการสัมผัส วิธีการทำงานของลูกโซ่การตอบกลับหมายความว่าลำดับชั้นมุมมองที่อยู่เหนือจุดนี้จะยังคงถูกส่งผ่านไปเรื่อย ๆ จนกว่าจะพบมุมมองที่จะตอบสนองต่อการสัมผัส อย่าส่งคืน superview เนื่องจากไม่ได้ขึ้นอยู่กับมุมมองนี้ว่า superview ควรยอมรับการสัมผัสหรือไม่!

วิธีนี้คือ:

  • สะดวกเพราะไม่ต้องมีการอ้างอิงถึงมุมมอง / มุมมองย่อย / วัตถุอื่นใด
  • ทั่วไปเนื่องจากใช้กับมุมมองใด ๆ ที่ทำหน้าที่เป็นคอนเทนเนอร์สำหรับมุมมองย่อยที่สัมผัสได้อย่างหมดจดและการกำหนดค่าของมุมมองย่อยจะไม่มีผลต่อวิธีการทำงาน (เช่นเดียวกับหากคุณแทนที่pointInside:withEvent:เพื่อส่งคืนพื้นที่ที่สามารถสัมผัสได้โดยเฉพาะ)
  • เข้าใจผิดมีรหัสไม่มาก ... และแนวคิดก็ไม่ยากที่จะเข้าใจ

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

@interface ISView : UIView
@property(nonatomic, assign) BOOL onlyRespondToTouchesInSubviews;
@end

@implementation ISView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self && onlyRespondToTouchesInSubviews) return nil;
    return hitView;
}
@end

UIViewจากนั้นไปป่าและใช้มุมมองนี้ทุกที่ที่คุณอาจใช้ธรรมดา การกำหนดค่ามันเป็นง่ายๆเป็นที่ตั้งเพื่อonlyRespondToTouchesInSubviewsYES


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

วิธีแก้ปัญหาอื่น ๆ ที่ใช้จุดบางอย่างมีปัญหาหากมุมมองของคุณเลื่อนได้เช่น UITableView og UICollectionView และคุณเลื่อนขึ้นหรือลง อย่างไรก็ตามโซลูชันนี้ใช้งานได้โดยไม่คำนึงถึงการเลื่อน
pajevic

31

มีหลายวิธีที่คุณสามารถจัดการกับปัญหานี้ได้ สิ่งที่ฉันชอบคือการแทนที่ hitTest: withEvent: ในมุมมองที่เป็น superview ทั่วไป (อาจจะเป็นทางอ้อม) ไปยังมุมมองที่ขัดแย้งกัน (ดูเหมือนคุณจะเรียกสิ่งเหล่านี้ว่า A และ B) ตัวอย่างเช่นสิ่งนี้ (ในที่นี้ A และ B คือตัวชี้ UIView โดยที่ B คือค่าที่ "ซ่อน" ซึ่งโดยปกติจะถูกละเว้น):

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInB = [B convertPoint:point fromView:self];

    if ([B pointInside:pointInB withEvent:event])
        return B;

    return [super hitTest:point withEvent:event];
}

คุณยังสามารถปรับเปลี่ยนpointInside:withEvent:วิธีการตามที่ gyim แนะนำ วิธีนี้ช่วยให้คุณได้ผลลัพธ์เดียวกันโดยการ "เจาะรู" อย่างมีประสิทธิภาพใน A อย่างน้อยก็สำหรับการสัมผัส

อีกวิธีหนึ่งคือการส่งต่อเหตุการณ์ซึ่งหมายถึงการลบล้างtouchesBegan:withEvent:และวิธีการที่คล้ายกัน (เช่นtouchesMoved:withEvent:ฯลฯ ) เพื่อส่งสัมผัสบางอย่างไปยังวัตถุที่แตกต่างจากที่ที่พวกเขาไปครั้งแรก ตัวอย่างเช่นใน A คุณสามารถเขียนดังนี้:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([self shouldForwardTouches:touches]) {
        [B touchesBegan:touches withEvent:event];
    }
    else {
        // Do whatever A does with touches.
    }
}

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

มีบล็อกโพสต์ที่ดีที่อธิบายรายละเอียดทั้งหมดนี้พร้อมกับโครงการ xcode ที่ใช้งานได้ขนาดเล็กเพื่อสาธิตแนวคิดที่มีอยู่ที่นี่:

http://bynomial.com/blog/?p=74


ปัญหาในการแก้ปัญหานี้ - การลบล้าง hitTest บน superview ทั่วไปคือไม่อนุญาตให้มุมมองด้านล่างทำงานได้อย่างถูกต้องทั้งหมดเมื่อมุมมองด้านล่างเป็นหนึ่งในมุมมองที่เลื่อนได้ทั้งชุด (MapView ฯลฯ ) การลบล้างจุดภายในมุมมองด้านบนตามคำแนะนำของ gyim ด้านล่างใช้งานได้ในทุกกรณีเท่าที่ฉันสามารถบอกได้
delany

@delany นั่นไม่เป็นความจริง คุณสามารถมีมุมมองที่เลื่อนได้ซึ่งอยู่ใต้มุมมองอื่น ๆ และปล่อยให้ทั้งคู่ทำงานโดยการลบล้าง hitTest นี่คือตัวอย่างโค้ด: bynomial.com/blogfiles/Temp32.zip
Tyler

เฮ้ ไม่ใช่ทุกมุมมองที่เลื่อนได้ - เพียงบางส่วน ... ฉันลองใช้รหัสไปรษณีย์ของคุณจากด้านบนเปลี่ยน UIScrollView เป็น MKMapView (เช่น) และไม่ได้ผล Taps work - เป็นการเลื่อนที่ดูเหมือนจะเป็นปัญหา
delany

โอเคฉันตรวจสอบแล้วและยืนยันว่า hitTest ไม่ทำงานอย่างที่คุณต้องการกับ MKMapView คุณพูดถูกแล้ว @delany; แม้ว่าจะทำงานได้อย่างถูกต้องกับ UIScrollView's ฉันสงสัยว่าทำไม MKMapView จึงล้มเหลว
Tyler

@Tyler ดังที่คุณกล่าวไว้ว่า "สิ่งสำคัญคือการควบคุมในตัวเช่น UIButton มักจะเพิกเฉยต่อการสัมผัสที่ส่งต่อ" ฉันอยากรู้ว่าคุณรู้ได้อย่างไรและเป็นเอกสารอย่างเป็นทางการที่จะอธิบายพฤติกรรมนี้หรือไม่ ฉันประสบปัญหาเมื่อ UIButton มีมุมมองย่อย UIView ธรรมดามันจะไม่ตอบสนองต่อเหตุการณ์การสัมผัสในขอบเขตของมุมมองย่อย และฉันพบว่าเหตุการณ์ถูกส่งต่อไปยัง UIButton อย่างถูกต้องเป็นพฤติกรรมเริ่มต้นของ UIView แต่ฉันไม่แน่ใจว่าเป็นคุณสมบัติที่กำหนดหรือเป็นเพียงข้อบกพร่อง คุณช่วยแสดงเอกสารเกี่ยวกับเรื่องนี้ให้ฉันดูได้ไหม ขอบคุณมากด้วยความจริงใจ
Neal

29

คุณต้องตั้งค่าupperView.userInteractionEnabled = NO;มิฉะนั้นมุมมองด้านบนจะขัดขวางการสัมผัส

เวอร์ชันของ Interface Builder คือช่องทำเครื่องหมายที่ด้านล่างของแผง View Attributes ที่เรียกว่า "User Interaction Enabled" ยกเลิกการเลือกและคุณควรจะไป


ขอโทษ - ควรจะพูด ฉันยังคงต้องการที่จะสามารถโต้ตอบกับวัตถุใน UIView ด้านบน
delany

แต่ UpperView ไม่สามารถรับสัมผัสใด ๆ ได้ให้รวมปุ่มไว้ใน upperView
imcaptor

2
วิธีนี้ใช้ได้ผลสำหรับฉัน ฉันไม่ต้องการให้มุมมองด้านบนตอบสนองต่อการสัมผัสเลย
TJ

11

การใช้งาน pointInside: withEvent แบบกำหนดเอง: ดูเหมือนจะเป็นหนทางที่จะไป แต่การจัดการกับพิกัดแบบฮาร์ดโค้ดดูเหมือนจะแปลกสำหรับฉัน ดังนั้นฉันจึงตรวจสอบว่า CGPoint อยู่ในปุ่ม CGRect หรือไม่โดยใช้ฟังก์ชัน CGRectContainsPoint ():

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    return (CGRectContainsPoint(disclosureButton.frame, point));
}

8

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

วิธีนี้ค่อนข้างดีกว่าคำตอบที่ยอมรับเนื่องจากคุณยังคงสามารถคลิกที่ส่วนUIButtonที่เป็นกึ่งโปร่งใสได้UIViewในขณะที่ส่วนที่ไม่โปร่งใสของUIViewจะยังคงตอบสนองต่อเหตุการณ์การสัมผัส

GIF

ดังที่คุณเห็นใน GIF ปุ่ม Giraffe เป็นรูปสี่เหลี่ยมผืนผ้าธรรมดา ๆ แต่เหตุการณ์การสัมผัสบนพื้นที่โปร่งใสจะถูกส่งต่อไปยังสีเหลืองUIButtonด้านล่าง

ลิงก์ไปยังชั้นเรียน


2
เนื่องจากรหัสของคุณมีความยาวไม่มากนักคุณจึงควรรวมส่วนที่เกี่ยวข้องไว้ในคำตอบของคุณในกรณีที่โครงการของคุณถูกย้ายหรือนำออกในอนาคต
Gavin

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

@ บรูซดีใจที่ช่วยคุณได้!
Segev

4

ฉันเดาว่าฉันไปปาร์ตี้นี้ช้าไปหน่อย แต่ฉันจะเพิ่มวิธีแก้ปัญหาที่เป็นไปได้นี้:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView != self) return hitView;
    return [self superview];
}

หากคุณใช้รหัสนี้เพื่อแทนที่ฟังก์ชัน hitTest มาตรฐานของ UIView ที่กำหนดเองจะไม่สนใจเฉพาะมุมมองเท่านั้น การดูย่อยใด ๆ ของการดูนั้นจะส่งคืนการเข้าชมตามปกติและการเข้าชมใด ๆ ที่จะเข้าสู่การดูนั้นจะถูกส่งต่อไปยังมุมมองขั้นสูง

-เถ้า


1
[self superview]นี้เป็นวิธีการที่ต้องการของฉันยกเว้นผมไม่คิดว่าคุณควรจะได้รับกลับมา เอกสารประกอบเกี่ยวกับสถานะวิธีนี้ "ส่งคืนลูกหลานที่อยู่ไกลที่สุดของผู้รับในลำดับชั้นของมุมมอง (รวมถึงตัวมันเอง) ที่มีจุดที่ระบุ" และ "คืนค่าศูนย์ถ้าจุดนั้นอยู่นอกลำดับชั้นมุมมองของผู้รับโดยสมบูรณ์" nilฉันคิดว่าคุณควรจะได้รับกลับมา เมื่อคุณคืนค่าศูนย์การควบคุมจะส่งผ่านไปยังซูเปอร์วิวเพื่อตรวจสอบว่ามี Hit หรือไม่ ดังนั้นโดยพื้นฐานแล้วมันจะทำในสิ่งเดียวกันยกเว้นการกลับมาซุปเปอร์วิวอาจทำให้บางอย่างพังในอนาคต
jasongregori

แน่นอนว่านั่นอาจจะเป็นการฉลาด (สังเกตวันที่ในคำตอบเดิมของฉัน - ทำการเข้ารหัสมากขึ้นตั้งแต่นั้นมา)
Ash

4

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

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // If one of our subviews wants it, return YES
    for (UIView *subview in self.subviews) {
        CGPoint pointInSubview = [subview convertPoint:point fromView:self];
        if ([subview pointInside:pointInSubview withEvent:event]) {
            return YES;
        }
    }
    // otherwise return NO, as if userInteractionEnabled were NO
    return NO;
}

หมายเหตุ:คุณไม่จำเป็นต้องทำการเรียกซ้ำในแผนผังมุมมองย่อยเพราะแต่ละpointInside:withEvent:วิธีจะจัดการสิ่งนั้นให้กับคุณ


3

การตั้งค่าคุณสมบัติ userInteraction ปิดใช้งานอาจช่วยได้ เช่น:

UIView * topView = [[TOPView alloc] initWithFrame:[self bounds]];
[self addSubview:topView];
[topView setUserInteractionEnabled:NO];

(หมายเหตุ: ในโค้ดด้านบน 'self' หมายถึงมุมมอง)

ด้วยวิธีนี้คุณจะแสดงได้เฉพาะมุมมองด้านบน แต่จะไม่ได้รับอินพุตจากผู้ใช้ ผู้ใช้สัมผัสทั้งหมดจะผ่านมุมมองนี้และมุมมองด้านล่างจะตอบสนองต่อพวกเขา ฉันจะใช้ topView นี้เพื่อแสดงภาพโปร่งใสหรือทำให้เคลื่อนไหว


3

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

@implementation PassThroughUIView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *v in self.subviews) {
        CGPoint localPoint = [v convertPoint:point fromView:self];
        if (v.alpha > 0.01 && ![v isHidden] && v.userInteractionEnabled && [v pointInside:localPoint withEvent:event])
            return YES;
    }
    return NO;
}

@end

2

ทางออกของฉันที่นี่:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInView = [self.toolkitController.toolbar convertPoint:point fromView:self];

    if ([self.toolkitController.toolbar pointInside:pointInView withEvent:event]) {
       self.userInteractionEnabled = YES;
    } else {
       self.userInteractionEnabled = NO;
    }

    return [super hitTest:point withEvent:event];
}

หวังว่านี่จะช่วยได้


2

มีบางอย่างที่คุณสามารถทำได้เพื่อสกัดกั้นการสัมผัสในทั้งสองมุมมอง

มุมมองด้านบน:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   // Do code in the top view
   [bottomView touchesBegan:touches withEvent:event]; // And pass them on to bottomView
   // You have to implement the code for touchesBegan, touchesEnded, touchesCancelled in top/bottom view.
}

แต่นั่นคือความคิด


สิ่งนี้เป็นไปได้อย่างแน่นอน - แต่เป็นงานจำนวนมาก (คุณจะต้องหมุนวัตถุที่ไวต่อการสัมผัสของคุณเองในเลเยอร์ด้านล่าง (เช่นปุ่ม) ฉันคิดว่า?) และดูเหมือนแปลกที่จะต้องหมุนของคุณเอง วิธีรับพฤติกรรมที่ดูเหมือนจะใช้งานง่าย
delany

ฉันไม่แน่ใจว่าเราควรจะลองดู
Alexandre Cassagne

2

นี่คือเวอร์ชัน Swift:

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
    return !CGRectContainsPoint(buttonView.frame, point)
}

2

สวิฟต์ 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}

1

ฉันไม่เคยสร้างอินเทอร์เฟซผู้ใช้ที่สมบูรณ์โดยใช้ชุดเครื่องมือ UI ดังนั้นฉันจึงไม่มีประสบการณ์กับมันมากนัก นี่คือสิ่งที่ฉันคิดว่าควรใช้งานได้

UIView ทุกตัวและ UIWindow นี้มีคุณสมบัติsubviewsซึ่งเป็น NSArray ที่มีมุมมองย่อยทั้งหมด

มุมมองย่อยแรกที่คุณเพิ่มลงในมุมมองจะได้รับดัชนี 0 และดัชนีถัดไป 1 และอื่น ๆ คุณยังสามารถแทนที่addSubview:ด้วยinsertSubview: atIndex:หรือinsertSubview:aboveSubview:และวิธีการดังกล่าวที่สามารถกำหนดตำแหน่งของมุมมองย่อยของคุณในลำดับชั้น

ดังนั้นตรวจสอบรหัสของคุณเพื่อดูว่าคุณเพิ่มมุมมองใดก่อนใน UIWindow ของคุณ นั่นจะเป็น 0 อีกอันจะเป็น 1
ตอนนี้จากมุมมองย่อยรายการหนึ่งของคุณไปยังอีกมุมมองหนึ่งคุณจะต้องทำสิ่งต่อไปนี้:

UIView * theOtherView = [[[self superview] subviews] objectAtIndex: 0];
// or using the properties syntax
UIView * theOtherView = [self.superview.subviews objectAtIndex:0];

แจ้งให้เราทราบหากใช้ได้กับกรณีของคุณ!


(ด้านล่างเครื่องหมายนี้คือคำตอบก่อนหน้าของฉัน):

หากมุมมองจำเป็นต้องสื่อสารกันควรทำผ่านคอนโทรลเลอร์ (นั่นคือใช้โมเดลMVCยอดนิยม)

เมื่อคุณสร้างมุมมองใหม่คุณสามารถตรวจสอบให้แน่ใจว่าได้ลงทะเบียนตัวเองกับคอนโทรลเลอร์

ดังนั้นเทคนิคคือตรวจสอบให้แน่ใจว่ามุมมองของคุณลงทะเบียนกับคอนโทรลเลอร์ (ซึ่งสามารถจัดเก็บตามชื่อหรืออะไรก็ได้ที่คุณต้องการในพจนานุกรมหรืออาร์เรย์) คุณสามารถให้คอนโทรลเลอร์ส่งข้อความถึงคุณหรือคุณสามารถขอข้อมูลอ้างอิงถึงมุมมองและสื่อสารกับตัวควบคุมโดยตรง

หากมุมมองของคุณไม่มีลิงก์กลับคอนโทรลเลอร์ (ซึ่งอาจเป็นเช่นนั้น) คุณสามารถใช้singletonsและ / หรือ class method เพื่ออ้างอิงถึงคอนโทรลเลอร์ของคุณได้


ขอบคุณสำหรับการตอบกลับ - แต่ฉันไม่แน่ใจว่าเข้าใจ มุมมองทั้งสองมีตัวควบคุม - ปัญหาคือด้วยมุมมองหนึ่งที่ด้านบนของอีกมุมมองด้านล่างไม่ได้รับเหตุการณ์ (และส่งต่อไปยังตัวควบคุม) แม้ว่าจะไม่มีวัตถุใดที่ 'ปิดกั้น' เหตุการณ์เหล่านั้นใน มุมมองด้านบน
delany

คุณมี UIWindow ที่ด้านบนของ UIViews หรือไม่? หากคุณทำเช่นนั้นเหตุการณ์ต่างๆควรได้รับการเผยแพร่และคุณไม่จำเป็นต้องทำ "เวทมนตร์" ใด ๆ อ่านเกี่ยวกับ [หน้าต่างและมุมมอง] [1] ที่ศูนย์ Apple Dev (และหากยังไม่ได้ผลให้เพิ่มความคิดเห็นอื่นหากสิ่งนี้ไม่สามารถช่วยคุณได้!) [1]: developer.apple.com/iphone/library/documentation/ iPhone / …
nash

ใช่แน่นอน - UIWindow ที่ด้านบนสุดของลำดับชั้น
delany

ฉันได้อัปเดตคำตอบของฉันเพื่อรวมรหัสสำหรับการดูลำดับชั้นของมุมมอง โปรดแจ้งให้เราทราบหากคุณต้องการความช่วยเหลืออื่น ๆ !
nash

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

1

ฉันคิดว่าวิธีที่ถูกต้องคือการใช้ห่วงโซ่มุมมองที่สร้างขึ้นในลำดับชั้นของมุมมอง สำหรับมุมมองย่อยของคุณที่ถูกพุชไปยังมุมมองหลักอย่าใช้ UIView ทั่วไป แต่ให้ใช้ UIView คลาสย่อยแทน (หรือตัวแปรตัวใดตัวหนึ่งเช่น UIImageView) เพื่อสร้าง MYView: UIView (หรือซุปเปอร์ประเภทใดก็ได้ที่คุณต้องการเช่น UIImageView) ในการใช้งาน YourView ให้ใช้เมธอด touchBegan วิธีนี้จะถูกเรียกใช้เมื่อสัมผัสมุมมองนั้น สิ่งที่คุณต้องมีในการใช้งานคือวิธีการของอินสแตนซ์:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ;
{   // cannot handle this event. pass off to super
    [self.superview touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]; }

สัมผัสนี้ Began เป็น API การตอบสนองดังนั้นคุณไม่จำเป็นต้องประกาศในอินเทอร์เฟซสาธารณะหรือส่วนตัวของคุณ มันเป็นหนึ่งใน Magic api ที่คุณต้องรู้ self.superview นี้จะส่งคำขอไปยัง viewController ในที่สุด ใน viewController จากนั้นใช้ touchBegan นี้เพื่อจัดการการสัมผัส

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


1

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

 for(UIGestureRecognizer *recognizer in topView.gestureRecognizers)
 {
     recognizer.delegate=self;
     [bottomView addGestureRecognizer:recognizer];   
 }
 topView.abView.userInteractionEnabled=NO; 

และการนำไปใช้UIGestureRecognizerDelegate:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

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


1

การใช้งาน Swift 4 สำหรับโซลูชันที่ใช้ HitTest

let hitView = super.hitTest(point, with: event)
if hitView == self { return nil }
return hitView

0

มาจากคำตอบที่ยอดเยี่ยมและส่วนใหญ่เข้าใจผิดของ Stuart และการใช้งานที่มีประโยชน์ของ Segev นี่คือแพ็คเกจ Swift 4 ที่คุณสามารถวางลงในโครงการใดก็ได้:

extension UIColor {
    static func colorOfPoint(point:CGPoint, in view: UIView) -> UIColor {

        var pixel: [CUnsignedChar] = [0, 0, 0, 0]

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)

        let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

        context!.translateBy(x: -point.x, y: -point.y)

        view.layer.render(in: context!)

        let red: CGFloat   = CGFloat(pixel[0]) / 255.0
        let green: CGFloat = CGFloat(pixel[1]) / 255.0
        let blue: CGFloat  = CGFloat(pixel[2]) / 255.0
        let alpha: CGFloat = CGFloat(pixel[3]) / 255.0

        let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)

        return color
    }
}

จากนั้นด้วย hitTest:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard UIColor.colorOfPoint(point: point, in: self).cgColor.alpha > 0 else { return nil }
    return super.hitTest(point, with: event)
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.