Mixins or Multiple Inheritance in Objective-C?
Building on Amin's answer, this is how you could do it:
Step 1: Create a TextSurrogateHosting
protocol that will contain all the methods of your UITextField
and UITextView
subclasses that you need to access from the methods that you want to add to both subclasses. This might for example be a text
and setText:
method, so that your methods can access and set the text of either a text field or a text view. It might look like this:
SPWKTextSurrogateHosting.h
#import <Foundation/Foundation.h>@protocol SPWKTextSurrogateHosting <NSObject>- (NSString *)text;- (void)setText:(NSString *)text;@end
Step 2: Create a TextSurrogate
class that contains all the methods that you want to share between both the UITextField
and the UITextView
subclasses. Add those methods to a protocol so that we can use code completion in Xcode and avoid compiler warnings/errors.
SPWKTextSurrogate.h
#import <Foundation/Foundation.h>#import "SPWKTextSurrogateHosting.h"@protocol SPWKTextSurrogating <NSObject>@optional- (void)appendQuestionMark;- (void)appendWord:(NSString *)aWord;- (NSInteger)characterCount;- (void)capitalize;@end@interface SPWKTextSurrogate : NSObject <SPWKTextSurrogating>/* We need to init with a "host", either a UITextField or UITextView subclass */- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost;@end
SPWKTextSurrogate.m
#import "SPWKTextSurrogate.h"@implementation SPWKTextSurrogate { id<SPWKTextSurrogateHosting> _host;}- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost{ self = [super init]; if (self) { _host = aHost; } return self;}- (void)appendQuestionMark{ _host.text = [_host.text stringByAppendingString:@"?"];}- (void)appendWord:(NSString *)aWord{ _host.text = [NSString stringWithFormat:@"%@ %@", _host.text, aWord];}- (NSInteger)characterCount{ return [_host.text length];}- (void)capitalize{ _host.text = [_host.text capitalizedString];}@end
Step 3: Create your UITextField
subclass. It will contain three necessary boilerplate methods to forward unrecognized method invocations to your SPWKTextSurrogate
.
SPWKTextField.h
#import <UIKit/UIKit.h>#import "SPWKTextSurrogateHosting.h"#import "SPWKTextSurrogate.h"@interface SPWKTextField : UITextField <SPWKTextSurrogateHosting, SPWKTextSurrogating>@end
SPWKTextField.m
#import "SPWKTextField.h"@implementation SPWKTextField { SPWKTextSurrogate *_surrogate;}- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { _surrogate = [[SPWKTextSurrogate alloc] initWithHost:self]; } return self;}#pragma mark Invocation Forwarding- (void)forwardInvocation:(NSInvocation *)anInvocation{ if ([_surrogate respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:_surrogate]; } else { [super forwardInvocation:anInvocation]; }}- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector{ NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [_surrogate methodSignatureForSelector:selector]; } return signature;}- (BOOL)respondsToSelector:(SEL)aSelector{ if ([super respondsToSelector:aSelector] || [_surrogate respondsToSelector:aSelector]) { return YES; } return NO;}@end
Step 4: Create your UITextView
subclass.
SPWKTextView.h
#import <UIKit/UIKit.h>#import "SPWKTextSurrogateHosting.h"#import "SPWKTextSurrogate.h"@interface SPWKTextView : UITextView <SPWKTextSurrogateHosting, SPWKTextSurrogating>@end
SPWKTextView.m
#import "SPWKTextView.h"#import "SPWKTextSurrogate.h"@implementation SPWKTextView { SPWKTextSurrogate *_surrogate;}- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { _surrogate = [[SPWKTextSurrogate alloc] initWithHost:self]; } return self;}#pragma mark Invocation Forwarding- (void)forwardInvocation:(NSInvocation *)anInvocation{ if ([_surrogate respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:_surrogate]; } else { [super forwardInvocation:anInvocation]; }}- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector{ NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [_surrogate methodSignatureForSelector:selector]; } return signature;}- (BOOL)respondsToSelector:(SEL)aSelector{ if ([super respondsToSelector:aSelector] || [_surrogate respondsToSelector:aSelector]) { return YES; } return NO;}@end
Step 5: Use it:
SPWKTextField *textField = [[SPWKTextField alloc] initWithFrame:CGRectZero];SPWKTextView *textView = [[SPWKTextView alloc] initWithFrame:CGRectZero];textField.text = @"The green fields";textView.text = @"What a wonderful view";[textField capitalize];[textField appendWord:@"are"];[textField appendWord:@"green"];[textField appendQuestionMark];NSLog(@"textField.text: %@", textField.text);// Output: The Green Fields are green?[textView capitalize];[textView appendWord:@"this"];[textView appendWord:@"is"];NSLog(@"textView.text: %@", textView.text);// Output: What A Wonderful View this is
This pattern should solve your problem. Hopefully :)
Some more background information is available here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105
What you want is a mixin. This is not supported in Objective-C. Categories are no mixins, because they add an api to one class not to many (>1) classes. Using categories, what is not possible for many reasons as you said, would not help you.
The usual way to solve that problem is to create a helper class containing the additional code and use it in both classes.
Then you will find yourself typing
[myUITextViewSubclass.helper doSomething]
instead of
[myUITextViewSubclass doSomething]
If this is really a problem, you can solve this with forward invocations. Just write a comment.
No. It is not possible.
The closest thing you could achieve would be to manually add functionality to UITextView
to make it mimic UITextField
. The obvious downside is that you must do this all manually, with your own code.