swizzling으로보다 일반적인 접근 방식을 사용하고 있습니다 -[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
4가있는 구조체로 구성되어 있기 때문에 CGFloat
"Rect"를 선택하고 다음과 같은 값을 입력하면 완벽하게 작동합니다 {{top, left}, {bottom, right}}
.