Честно говоря, не уверен, что не так с подклассом, но сеть (включая SO) полна предостерегающих рассказов о подклассе UIButton и о том, как не следует.
Итак, я перейду к методу, описывающему четыре метода сенсорной обработки. Следующая функция заменяет предоставленный метод кнопки моей реализацией (взятой из класса MyButton), сохраняя при этом старый в классе системных кнопок под другим селектором:
//Does NOT look at superclasses
static bool MethodInClass(Class c, SEL sel)
{
unsigned n,i ;
Method *m = class_copyMethodList(c, &n);
for(i=0;i<n;i++)
{
if(sel_isEqual(method_getName(m[i]), sel))
return true;
}
return false;
}
static void MountMethod(Class &buc, SEL SrcSel, SEL SaveSlotSel)
{
IMP OldImp = [buc instanceMethodForSelector:SrcSel];
IMP NewImp = [[MyButton class] instanceMethodForSelector:SrcSel];
if(OldImp && NewImp)
{
//Save the old implementation. Might conflict if the technique is used
//independently on many classes in the same hierarchy
Method SaveMe = class_getInstanceMethod(buc, SaveSlotSel);
if(SaveMe == NULL)
class_addMethod(buc, SaveSlotSel, OldImp, "v@:@@");
else
method_setImplementation(SaveMe, OldImp);
//Note: the method's original implemenation might've been in the base class
if(MethodInClass(buc, SrcSel))
{
Method SrcMe = class_getInstanceMethod(buc, SrcSel);
if(SrcMe)
method_setImplementation(SrcMe, NewImp);
}
else //Add an override in the current class
class_addMethod(buc, SrcSel, NewImp, "v@:@@");
}
}
И назовите это так:
Class buc = [bu class];
MountMethod(buc, @selector(touchesBegan:withEvent:), @selector(MyButton_SavedTouchesBegan:withEvent:));
MountMethod(buc, @selector(touchesCancelled:withEvent:), @selector(MyButton_SavedTouchesCancelled:withEvent:));
MountMethod(buc, @selector(touchesEnded:withEvent:), @selector(MyButton_SavedTouchesEnded:withEvent:));
MountMethod(buc, @selector(touchesMoved:withEvent:), @selector(MyButton_SavedTouchesMoved:withEvent:));
Недостатком является то, что указанные способы монтируются для всех кнопок, а не только для желаемых. В реализации MyButton есть дополнительная проверка, должна ли быть включена функция перетаскивания для этой конкретной кнопки. Для этого я использовал связанные объекты.
Один замечательный момент заключается в том, что методы touchesXXX реализованы в классе UIControl, а не в UIButton. Поэтому моя первая наивная реализация swizzling заменит метод в UIControl вместо класса кнопки. Текущая реализация не предполагает ни того, ни другого. Кроме того, он не делает никаких предположений о классе кнопок во время выполнения. Может быть UIButton, может быть что угодно (а в реальной iOS это UIRoundedRectButton).