UIButton: Làm cho vùng đánh lớn hơn vùng đánh mặc định


188

Tôi có một câu hỏi liên quan đến UIButton và khu vực đánh của nó. Tôi đang sử dụng nút Info Dark trong trình tạo giao diện, nhưng tôi thấy rằng vùng nhấn không đủ lớn cho ngón tay của một số người.

Có cách nào để tăng diện tích nhấn của nút theo chương trình hoặc trong Trình tạo giao diện mà không thay đổi kích thước của đồ họa InfoButton không?


Nếu không có gì hoạt động, chỉ cần thêm một UIButton mà không có hình ảnh và sau đó đặt lớp phủ ImageView!
Varun

Đây sẽ phải là câu hỏi đơn giản đáng kinh ngạc nhất trên toàn bộ trang web, trong đó có hàng tá câu trả lời. Ý tôi là, tôi có thể dán toàn bộ câu trả lời vào đây: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Fattie

Câu trả lời:


137

Vì tôi đang sử dụng hình nền, không có giải pháp nào trong số này hoạt động tốt với tôi. Đây là một giải pháp thực hiện một số phép thuật khách quan thú vị và cung cấp một giải pháp giảm với mã tối thiểu.

Đầu tiên, thêm một danh mục để UIButtonghi đè kiểm tra lần truy cập và cũng thêm một thuộc tính để mở rộng khung kiểm tra lần truy cập.

UIButton + Tiện ích mở rộng.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + Tiện ích mở rộng.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

Khi lớp này được thêm vào, tất cả những gì bạn cần làm là đặt các cạnh trong của nút. Lưu ý rằng tôi đã chọn thêm phần phụ để nếu bạn muốn làm cho vùng nhấn lớn hơn, bạn phải sử dụng số âm.

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

Lưu ý: Hãy nhớ nhập danh mục ( #import "UIButton+Extensions.h") trong các lớp của bạn.


26
Ghi đè một phương thức trong một danh mục là một ý tưởng tồi. Điều gì xảy ra nếu một danh mục khác cũng cố gắng ghi đè pointInside: withEvent:? Và cuộc gọi đến [super pointInside: withEvent] không gọi phiên bản cuộc gọi của UIButton (nếu có) mà là phiên bản của cha mẹ của UIButton.
honus

@honus Bạn nói đúng và Apple không khuyên bạn nên làm điều này. Điều đó đang được nói, miễn là mã này không nằm trong thư viện dùng chung và chỉ cục bộ trong ứng dụng của bạn, bạn sẽ ổn thôi.
Đuổi theo

Xin chào Chase, có bất kỳ nhược điểm nào khi sử dụng một lớp con thay thế không?
Sid

3
Bạn đang làm quá nhiều việc không cần thiết, bạn có thể thực hiện hai dòng mã đơn giản: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];Và chỉ cần đặt chiều rộng và chiều cao mà bạn cần: `backButton.frame = CGRectMake (5, 28, 45, 30);`
Resty

7
@Resty, anh ấy đang sử dụng hình nền, nghĩa là cách tiếp cận của bạn sẽ không hiệu quả.
mattsson

77

Chỉ cần đặt giá trị chèn cạnh hình ảnh trong trình tạo giao diện.


1
Tôi nghĩ rằng điều này sẽ không phù hợp với tôi vì hình ảnh nền không phản hồi với các phần tử nhưng tôi đã thay đổi để đặt thuộc tính hình ảnh và sau đó đặt phần bên trái âm cho tiêu đề của tôi chiều rộng của hình ảnh và nó nằm ở trên cùng của hình ảnh của tôi
Dave Ross

12
Điều này cũng có thể được thực hiện trong mã. Ví dụ:button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

Câu trả lời của Dave Ross rất hay. Đối với những người không thể hình dung ra nó, hình ảnh sẽ đặt tiêu đề của bạn sang bên phải của nó (rõ ràng), vì vậy bạn có thể nhấp vào công cụ nhấp chuột nhỏ trong IB để di chuyển trở lại (hoặc theo mã như đã nêu). Điểm trừ duy nhất tôi thấy là tôi không thể sử dụng hình ảnh có thể kéo dài. Giá nhỏ để trả IMO
Paul Bruneau

7
Làm thế nào để làm điều này mà không kéo dài hình ảnh?
zonabi

Đặt chế độ nội dung thành một cái gì đó như Center và không phải bất cứ thứ gì Thu nhỏ.
Samuel Goodwin

63

Đây là một giải pháp tao nhã sử dụng Tiện ích mở rộng trong Swift. Nó cung cấp cho tất cả các UIButton một khu vực đạt ít nhất 44x44 điểm, theo Nguyên tắc giao diện con người của Apple ( https://developer.apple.com/ios/human-interface-guferences/visual-design/layout/ )

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

Swift 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
Bạn cần kiểm tra nếu nút hiện đang bị ẩn. Nếu vậy, trở về con số không.
Josh Gafni

Cảm ơn, điều này có vẻ tuyệt vời. Bất kỳ nhược điểm tiềm năng để nhận thức được?
Crashalot

Tôi thích giải pháp này, không yêu cầu tôi đặt các thuộc tính bổ sung trên tất cả các nút. Cảm ơn
xtrinch

1
Nếu hướng dẫn của Apple đề xuất các kích thước này cho khu vực nhấn, tại sao chúng ta phải sửa lỗi không thực hiện? Điều này có được giải quyết trong SDK sau này không?
NoodleOfDeath

2
Cảm ơn trời, chúng tôi có Stack Overflow và những người đóng góp. Bởi vì chúng tôi chắc chắn không thể dựa vào Apple để có thông tin hữu ích, kịp thời, chính xác về cách khắc phục các sự cố cơ bản, phổ biến nhất của họ.
Womble

49

Bạn cũng có thể phân lớp UIButtonhoặc tùy chỉnh UIViewvà ghi đè point(inside:with:)bằng một cái gì đó như:

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

Mục tiêu-C

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

1
cảm ơn giải pháp thực sự mát mẻ mà không có bất kỳ loại bổ sung. Ngoài ra tôi đơn giản tích hợp nó với các nút mà tôi có trong XIB và nó hoạt động tốt. câu trả lời tuyệt vời
Matrosov Alexander

Đây là một giải pháp tuyệt vời có thể được mở rộng cho tất cả các điều khiển khác! Tôi thấy nó hữu ích để phân lớp UISearchBar chẳng hạn. Phương thức pointInside có trên mọi UIView kể từ iOS 2.0. BTW - Swift: func pointInside (_ point: CGPoint, withEvent event: UIEvent!) -> Bool.
Tommie C.

Cảm ơn vì điều đó! Như đã nói, điều này có thể thực sự hữu ích với các điều khiển khác!
Yanchi

Điều đó thật tuyệt!! Cảm ơn, bạn đã tiết kiệm cho tôi rất nhiều thời gian! :-)
Vojta

35

Đây là UIButton + Tiện ích mở rộng trong 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)
    }
}

Để sử dụng nó, bạn có thể:

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

1
Trên thực tế, chúng ta nên mở rộng UIControl, không UIButton.
Valentin Shergin

2
Và biến pTouchAreaEdgeInsetsphải Int, không UIEdgeInsets. (Trên thực tế, nó có thể là bất kỳ loại nào, nhưng Intlà loại chung chung và đơn giản nhất, vì vậy nó là phổ biến trong xây dựng như vậy). (Vì vậy, tôi rất thích cách tiếp cận này nói chung. Cảm ơn bạn, dcRay!)
Valentin Shergin

1
Tôi đã thử titleEdgeInsets, imageEdgeInsets, contentEdgeInset tất cả đều không hoạt động đối với tôi tiện ích mở rộng này đang hoạt động tốt. Cảm ơn vì đã đăng tải điều này !! Bạn đã làm rất tốt !!
Yogesh Patel

19

Tôi khuyên bạn nên đặt UIButton với loại Tùy chỉnh tập trung vào nút thông tin của bạn. Thay đổi kích thước nút tùy chỉnh theo kích thước bạn muốn khu vực nhấn. Từ đó bạn có hai lựa chọn:

  1. Kiểm tra tùy chọn 'Show touch on highlight' của nút tùy chỉnh. Ánh sáng trắng sẽ xuất hiện trên nút thông tin, nhưng trong hầu hết các trường hợp, ngón tay người dùng sẽ che đi điều này và tất cả những gì họ sẽ thấy là ánh sáng xung quanh bên ngoài.

  2. Thiết lập IBOutlet cho nút thông tin và hai IBActions cho nút tùy chỉnh một cho 'Chạm xuống' và một cho 'Chạm vào bên trong'. Sau đó, trong Xcode, làm cho sự kiện touchdown đặt thuộc tính được tô sáng của nút thông tin thành CÓ và sự kiện touchupinside đặt thuộc tính được tô sáng thành NO.


19

Không đặt thuộc backgroundImagetính với hình ảnh của bạn, đặt thuộc imageViewtính. Ngoài ra, hãy chắc chắn rằng bạn đã imageView.contentModethiết lập tại UIViewContentModeCenter.


1
Cụ thể hơn, đặt thuộc tính contentMode của nút thành UIViewContentModeCenter. Sau đó, bạn có thể làm cho khung của nút lớn hơn hình ảnh. Làm việc cho tôi!
arlomedia

FYI, UIButton không tôn trọng UIViewContentMode: stackoverflow.com/a/4503920/361247
Enrico Susatyo

@EnricoSusatyo xin lỗi, tôi đã làm rõ những API tôi đang nói về. Có imageView.contentModethể được đặt là UIContentModeCenter.
Maurizio

Điều này hoạt động hoàn hảo, nhờ lời khuyên! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty

@Resty Nhận xét được đề cập ở trên không hoạt động đối với tôi: / Hình ảnh được thay đổi kích thước thành kích thước khung hình lớn hơn.
Anil

14

Giải pháp của tôi trên 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

Không có gì sai với các câu trả lời được trình bày; tuy nhiên tôi muốn mở rộng câu trả lời của jlarjlar vì nó có tiềm năng đáng kinh ngạc có thể tăng giá trị cho cùng một vấn đề với các điều khiển khác (ví dụ SearchBar). Điều này là do pointInside được gắn vào UIView, nên người ta có thể phân lớp bất kỳ điều khiển nào để cải thiện vùng cảm ứng. Câu trả lời này cũng cho thấy một mẫu đầy đủ về cách thực hiện giải pháp hoàn chỉnh.

Tạo một lớp con mới cho nút của bạn (hoặc bất kỳ điều khiển nào)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Tiếp theo ghi đè phương thức pointInside trong triển khai lớp con của bạn

@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

Trên tập tin bảng phân cảnh / xib của bạn, chọn điều khiển trong câu hỏi và mở trình kiểm tra danh tính và nhập tên của lớp tùy chỉnh của bạn.

mngbutton

Trong lớp UIViewControll của bạn cho cảnh chứa nút, hãy thay đổi loại lớp cho nút thành tên của lớp con của bạn.

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

Liên kết nút bảng phân cảnh / xib của bạn với IBOutlet thuộc tính và vùng cảm ứng của bạn sẽ được mở rộng để phù hợp với khu vực được xác định trong lớp con.

Ngoài trọng các phương pháp pointInside cùng với CGRectInsetCGRectContainsPoint phương pháp, ta nên dành thời gian để kiểm tra CGGeometry cho việc mở rộng khu vực cảm ứng hình chữ nhật của bất kỳ phân lớp UIView. Bạn cũng có thể tìm thấy một số mẹo hay về các trường hợp sử dụng CGGeometry tại NSHipster .

Ví dụ: người ta có thể làm cho vùng cảm ứng không đều bằng cách sử dụng các phương pháp được đề cập ở trên hoặc chỉ cần chọn để làm cho vùng cảm ứng chiều rộng lớn gấp đôi diện tích chạm ngang:

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

Lưu ý: Việc thay thế bất kỳ điều khiển Lớp UI nào sẽ tạo ra kết quả tương tự khi mở rộng vùng cảm ứng cho các điều khiển khác nhau (hoặc bất kỳ lớp con UIView nào, như UIImageView, v.v.).


10

Điều này làm việc cho tôi:

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
Điều này hoạt động, nhưng hãy cẩn thận nếu bạn cần đặt cả hình ảnh và tiêu đề thành một nút. Trong trường hợp đó, bạn sẽ cần sử dụng setBackgoundImage, nó sẽ chia tỷ lệ hình ảnh của bạn sang khung mới của nút.
Mihai Damian

7

Đừng thay đổi hành vi của 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

Nếu nút của bạn là 40x40, phương thức này sẽ không được gọi nếu số âm lớn hơn - như người khác đã đề cập. Nếu không thì điều này hoạt động.
James O'Brien

Điều này không làm việc cho tôi. hitFrame xuất hiện được tính toán chính xác, nhưng pointInside: không bao giờ được gọi khi điểm nằm ngoài self.bound. if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bound, point)) {NSLog (@ "Thành công!"); // Không bao giờ được gọi}
arsenius

7

Tôi đang sử dụng một cách tiếp cận chung chung hơn bằng cách thay đổi -[UIView pointInside:withEvent:]. Điều này cho phép tôi sửa đổi hành vi kiểm tra hit trên bất kỳ UIView, không chỉ UIButton.

Thông thường, một nút được đặt bên trong chế độ xem container cũng giới hạn kiểm tra lần truy cập. Chẳng hạn, khi một nút ở trên cùng của chế độ xem container và bạn muốn mở rộng mục tiêu cảm ứng lên trên, bạn cũng phải mở rộng mục tiêu cảm ứng của chế độ xem container.

@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

Điều thú vị về cách tiếp cận này là bạn có thể sử dụng điều này ngay cả trong Bảng phân cảnh bằng cách thêm Thuộc tính Thời gian chạy do Người dùng Xác định. Đáng buồn thay, UIEdgeInsetskhông có sẵn trực tiếp như một loại ở đó nhưng vì CGRectcũng bao gồm một cấu trúc với bốn cấu trúc, CGFloatnó hoạt động hoàn hảo bằng cách chọn "Rect" và điền vào các giá trị như thế này : {{top, left}, {bottom, right}}.


6

Tôi đang sử dụng lớp sau trong Swift, để cũng kích hoạt thuộc tính Trình tạo giao diện để điều chỉnh lề:

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

Làm thế nào để tự thay đổi pointInside này? Nếu tôi đã tạo một UIButton với lề cũ, nhưng bây giờ tôi muốn thay đổi nó?
TomSawyer

5

Chà, bạn có thể đặt UIButton của mình bên trong một UIView trong suốt và lớn hơn một chút, sau đó bắt các sự kiện chạm trên đối tượng UIView như trong UIButton. Bằng cách đó, bạn vẫn sẽ có nút của mình, nhưng với vùng cảm ứng lớn hơn. Theo cách thủ công, bạn sẽ phải xử lý các trạng thái được chọn và tô sáng bằng nút nếu người dùng chạm vào chế độ xem thay vì nút.

Khả năng khác liên quan đến việc sử dụng UIImage thay vì UIButton.


5

Tôi đã có thể tăng vùng nhấn của nút thông tin theo chương trình. Đồ họa "i" không thay đổi tỷ lệ và vẫn tập trung trong khung nút mới.

Kích thước của nút thông tin dường như được cố định là 18x19 [*] trong Trình tạo giao diện. Bằng cách kết nối nó với IBOutlet, tôi có thể thay đổi kích thước khung của nó trong mã mà không gặp vấn đề gì.

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

[*]: Các phiên bản iOS sau này dường như đã tăng diện tích nhấn của nút thông tin.


5

Đây là Giải pháp Swift 3 của tôi (dựa trên blogpost này: 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

Thử nghiệm hit tùy chỉnh của Chase được triển khai như một lớp con của UIButton. Viết trong Mục tiêu-C.

Nó dường như làm việc cả cho initbuttonWithType:xây dựng. Đối với nhu cầu của tôi, nó hoàn hảo, nhưng kể từ khi phân lớpUIButton có thể có lông, tôi rất muốn biết liệu có ai có lỗi với nó không.

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

Thực hiện thông qua ghi đè UIButton kế thừa.

Swift 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

Không bao giờ ghi đè phương thức trong thể loại. Nút phân lớp và ghi đè - pointInside:withEvent:. Ví dụ: nếu cạnh của nút của bạn nhỏ hơn 44 px (được khuyến nghị là vùng có thể khai thác tối thiểu), hãy sử dụng:

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

Tôi đã làm một thư viện cho mục đích này.

Bạn có thể chọn sử dụng một UIViewdanh mục, không yêu cầu phân lớp :

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

Hoặc bạn có thể phân lớp UIView hoặc UIButton của bạn và đặt minimumHitTestWidthvà / hoặcminimumHitTestHeight . Vùng kiểm tra nhấn nút của bạn sau đó sẽ được đại diện bởi 2 giá trị này.

Cũng giống như các giải pháp khác, nó sử dụng - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventphương pháp. Phương thức này được gọi khi iOS thực hiện kiểm tra hit. Bài đăng trên blog này có một mô tả hay về cách hoạt động của thử nghiệm hit iOS.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

Bạn cũng có thể chỉ phân lớp và sử dụng Trình tạo giao diện mà không cần viết bất kỳ mã nào: nhập mô tả hình ảnh ở đây


1
Tôi đã kết thúc việc cài đặt này chỉ vì lợi ích của tốc độ. Thêm IBInspectable là một ý tưởng tuyệt vời cảm ơn vì đã kết hợp nó.
dùng344977

2

Dựa trên câu trả lời của giaset ở trên (mà tôi tìm thấy giải pháp tao nhã nhất), đây là phiên bản 3 swift:

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

Nó chỉ đơn giản như vậy:

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

Đó là toàn bộ.


1

Tôi đang sử dụng thủ thuật này cho nút bên trong tableviewcell.accessoryView để phóng to vùng cảm ứng của nó

#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

Tôi vừa thực hiện cổng của giải pháp @Chase trong 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)
    }
}

một bạn có thể sử dụng như thế này

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

Đối với bất kỳ tài liệu tham khảo nào khác, hãy xem https://stackoverflow.com/a/13067285/1728552


1

Câu trả lời của @ antoine được định dạng cho 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

Tôi đã theo dõi phản hồi của Chase và nó hoạt động rất tốt, một vấn đề duy nhất khi bạn tạo ra vùng đất quá lớn, lớn hơn vùng mà nút được bỏ chọn (nếu vùng không lớn hơn), nó không gọi bộ chọn cho sự kiện UIControlEventTouchUpInside .

Tôi nghĩ rằng kích thước là hơn 200 bất kỳ hướng nào hoặc một cái gì đó như thế.


0

Tôi đến trò chơi này rất muộn, nhưng muốn cân nhắc về một kỹ thuật đơn giản có thể giải quyết vấn đề của bạn. Đây là một đoạn UIButton lập trình điển hình cho tôi:

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];

Tôi đang tải Hình ảnh png trong suốt cho nút của mình và đặt hình nền. Tôi đang thiết lập khung dựa trên UIImage và tăng 50% cho võng mạc. OK, có thể bạn đồng ý với những điều trên hay không, NHƯNG nếu bạn muốn làm cho khu vực hit LỚN và tự cứu mình khỏi đau đầu:

Những gì tôi làm, mở hình ảnh trong photoshop và chỉ cần tăng kích thước canvas lên 120% và tiết kiệm. Thực tế, bạn vừa làm cho hình ảnh lớn hơn với các pixel trong suốt.

Chỉ cần một cách tiếp cận.


0

Câu trả lời của @jlajlar ở trên có vẻ tốt và đơn giản nhưng không phù hợp với Xamarin.iOS, vì vậy tôi đã chuyển đổi nó thành Xamarin. Nếu bạn đang tìm kiếm một giải pháp trên Xamarin iOS, thì đây là:

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

Bạn có thể thêm phương thức này vào lớp nơi bạn đang ghi đè UIView hoặc UIImageView. Điều này làm việc độc đáo :)


0

Không có câu trả lời nào phù hợp với tôi, vì tôi sử dụng hình nền và tiêu đề trên nút đó. Hơn nữa, nút sẽ thay đổi kích thước khi thay đổi kích thước màn hình.

Thay vào đó, tôi phóng to khu vực vòi bằng cách làm cho vùng trong suốt png lớn hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.