UIButton: การทำให้พื้นที่การตีมีขนาดใหญ่กว่าพื้นที่การโจมตีเริ่มต้น


188

ฉันมีคำถามเกี่ยวกับ UIButton และพื้นที่ฮิตของมัน ฉันกำลังใช้ปุ่ม Info Dark ในเครื่องมือสร้างส่วนต่อประสาน แต่ฉันพบว่าพื้นที่ Hit นั้นไม่ใหญ่พอสำหรับบางคน

มีวิธีเพิ่มพื้นที่กดของปุ่มอย่างใดอย่างหนึ่งโดยทางโปรแกรมหรือในตัวสร้างส่วนต่อประสานโดยไม่เปลี่ยนขนาดของกราฟิก InfoButton หรือไม่?


หากไม่มีอะไรใช้งานได้เพียงเพิ่ม UIButton โดยไม่มีรูปภาพจากนั้นใส่ ImageView overlay!
Varun

นี่จะเป็นคำถามที่ง่ายที่สุดในเว็บไซต์ซึ่งมีคำตอบมากมาย ฉันหมายถึงฉันสามารถวางคำตอบทั้งหมดได้ที่นี่:override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
464

คำตอบ:


137

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

ก่อนอื่นเพิ่มหมวดหมู่ลงในUIButtonนั้นจะแทนที่การทดสอบการเข้าชมและยังเพิ่มคุณสมบัติสำหรับการขยายเฟรมการทดสอบการเข้าชม

UIButton + Extensions.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + Extensions.m

#import "UIButton+Extensions.h"
#import <objc/runtime.h>

@implementation UIButton (Extensions)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }

    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);

    return CGRectContainsPoint(hitFrame, point);
}

@end

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

[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];

หมายเหตุ: อย่าลืมนำเข้าหมวดหมู่ ( #import "UIButton+Extensions.h") ในชั้นเรียนของคุณ


26
การเอาชนะวิธีในหมวดหมู่เป็นความคิดที่ไม่ดี ถ้าหมวดหมู่อื่นพยายามแทนที่ pointInside: withEvent:? และการเรียกใช้ [super pointInside: withEvent] ไม่ได้เป็นการโทรรุ่นของ UIButton (ถ้ามี) แต่เป็นการเรียกรุ่นของ UIButton
honus

@honus คุณพูดถูกและ Apple ไม่แนะนำให้ทำเช่นนี้ ที่ถูกกล่าวว่าตราบใดที่รหัสนี้ไม่ได้อยู่ในห้องสมุดที่ใช้ร่วมกันและเพียงแค่ในเครื่องของคุณคุณควรจะใช้ได้
Chase

สวัสดี Chase มีข้อเสียของการใช้คลาสย่อยแทนหรือไม่?
Sid

3
คุณกำลังทำงานที่ไม่จำเป็นมากเกินไปคุณสามารถทำโค้ดง่ายๆสองบรรทัด: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];และเพียงตั้งค่าความกว้างและความสูงที่คุณต้องการ: `backButton.frame = CGRectMake (5, 28, 45, 30); '
Resty

7
@Resty เขาใช้ภาพพื้นหลังซึ่งหมายความว่าวิธีการของคุณจะไม่ทำงาน
mattsson

77

เพียงแค่ตั้งค่าขอบภาพในเครื่องมือสร้างอินเตอร์เฟส


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

12
สิ่งนี้สามารถทำได้ในรหัส ตัวอย่างเช่นbutton.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

คำตอบของ Dave Ross นั้นยอดเยี่ยม สำหรับผู้ที่ไม่สามารถจินตนาการได้ภาพ -image ชื่อของคุณไปทางขวาของตัวเอง (ชัด) ดังนั้นคุณสามารถคลิกที่ clicker เล็ก ๆ ใน IB เพื่อย้ายมันกลับมา (หรือในรหัสตามที่ระบุไว้) ข้อเสียเดียวที่ฉันเห็นคือฉันไม่สามารถใช้ภาพที่ยืดหยุ่นได้ ราคาเล็ก ๆ ที่จะจ่าย IMO
Paul Bruneau

7
วิธีการทำเช่นนี้โดยไม่ต้องยืดภาพ?
zonabi

ตั้งค่าโหมดเนื้อหาเป็นแบบ Center และไม่ใช่ Scaling
ซามูเอลกูดวิน

63

นี่คือโซลูชันที่สวยงามที่ใช้ส่วนขยายใน Swift มันทำให้ UIButtons ทั้งหมดได้รับผลกระทบอย่างน้อย 44x44 คะแนนตามหลักเกณฑ์ Human Interface Guidelines ของแอปเปิล ( https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/ )

สวิฟท์ 2:

private let minimumHitArea = CGSizeMake(44, 44)

extension UIButton {
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = CGRectInset(self.bounds, -widthToAdd / 2, -heightToAdd / 2)

        // perform hit test on larger frame
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    }
}

สวิฟท์ 3:

fileprivate let minimumHitArea = CGSize(width: 100, height: 100)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = self.bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

3
คุณต้องทดสอบว่าปุ่มนั้นถูกซ่อนอยู่หรือไม่ ถ้าเป็นเช่นนั้นกลับศูนย์
Josh Gafni

ขอบคุณมันดูดีมาก ข้อเสียที่อาจเกิดขึ้นที่ต้องระวังคืออะไร?
Crashalot

ฉันชอบโซลูชันนี้ไม่ต้องการให้ฉันตั้งค่าคุณสมบัติเพิ่มเติมบนปุ่มทั้งหมด ขอบคุณ
xtrinch

1
หากหลักเกณฑ์ของ Apple แนะนำมิติเหล่านี้สำหรับพื้นที่ฮิตทำไมทำให้เราต้องแก้ไขความล้มเหลวในการนำไปใช้ สิ่งนี้ได้รับการแก้ไขใน SDK ถัดไปหรือไม่
NoodleOfDeath

2
ขอบคุณพระเจ้าที่เรามี Stack Overflow และผู้มีส่วนร่วม เพราะแน่นอนว่าเราไม่สามารถพึ่งพา Apple สำหรับข้อมูลที่มีประโยชน์ตรงเวลาถูกต้องเกี่ยวกับวิธีแก้ไขปัญหาพื้นฐานที่พบบ่อยที่สุด
Womble

49

คุณสามารถ subclass UIButtonหรือกำหนดเองUIViewและแทนที่point(inside:with:)ด้วยสิ่งที่ชอบ:

สวิฟท์ 3

override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
    let margin: CGFloat = 5
    let area = self.bounds.insetBy(dx: -margin, dy: -margin)
    return area.contains(point)
}

Objective-C

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}

1
ขอบคุณวิธีแก้ปัญหาที่ยอดเยี่ยมจริงๆไม่มีหมวดหมู่เพิ่มเติมใด ๆ นอกจากนี้ฉันง่ายรวมกับปุ่มของฉันที่ฉันมีใน XIB และใช้งานได้ดี คำตอบที่ดี
Matrosov Alexander

นี่เป็นทางออกที่ยอดเยี่ยมที่สามารถขยายไปยังส่วนควบคุมอื่น ๆ ได้! ฉันพบว่ามีประโยชน์ในการแบ่งคลาสย่อย UISearchBar เป็นตัวอย่าง วิธีการ pointInside นั้นใช้กับ UIView ทุกตัวตั้งแต่ iOS 2.0 BTW - รวดเร็ว: func pointInside (_ point: CGPoint, กับเหตุการณ์ Event: UIEvent!) -> Bool
Tommie C.

ขอบคุณสำหรับสิ่งนี้! อย่างที่คนอื่นพูดว่ามันมีประโยชน์มากกับการควบคุมอื่น ๆ !
Yanchi

มันเยี่ยมมาก !! ขอบคุณคุณช่วยฉันมากเวลา! :-)
Vojta

35

นี่คือ UIButton + ส่วนขยายของ Chase ใน Swift 3.0


import UIKit

private var pTouchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {

    var touchAreaEdgeInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &pTouchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }
            else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &pTouchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.touchAreaEdgeInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaEdgeInsets)

        return hitFrame.contains(point)
    }
}

หากต้องการใช้งานคุณสามารถ:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)

1
ที่จริงเราควรขยายไม่UIControl UIButton
Valentin Shergin

2
และตัวแปรpTouchAreaEdgeInsetsจะต้องไม่Int UIEdgeInsets(จริงๆแล้วมันอาจเป็นประเภทใดก็ได้ แต่Intเป็นประเภทที่ธรรมดาที่สุดและเรียบง่ายดังนั้นจึงเป็นเรื่องธรรมดาในการก่อสร้างดังกล่าว) (ดังนั้นฉันรักวิธีการนี้โดยทั่วไปขอบคุณ dcRay!)
Valentin Shergin

1
ฉันได้ลอง titleEdgeInsets, imageEdgeInsets, contentEdgeInset ทั้งหมดไม่ทำงานสำหรับฉันส่วนขยายนี้ทำงานได้ดี ขอขอบคุณสำหรับการโพสต์นี้ !! เยี่ยมมาก !!
Yogesh Patel

19

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

  1. ทำเครื่องหมายที่ตัวเลือก 'Show touch on highlight' ของปุ่มที่กำหนดเอง แสงสีขาวจะปรากฏขึ้นเหนือปุ่มข้อมูล แต่ในกรณีส่วนใหญ่นิ้วของผู้ใช้จะครอบคลุมสิ่งนี้และสิ่งที่พวกเขาจะเห็นก็คือแสงรอบนอก

  2. ตั้งค่า IBOutlet สำหรับปุ่มข้อมูลและสอง IBActions สำหรับปุ่มที่กำหนดเองหนึ่งรายการสำหรับ 'Touch Down' และอีกอันสำหรับ 'Touch Up Inside' จากนั้นใน Xcode ให้เหตุการณ์ทัชดาวน์ตั้งค่าคุณสมบัติที่เน้นของปุ่มข้อมูลเป็น YES และเหตุการณ์ touchupinside ตั้งค่าคุณสมบัติที่เน้นเป็น NO


19

อย่าตั้งค่าbackgroundImageคุณสมบัติด้วยรูปภาพของคุณตั้งค่าimageViewคุณสมบัติ ยังให้แน่ใจว่าคุณได้ตั้งไว้ที่imageView.contentModeUIViewContentModeCenter


1
โดยเฉพาะอย่างยิ่งตั้งค่าคุณสมบัติ contentMode ของปุ่มเป็น UIViewContentModeCenter จากนั้นคุณสามารถทำให้กรอบของปุ่มมีขนาดใหญ่กว่าภาพ ใช้งานได้สำหรับฉัน!
arlomedia

FYI, UIButton ไม่เคารพ UIViewContentMode: stackoverflow.com/a/4503920/361247
Enrico Susatyo

@EnricoSusatyo ขออภัยฉันได้ชี้แจงเกี่ยวกับ API ที่ฉันพูดถึง สามารถกำหนดให้เป็นimageView.contentMode UIContentModeCenter
Maurizio

มันทำงานได้อย่างสมบูรณ์ขอบคุณสำหรับคำแนะนำ! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
พักผ่อน

@Resty ความคิดเห็นที่กล่าวถึงข้างต้นใช้งานไม่ได้สำหรับฉัน: / ภาพกำลังปรับขนาดเป็นขนาดเฟรมที่ใหญ่ขึ้น
Anil

14

วิธีแก้ปัญหาของฉันใน Swift 3:

class MyButton: UIButton {

    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake(-25, -25, -25, -25)
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return hitFrame.contains(point)
    }
}

12

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

สร้างคลาสย่อยใหม่สำหรับปุ่มของคุณ (หรือตัวควบคุมใด ๆ )

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

ถัดไปแทนที่เมธอด pointInside ในการใช้งานคลาสย่อยของคุณ

@implementation MNGButton


-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //increase touch area for control in all directions by 20
    CGFloat margin = 20.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}


@end

บนสตอรี่บอร์ด / ไฟล์ xib ของคุณเลือกการควบคุมที่มีปัญหาและเปิดตัวตรวจสอบตัวตนแล้วพิมพ์ชื่อคลาสที่คุณกำหนดเอง

mngbutton

ในคลาส UIViewController ของคุณสำหรับฉากที่มีปุ่มให้เปลี่ยนประเภทชั้นเรียนของปุ่มเป็นชื่อของคลาสย่อยของคุณ

@property (weak, nonatomic) IBOutlet MNGButton *helpButton;

ลิงก์ปุ่มสตอรีบอร์ด / xib ของคุณไปยังคุณสมบัติ IBOutlet และพื้นที่สัมผัสของคุณจะถูกขยายเพื่อให้พอดีกับพื้นที่ที่กำหนดในคลาสย่อย

นอกเหนือจากการเอาชนะเมธอด pointInsideร่วมกับเมธอด CGRectInsetและCGRectContainsPointแล้วเราควรใช้เวลาในการตรวจสอบCGGeometryเพื่อขยายพื้นที่สัมผัสสี่เหลี่ยมของคลาสย่อย UIView ใด ๆ คุณอาจพบเคล็ดลับดี ๆ เกี่ยวกับ CGGeometry use-cases ที่NSHipster NSHipster

ตัวอย่างเช่นสามารถทำให้พื้นที่สัมผัสผิดปกติโดยใช้วิธีการที่กล่าวถึงข้างต้นหรือเพียงแค่เลือกที่จะทำให้พื้นที่สัมผัสกว้างเป็นสองเท่าของพื้นที่สัมผัสแนวนอน:

CGRect area = CGRectInset(self.bounds, -(2*margin), -margin);

หมายเหตุ: การแทนที่การควบคุมคลาส UI ใด ๆ ควรให้ผลลัพธ์ที่คล้ายกันในการขยายพื้นที่การสัมผัสสำหรับการควบคุมที่แตกต่างกัน (หรือคลาสย่อย UIView ใด ๆ เช่น UIImageView เป็นต้น)


10

สิ่งนี้ใช้ได้กับฉัน:

UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
// set the image (here with a size of 32 x 32)
[button setImage: [UIImage imageNamed: @"myimage.png"] forState: UIControlStateNormal];
// just set the frame of the button (here 64 x 64)
[button setFrame: CGRectMake(xPositionOfMyButton, yPositionOfMyButton, 64, 64)];

3
ใช้งานได้ แต่ต้องระวังหากคุณต้องการตั้งค่าทั้งรูปภาพและชื่อให้เป็นปุ่ม ในกรณีนี้คุณจะต้องใช้ setBackgoundImage ซึ่งจะขยายภาพของคุณไปยังเฟรมใหม่ของปุ่ม
หมดเวลา Damian

7

อย่าเปลี่ยนพฤติกรรมของ UIButton

@interface ExtendedHitButton: UIButton

+ (instancetype) extendedHitButton;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

@end

@implementation ExtendedHitButton

+ (instancetype) extendedHitButton {
    return (ExtendedHitButton *) [ExtendedHitButton buttonWithType:UIButtonTypeCustom];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-44, -44, -44, -44);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

หากปุ่มของคุณคือ 40x40 วิธีนี้จะไม่ถูกเรียกถ้าจำนวนลบมากกว่า - เหมือนที่คนอื่นพูดถึง มิฉะนั้นงานนี้
James O'Brien

มันไม่ได้ผลสำหรับฉัน hitFrame ปรากฏขึ้นคำนวณอย่างถูกต้อง แต่ pointInside: ไม่เคยถูกเรียกเมื่อจุดนั้นอยู่นอกขอบเขตของตัวเอง if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "สำเร็จ!"); // ไม่เคยเรียก}}
arsenius

7

ฉันใช้วิธีการทั่วไปที่กว้าง-[UIView pointInside:withEvent:]ขึ้น สิ่งนี้ทำให้ฉันสามารถปรับเปลี่ยนพฤติกรรมการทดสอบการเข้าชมที่ใด ๆUIViewไม่ใช่เพียงUIButtonไม่เพียง

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

@interface UIView(Additions)
@property(nonatomic) UIEdgeInsets hitTestEdgeInsets;
@end

@implementation UIView(Additions)

+ (void)load {
    Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
}

- (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {

    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || self.hidden ||
       ([self isKindOfClass:UIControl.class] && !((UIControl*)self).enabled))
    {
        return [self myPointInside:point withEvent:event]; // original implementation
    }
    CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
    hitFrame.size.width = MAX(hitFrame.size.width, 0); // don't allow negative sizes
    hitFrame.size.height = MAX(hitFrame.size.height, 0);
    return CGRectContainsPoint(hitFrame, point);
}

static char hitTestEdgeInsetsKey;
- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, [NSValue valueWithUIEdgeInsets:hitTestEdgeInsets], OBJC_ASSOCIATION_RETAIN);
}

- (UIEdgeInsets)hitTestEdgeInsets {
    return [objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) UIEdgeInsetsValue];
}

void Swizzle(Class c, SEL orig, SEL new) {

    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}
@end

สิ่งที่ดีเกี่ยวกับวิธีนี้คือคุณสามารถใช้สิ่งนี้แม้ในสตอรีบอร์ดโดยการเพิ่มแอททริบิวต์รันไทม์ที่กำหนดโดยผู้ใช้ น่าเศร้าที่UIEdgeInsetsไม่ได้โดยตรงสามารถใช้ได้เป็นประเภทมี แต่เนื่องจากCGRectยังประกอบด้วยโครงสร้างที่มีสี่CGFloatมันทำงานไม่มีที่ติโดยเลือก "Rect" {{top, left}, {bottom, right}}และกรอกข้อมูลในค่าเช่นนี้:


6

ฉันใช้คลาสต่อไปนี้ใน Swift เพื่อเปิดใช้งานคุณสมบัติตัวสร้างส่วนต่อประสานเพื่อปรับระยะขอบ:

@IBDesignable
class ALExtendedButton: UIButton {

    @IBInspectable var touchMargin:CGFloat = 20.0

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        var extendedArea = CGRectInset(self.bounds, -touchMargin, -touchMargin)
        return CGRectContainsPoint(extendedArea, point)
    }
}

วิธีเปลี่ยนจุดนี้เองภายใน ถ้าฉันได้สร้าง UIButton ด้วย margin เก่า แต่ตอนนี้ฉันต้องการเปลี่ยนหรือไม่
TomSawyer

5

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

ความเป็นไปได้อื่น ๆ เกี่ยวข้องกับการใช้ UIImage แทน UIButton


5

ฉันสามารถเพิ่มพื้นที่การเข้าถึงของปุ่มข้อมูลแบบเป็นโปรแกรม กราฟิก "i" จะไม่เปลี่ยนขนาดและยังคงอยู่กึ่งกลางในกรอบปุ่มใหม่

ขนาดของปุ่มข้อมูลดูเหมือนจะคงที่เป็น 18x19 [*] ในเครื่องมือสร้างส่วนต่อประสาน ด้วยการเชื่อมต่อกับ IBOutlet ฉันสามารถเปลี่ยนขนาดเฟรมเป็นรหัสได้โดยไม่มีปัญหาใด ๆ

static void _resizeButton( UIButton *button )
{
    const CGRect oldFrame = infoButton.frame;
    const CGFloat desiredWidth = 44.f;
    const CGFloat margin = 
        ( desiredWidth - CGRectGetWidth( oldFrame ) ) / 2.f;
    infoButton.frame = CGRectInset( oldFrame, -margin, -margin );
}

[*]: iOS รุ่นที่ใหม่กว่าดูเหมือนจะเพิ่มพื้นที่การกดปุ่มข้อมูล


5

นี่คือโซลูชันSwift 3ของฉัน(อ้างอิงจากบล็อกนี้: http://bdunagan.com/2010/03/01/iphone-tip-larger-hit-area-for-uibutton/ )

class ExtendedHitAreaButton: UIButton {

    @IBInspectable var hitAreaExtensionSize: CGSize = CGSize(width: -10, height: -10)

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

        let extendedFrame: CGRect = bounds.insetBy(dx: hitAreaExtensionSize.width, dy: hitAreaExtensionSize.height)

        return extendedFrame.contains(point) ? self : nil
    }
}

3

การทดสอบ Hit ที่กำหนดเองของ Chase ถูกนำไปใช้เป็นคลาสย่อยของ UIButton เขียนใน Objective-C

ดูเหมือนว่าจะทำงานทั้งสำหรับinitและbuttonWithType:ก่อสร้าง สำหรับความต้องการของฉันมันสมบูรณ์แบบ แต่เนื่องจาก subclassing UIButtonมีขนดกฉันจึงสนใจที่จะรู้ว่าถ้าใครมีความผิดกับมัน

CustomeHitAreaButton.h

#import <UIKit/UIKit.h>

@interface CustomHitAreaButton : UIButton

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;

@end

CustomHitAreaButton.m

#import "CustomHitAreaButton.h"

@interface CustomHitAreaButton()

@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

@implementation CustomHitAreaButton

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.hitTestEdgeInsets = UIEdgeInsetsZero;
    }
    return self;
}

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    self->_hitTestEdgeInsets = hitTestEdgeInsets;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

3

การใช้งานผ่านการแทนที่ UIButton สืบทอด

สวิฟท์ 2.2 :

// don't forget that negative values are for outset
_button.hitOffset = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
...
class UICustomButton: UIButton {
    var hitOffset = UIEdgeInsets()

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard hitOffset != UIEdgeInsetsZero && enabled && !hidden else {
            return super.pointInside(point, withEvent: event)
        }
        return UIEdgeInsetsInsetRect(bounds, hitOffset).contains(point)
    }
}

2

WJBackgroundInsetButton.h

#import <UIKit/UIKit.h>

@interface WJBackgroundInsetButton : UIButton {
    UIEdgeInsets backgroundEdgeInsets_;
}

@property (nonatomic) UIEdgeInsets backgroundEdgeInsets;

@end

WJBackgroundInsetButton.m

#import "WJBackgroundInsetButton.h"

@implementation WJBackgroundInsetButton

@synthesize backgroundEdgeInsets = backgroundEdgeInsets_;

-(CGRect) backgroundRectForBounds:(CGRect)bounds {
    CGRect sup = [super backgroundRectForBounds:bounds];
    UIEdgeInsets insets = self.backgroundEdgeInsets;
    CGRect r = UIEdgeInsetsInsetRect(sup, insets);
    return r;
}

@end

2

อย่าแทนที่เมธอดในหมวดหมู่ - pointInside:withEvent:ปุ่มซับคลาสและแทนที่ ตัวอย่างเช่นถ้าด้านข้างของปุ่มของคุณมีขนาดเล็กกว่า 44 px (ซึ่งแนะนำให้ใช้เป็นพื้นที่แท็บขั้นต่ำ) ให้ใช้สิ่งนี้:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return (ABS(point.x - CGRectGetMidX(self.bounds)) <= MAX(CGRectGetMidX(self.bounds), 22)) && (ABS(point.y - CGRectGetMidY(self.bounds)) <= MAX(CGRectGetMidY(self.bounds), 22));
}

2

ฉันทำห้องสมุดเพื่อจุดประสงค์นี้

คุณสามารถเลือกที่จะใช้UIViewหมวดหมู่โดยไม่ต้องมีคลาสย่อย :

@interface UIView (KGHitTesting)
- (void)setMinimumHitTestWidth:(CGFloat)width height:(CGFloat)height;
@end

หรือคุณสามารถแบ่งคลาสย่อย UIView หรือ UIButton ของคุณแล้วตั้งค่าminimumHitTestWidthและ / หรือminimumHitTestHeightและพื้นที่ทดสอบการกดปุ่มของคุณจะถูกแสดงด้วย 2 ค่าเหล่านี้

เช่นเดียวกับโซลูชันอื่น ๆ มันใช้- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventวิธีการ วิธีการนี้เรียกเมื่อ iOS ทำการทดสอบ โพสต์บล็อกนี้มีคำอธิบายที่ดีเกี่ยวกับวิธีการทดสอบ Hit ของ iOS

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

@property (nonatomic) CGFloat minimumHitTestHeight; 
@property (nonatomic) CGFloat minimumHitTestWidth;

@end

คุณสามารถเพียงคลาสย่อยและใช้ตัวสร้างส่วนต่อประสานโดยไม่ต้องเขียนโค้ดใด ๆ : ป้อนคำอธิบายรูปภาพที่นี่


1
ฉันลงเอยด้วยการติดตั้งสิ่งนี้เพื่อความรวดเร็ว การเพิ่ม IBInspectable เป็นความคิดที่ดีมากขอบคุณที่รวบรวมสิ่งนี้ไว้ด้วยกัน
user3344977

2

ตามคำตอบของ giaset ด้านบน (ซึ่งฉันได้พบทางออกที่ดีที่สุด) นี่คือรุ่น 3 ที่รวดเร็ว:

import UIKit

fileprivate let minimumHitArea = CGSize(width: 44, height: 44)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if isHidden || !isUserInteractionEnabled || alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

2

มันง่ายแค่นี้:

class ChubbyButton: UIButton {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return bounds.insetBy(dx: -20, dy: -20).contains(point)
    }
}

นั่นคือสิ่งทั้งหมด


1

ฉันใช้เคล็ดลับนี้สำหรับปุ่มที่อยู่ภายใน tableviewcell.accessoryView เพื่อขยายพื้นที่การสัมผัส

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(!CGRectContainsPoint(accessoryViewTouchRect, location))
        [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(CGRectContainsPoint(accessoryViewTouchRect, location) && [self.accessoryView isKindOfClass:[UIButton class]])
    {
        [(UIButton *)self.accessoryView sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else
        [super touchesEnded:touches withEvent:event];
}

1

ฉันเพิ่งทำพอร์ตของโซลูชัน @Chase ใน swift 2.2

import Foundation
import ObjectiveC

private var hitTestEdgeInsetsKey: UIEdgeInsets = UIEdgeInsetsZero

extension UIButton {
    var hitTestEdgeInsets:UIEdgeInsets {
        get {
            let inset = objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) as? NSValue ?? NSValue(UIEdgeInsets: UIEdgeInsetsZero)
            return inset.UIEdgeInsetsValue()
        }
        set {
            let inset = NSValue(UIEdgeInsets: newValue)
            objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, inset, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard !UIEdgeInsetsEqualToEdgeInsets(hitTestEdgeInsets, UIEdgeInsetsZero) && self.enabled == true && self.hidden == false else {
            return super.pointInside(point, withEvent: event)
        }
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return CGRectContainsPoint(hitFrame, point)
    }
}

ที่คุณสามารถใช้เช่นนี้

button.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10)

สำหรับการอ้างอิงอื่น ๆ โปรดดูhttps://stackoverflow.com/a/13067285/1728552


1

คำตอบ @ antoine ถูกจัดรูปแบบสำหรับ Swift 4

class ExtendedHitButton: UIButton
{
    override func point( inside point: CGPoint, with event: UIEvent? ) -> Bool
    {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake( -44, -44, -44, -44 ) // Apple recommended hit target
        let hitFrame = UIEdgeInsetsInsetRect( relativeFrame, hitTestEdgeInsets )
        return hitFrame.contains( point );
    }
}

0

ฉันได้ติดตามการตอบสนองของ Chase และใช้งานได้ดีปัญหาเดียวเมื่อคุณสร้าง arrea ใหญ่เกินไปใหญ่กว่าโซนที่ปุ่มถูกยกเลิกการเลือก (ถ้าโซนไม่ใหญ่) จะไม่เรียกตัวเลือกสำหรับเหตุการณ์ UIControlEventTouchUpInside .

ฉันคิดว่าขนาดอยู่ที่ 200 กว่าทิศทางใด ๆ หรืออะไรแบบนั้น


0

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

UIImage *arrowImage = [UIImage imageNamed:@"leftarrow"];
arrowButton = [[UIButton alloc] initWithFrame:CGRectMake(15.0, self.frame.size.height-35.0, arrowImage.size.width/2, arrowImage.size.height/2)];
[arrowButton setBackgroundImage:arrowImage forState:UIControlStateNormal];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
[arrowButton addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
[arrowButton addTarget:self action:@selector(onTap:) forControlEvents:UIControlEventTouchUpInside];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchDragExit];
[arrowButton setUserInteractionEnabled:TRUE];
[arrowButton setAdjustsImageWhenHighlighted:NO];
[arrowButton setTag:1];
[self addSubview:arrowButton];

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

สิ่งที่ฉันทำเปิดภาพใน photoshop และเพิ่มขนาดผืนผ้าใบเป็น 120% และประหยัด ได้อย่างมีประสิทธิภาพคุณเพียงแค่ทำให้ภาพใหญ่ขึ้นด้วยพิกเซลโปร่งใส

เพียงหนึ่งวิธี


0

คำตอบของ @jlajlar ดูดีและตรงไปตรงมา แต่ไม่ตรงกับ Xamarin.iOS ดังนั้นฉันจึงแปลงมันเป็น Xamarin หากคุณกำลังมองหาวิธีแก้ปัญหาบน Xamarin iOS ตรงนี้มันจะไป:

public override bool PointInside (CoreGraphics.CGPoint point, UIEvent uievent)
{
    var margin = -10f;
    var area = this.Bounds;
    var expandedArea = area.Inset(margin, margin);
    return expandedArea.Contains(point);
}

คุณสามารถเพิ่มวิธีนี้ในชั้นเรียนที่คุณกำลังเอาชนะ UIView หรือ UIImageView มันใช้งานได้ดี :)


0

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

แต่ฉันขยายพื้นที่ประปาโดยทำให้พื้นที่โปร่งใส png ใหญ่ขึ้นแทน

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