How to add a category to a “hidden” class
-
04-06-2021 - |
Question
Is there a way to add a category to a class whose header file you can't access?
For testing purposes, I want to add a category to UITableViewCellDeleteConfirmationControl
, but the class is (as far as I can tell) part of a private framework.
How can I do that?
Elaboration (per mihirios's request):
I am trying to extend the Frank testing framework to simulate tapping the confirmation button (the big red "Delete" button) that appears when you try to delete a UITableViewCell
. Frank adds a tap
method to UIControl
. For some reason, Frank's usual way of tapping a control does not work for the UITableViewCellDeleteConfirmationControl
class (which subclasses UIControl
).
I've create a workaround. I added a category to UITableViewCell
, with the following method.
- (BOOL)confirmDeletion {
if (![self showingDeleteConfirmation]) {
return NO;
}
UITableView *tableView = (UITableView *)[self superview];
id <UITableViewDataSource> dataSource = [tableView dataSource];
NSIndexPath *indexPath = [tableView indexPathForCell:self];
[dataSource tableView:tableView
commitEditingStyle:UITableViewCellEditingStyleDelete
forRowAtIndexPath:indexPath];
return YES;
}
This finds the table's data source and invokes its tableView:commitEditingStyle:forRowAtIndexPath:
method, which (according to the documentation for UITableView
) is what the system does when the user taps the confirmation button.
This works, but I would prefer to make UITableViewCellDeleteConfirmationControl
appear to be a tappable button by adding a tap
method to it, overriding Frank's default one. The tap
method would find the cell that contains the confirmation button, then invoke [cell confirmDeletion]
.
When I try to declare a category for UITableViewCellDeleteConfirmationControl
, the compiler complains that it "can't resolve interface 'UITableViewCellDeleteConfirmationControl'."
When I try to use the header file that someone generated using class-dump, the linker complains that it can't find the symbol _OBJC_CLASS_$_UITableViewCellDeleteConfirmationControl.
Solution
For testing purposes, you can always get the class object using NSClassFromString
and then use the class_replaceMethod
runtime method to do whatever you need. See the Objective-C Runtime Reference for details.
OTHER TIPS
As far as i know you can not use a Category, but you could add the methods manually during runtime.
A Possible way to do this is, to create a new class, implement the methods you want to, and send this methods to UITableViewCellDeleteConfirmationControl using the appropriate objc-runtime functions. There are some things to take care of, like storing the original functions for later use in case of overloading, also in your 'category'-class you have to pay attention when you want to call super, as this will not work, you have to use objc-runtime function objc_msgSendSuper instead.
As Long as you don't need to call super this will do fine:
#import <objc/runtime.h>
#import <objc/message.h>
void implementInstanceMethods(Class src, Class dest) {
unsigned int count;
Method *methods = class_copyMethodList(src, &count);
for (int i = 0; i < count; ++i) {
IMP imp = method_getImplementation(methods[i]);
SEL selector = method_getName(methods[i]);
NSString *selectorName = NSStringFromSelector(selector);
const char *types = method_getTypeEncoding(methods[i]);
class_replaceMethod(dest, selector, imp, types);
}
free(methods);
}
a good point to call the method is in main.m, for example:
@autoreleasepool {
implementInstanceMethods([MyCategory class], NSClassFromString(@"UITableViewCellDeleteConfirmationControl"));
return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
}
But i don't know why you not just move the confirmation handling in the controller-class.
As long as the compiler can (eventually) link to the class in question you can create a category for it. The more important question will be how to design the category since it seems you do not have access to the source for the original class.