เป็นไปได้ไหมที่ UIButton (หรือส่วนควบคุมอื่น ๆ สำหรับเรื่องนั้น) จะรับเหตุการณ์การสัมผัสเมื่อเฟรมของ UIButton อยู่นอกเฟรมของผู้ปกครอง ทำให้เมื่อฉันลองสิ่งนี้ UIButton ของฉันดูเหมือนจะไม่สามารถรับเหตุการณ์ใด ๆ ได้ ฉันจะแก้ไขปัญหานี้ได้อย่างไร
เป็นไปได้ไหมที่ UIButton (หรือส่วนควบคุมอื่น ๆ สำหรับเรื่องนั้น) จะรับเหตุการณ์การสัมผัสเมื่อเฟรมของ UIButton อยู่นอกเฟรมของผู้ปกครอง ทำให้เมื่อฉันลองสิ่งนี้ UIButton ของฉันดูเหมือนจะไม่สามารถรับเหตุการณ์ใด ๆ ได้ ฉันจะแก้ไขปัญหานี้ได้อย่างไร
คำตอบ:
ใช่. คุณสามารถแทนที่hitTest:withEvent:
วิธีการคืนค่ามุมมองสำหรับชุดของจุดที่ใหญ่กว่าที่มุมมองนั้นมีได้ ดูUIView ชั้นอ้างอิง
แก้ไข:ตัวอย่าง:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat radius = 100.0;
CGRect frame = CGRectMake(-radius, -radius,
self.frame.size.width + radius,
self.frame.size.height + radius);
if (CGRectContainsPoint(frame, point)) {
return self;
}
return nil;
}
แก้ไข 2: (หลังจากการชี้แจง :) เพื่อให้แน่ใจว่าปุ่มได้รับการปฏิบัติว่าอยู่ในขอบเขตของผู้ปกครองคุณต้องแทนที่pointInside:withEvent:
ในพาเรนต์เพื่อรวมกรอบของปุ่ม
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(self.view.bounds, point) ||
CGRectContainsPoint(button.view.frame, point))
{
return YES;
}
return NO;
}
โปรดสังเกตว่ารหัสตรงนั้นสำหรับการแทนที่ pointInside นั้นไม่ถูกต้องนัก ตามที่ Summon อธิบายไว้ด้านล่างให้ทำสิ่งนี้:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
return YES;
return [super pointInside:point withEvent:event];
}
โปรดทราบว่าคุณมีแนวโน้มที่จะใช้self.oversizeButton
เป็น IBOutlet ในคลาสย่อย UIView นี้ จากนั้นคุณสามารถลาก "ปุ่มขนาดใหญ่" ที่ต้องการไปยังมุมมองพิเศษที่เป็นปัญหาได้ (หรือหากคุณทำสิ่งนี้เป็นจำนวนมากในโครงการด้วยเหตุผลบางประการคุณจะมีคลาสย่อย UIButton พิเศษและคุณสามารถดูรายการมุมมองย่อยของชั้นเรียนเหล่านั้นได้) หวังว่าจะช่วยได้
pointInside:withEvent:
เพื่อรวมเฟรมของปุ่ม ฉันจะเพิ่มโค้ดตัวอย่างในคำตอบด้านบน
hitTest:withEvent:
และpointInside:withEvent:
พื้นที่ที่ไม่เรียกหาฉันเมื่อฉันกดปุ่มที่อยู่นอกขอบเขตของผู้ปกครอง มีอะไรผิดพลาดได้บ้าง?
@jnic ฉันกำลังทำงานกับ iOS SDK 5.0 และเพื่อให้โค้ดของคุณทำงานได้อย่างถูกต้องฉันต้องทำสิ่งนี้:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
return YES;
}
return [super pointInside:point withEvent:event]; }
มุมมองคอนเทนเนอร์ในกรณีของฉันคือ UIButton และองค์ประกอบลูกทั้งหมดยังเป็น UIButtons ที่สามารถย้ายออกนอกขอบเขตของ UIButton หลัก
ดีที่สุด
ในมุมมองระดับบนสุดคุณสามารถแทนที่วิธีการทดสอบ Hit:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];
if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
return [_myButton hitTest:translatedPoint withEvent:event];
}
return [super hitTest:point withEvent:event];
}
ในกรณีนี้หากจุดอยู่ในขอบเขตของปุ่มคุณจะโอนสายไปที่นั่น หากไม่เป็นเช่นนั้นให้เปลี่ยนกลับไปใช้การใช้งานเดิม
ในกรณีของฉันฉันมีUICollectionViewCell
คลาสย่อยที่มีไฟล์UIButton
. ฉันปิดการใช้งานclipsToBounds
บนเซลล์และปุ่มนี้มองเห็นได้นอกขอบเขตของเซลล์ อย่างไรก็ตามปุ่มไม่ได้รับเหตุการณ์การสัมผัส ฉันสามารถตรวจจับเหตุการณ์การสัมผัสบนปุ่มได้โดยใช้คำตอบของแจ็ค: https://stackoverflow.com/a/30431157/3344977
นี่คือเวอร์ชัน Swift:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let translatedPoint = button.convertPoint(point, fromView: self)
if (CGRectContainsPoint(button.bounds, translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, withEvent: event)
}
return super.hitTest(point, withEvent: event)
}
Swift 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let translatedPoint = button.convert(point, from: self)
if (button.bounds.contains(translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, with: event)
}
return super.hitTest(point, with: event)
}
เหตุใดจึงเกิดขึ้น
เนื่องจากเมื่อมุมมองย่อยของคุณอยู่นอกขอบเขตของซูเปอร์วิวของคุณเหตุการณ์การสัมผัสที่เกิดขึ้นจริงในมุมมองย่อยนั้นจะไม่ถูกส่งไปยังมุมมองย่อยนั้น อย่างไรก็ตามจะส่งไปยัง superview
ไม่ว่าจะมีการตัดมุมมองย่อยด้วยภาพหรือไม่ก็ตามเหตุการณ์การสัมผัสจะเคารพกรอบสี่เหลี่ยมของมุมมองยอดเยี่ยมของมุมมองเป้าหมายเสมอ กล่าวอีกนัยหนึ่งคือเหตุการณ์การสัมผัสที่เกิดขึ้นในส่วนหนึ่งของมุมมองที่อยู่นอกกรอบสี่เหลี่ยมขอบเขตของ superview จะไม่ถูกส่งไปยังมุมมองนั้น ลิงค์
สิ่งที่คุณต้องทำ?
เมื่อซูเปอร์วิวของคุณได้รับเหตุการณ์การสัมผัสที่กล่าวถึงข้างต้นคุณจะต้องบอก UIKit อย่างชัดเจนว่ามุมมองย่อยของฉันควรเป็นผู้ที่จะได้รับเหตุการณ์สัมผัสนี้
แล้วโค้ดล่ะ?
ใน superview ของคุณใช้ func hitTest(_ point: CGPoint, with event: UIEvent?)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
// convert the point into subview's coordinate system
let subviewPoint = self.convert(point, to: subview)
// if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
return super.hitTest(point, with: event)
}
Gotchya ที่น่าสนใจ: คุณต้องไปที่ "superview ที่เล็กเกินไป"
คุณต้องไป "ขึ้น" ไปที่มุมมอง "สูงสุด" ซึ่งมุมมองปัญหาอยู่ภายนอก
ตัวอย่างทั่วไป:
สมมติว่าคุณมีหน้าจอ S พร้อมมุมมองคอนเทนเนอร์ C. มุมมองของคอนเทนเนอร์วิวคอนโทรลเลอร์คือ V. (เรียกคืน V จะอยู่ด้านในของ C และมีขนาดเท่ากัน) V มีมุมมองย่อย (อาจเป็นปุ่ม) B B คือปัญหา มุมมองที่อยู่นอก V.
แต่สังเกตว่า B ก็อยู่นอก C เช่นกัน
ในตัวอย่างนี้คุณต้องใช้วิธีการแก้ปัญหาoverride hitTest
ในความเป็นจริงที่ C ไม่ V หากคุณใช้กับ V - จะไม่ทำอะไรเลย
รวดเร็ว:
โดยที่ targetView คือมุมมองที่คุณต้องการรับเหตุการณ์ (ซึ่งอาจอยู่นอกกรอบของมุมมองระดับบนสุดทั้งหมดหรือบางส่วน)
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
return closeButton
}
return super.hitTest(point, withEvent: event)
}
สำหรับฉัน (สำหรับคนอื่น ๆ ) คำตอบคือไม่ได้แทนที่hitTest
วิธีการ แต่เป็นpointInside
วิธีการ ฉันต้องทำสิ่งนี้เพียงสองแห่ง
Superview
นี่คือมุมมองที่หากclipsToBounds
ตั้งค่าเป็นจริงจะทำให้ปัญหานี้หมดไป นี่เป็นวิธีง่ายๆของฉันUIView
ใน Swift 3:
class PromiscuousView : UIView {
override func point(inside point: CGPoint,
with event: UIEvent?) -> Bool {
return true
}
}
Subview
ระยะทางของคุณอาจแตกต่างกันไป แต่ในกรณีของฉันฉันต้องเขียนทับpointInside
วิธีการสำหรับมุมมองย่อยที่ได้ตัดสินใจว่าการสัมผัสนั้นอยู่นอกขอบเขต เหมืองอยู่ใน Objective-C ดังนั้น:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
WEPopoverController *controller = (id) self.delegate;
if (self.takeHitsOutside) {
return YES;
} else {
return [super pointInside:point withEvent:event];
}
}
คำตอบนี้ (และคำถามอื่น ๆ อีกสองสามข้อ) สามารถช่วยให้คุณเข้าใจชัดเจนขึ้น
อัปเดต swift 4.1 แล้ว
หากต้องการรับเหตุการณ์การสัมผัสใน UIView เดียวกันคุณเพียงแค่เพิ่มวิธีการลบล้างต่อไปนี้มันจะระบุมุมมองเฉพาะที่แตะใน UIView ซึ่งอยู่นอกขอบเขต
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
if btnAngle.bounds.contains(pointforTargetview) {
return self
}
return super.hitTest(point, with: event)
}