Skip to content

Instantly share code, notes, and snippets.

@tangphillip
Created May 10, 2014 23:28
Show Gist options
  • Save tangphillip/818bdd6d916b62f607b7 to your computer and use it in GitHub Desktop.
Save tangphillip/818bdd6d916b62f607b7 to your computer and use it in GitHub Desktop.
Objective-C: Subclassing for delegate behavior!
// A demonstration: fixing the UITextView return bug with a subclass
//
// Code for acutally fixing the bug from http://inessential.com/2014/01/07/uitextview_the_solution
#import <UIKit/UIKit.h>
@interface PSTTextView : UITextView
@end
#import "PSTTextView.h"
#import <objc/runtime.h>
@interface PSTTextView () <UITextViewDelegate>
@property (nonatomic, weak) id<UITextViewDelegate> externalDelegate;
@end
@implementation PSTTextView
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[super setDelegate:self];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
if (self = [super initWithFrame:frame textContainer:textContainer]) {
[super setDelegate:self];
}
return self;
}
#pragma mark - Mutator
- (void)setDelegate:(id<UITextViewDelegate>)delegate {
self.externalDelegate = delegate;
}
#pragma mark - Method forwarding
+ (BOOL)isInstanceMethodSelector:(SEL)selector inProtocol:(Protocol *)protocol {
struct objc_method_description requiredMethod = protocol_getMethodDescription(protocol, selector, YES, YES);
struct objc_method_description optionalMethod = protocol_getMethodDescription(protocol, selector, NO, YES);
return (requiredMethod.name != NULL || optionalMethod.name != NULL);
}
- (id)forwardingTargetForSelector:(SEL)selector {
BOOL isDelegateSelector = [[self class] isInstanceMethodSelector:selector
inProtocol:@protocol(UITextViewDelegate)];
if (isDelegateSelector && [self.externalDelegate respondsToSelector:selector]) {
return self.externalDelegate;
}
return [super forwardingTargetForSelector:selector];
}
- (BOOL)respondsToSelector:(SEL)selector {
BOOL isDelegateSelector = [[self class] isInstanceMethodSelector:selector
inProtocol:@protocol(UITextViewDelegate)];
if (isDelegateSelector && [self.externalDelegate respondsToSelector:selector]) {
return YES;
}
return [super respondsToSelector:selector];
}
#pragma mark - Return bug fix
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:@"\n"] || [text isEqualToString:@""]) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(scrollToShowSelection) object:nil];
[self performSelector:@selector(scrollToShowSelection) withObject:nil afterDelay:0.1]; /*Smaller delays are unreliable.*/
}
if ([self.externalDelegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) {
return [self.externalDelegate textView:textView shouldChangeTextInRange:range replacementText:text];
}
else return YES;
}
- (void)scrollToShowSelection {
if (self.selectedRange.location < self.text.length) {
return;
}
CGPoint bottomOffset = CGPointMake(0, MAX(0, self.contentSize.height - self.bounds.size.height));
[self setContentOffset:bottomOffset animated:YES];
}
@end
@ma11hew28
Copy link

Thank you for posting this. Wouldn't you also want to override getDelegate: to return self.externalDelegate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment