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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Thank you for posting this. Wouldn't you also want to override
getDelegate:
to returnself.externalDelegate
?