Shared UITableViewDelegate - objective-c

I'm writting a subclass of UITableView and I want my subclass to handle some of the UITableViewDelegate methods itself before passing them along to the "real" delegate as well as forward all the UITableViewDelegate methods not implemented by my subclass.
In the subclass I have a private property:
#property (nonatomic, assign) id <UITableViewDelegate> trueDelegate;
which holds the "real delegate" that all the unimplemented methods should forward to. In both my init methods I set
self.delegate = self;
and I override - (void)setDelegate:(id) like this
-(void)setDelegate:(id<UITableViewDelegate>)delegate {
if (delegate != self) {
_trueDelegate = delegate;
} else {
[super setDelegate:self];
}
}
Then I override these to handle the message forwarding
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig;
sig = [[self.delegate class] instanceMethodSignatureForSelector:aSelector];
if (sig == nil) {
sig = [NSMethodSignature signatureWithObjCTypes:"#^v^c"];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = anInvocation.selector;
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
} else {
[anInvocation invokeWithTarget:_trueDelegate];
}
}
The problem is that the unimplemented delegate methods never get called on the tableview, therefore they are not given a chance to be forwarded along to the _trueDelegate object.
I tried checking for them here:
- (BOOL)respondsToSelector:(SEL)aSelector {
}
but that method is never called for the UITableViewDelegate methods although it catches other methods just fine.

For performance, UITableView checks and remembers which delegate methods are available as soon as the delegate is set. You set the delegate self first, then the trueDelegate. So at the time the delegate is set on the UITableView, trueDelegate is nil, and so -respondsToSelector: on that one always returns NO.
To fix that, set the delegate after trueDelegate is set. Also, you can simplify the forwarding code. Remove all the code you have above except for the property and replace it with:
- (void)setDelegate:(id <UITableViewDelegate>)delegate
{
if (delegate == self) return;
self.trueDelegate = delegate;
[super setDelegate:self];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([super respondsToSelector:aSelector]) return YES;
return [self.trueDelegate respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.trueDelegate;
}

Related

NSTreeController KVO notifications unexpectedly not firing

I've encountered a bit of a poser involving NSTreeController and KVO. NSTreeController's selectionIndexPaths property is documented as being KVO-observable—and when I observe it directly, it works perfectly. However, if I list NSTreeController's selectionIndexPath as a dependency of some other property, and then try to observe that, the notifications are not fired when one would expect.
Here's the shortest sample code I could come up with to demonstrate what I mean:
import Cocoa
class ViewController: NSViewController {
// Our tree controller
#IBOutlet dynamic var treeController: NSTreeController!
// Some random property on my object; you'll see why it's here later
#objc dynamic var foo: String = "Foo"
// A quick-and-dirty class to give us something to populate our tree with
class Thingy: NSObject {
#objc let name: String
init(_ name: String) { self.name = name }
#objc var children: [Thingy] { return [] }
}
// The property that the tree controller's `Content Array` is bound to
#objc dynamic var thingies: [Thingy] = [Thingy("Foo"), Thingy("Bar")]
// Dependencies for selectionIndexPaths
#objc private static let keyPathsForValuesAffectingSelectionIndexPaths: Set<String> = [
#keyPath(treeController.selectionIndexPaths),
#keyPath(foo)
]
// This property should be dependent on the tree controller's selectionIndexPaths
// (and also on foo)
#objc dynamic var selectionIndexPaths: [IndexPath] {
return self.treeController.selectionIndexPaths
}
// Some properties to store our KVO observations
var observer1: NSKeyValueObservation? = nil
var observer2: NSKeyValueObservation? = nil
// And set up the observations
override func viewDidLoad() {
super.viewDidLoad()
self.observer1 = self.observe(\.selectionIndexPaths) { _, _ in
print("This is only logged when foo changes")
}
self.observer2 = self.observe(\.treeController.selectionIndexPaths) { _, _ in
print("This, however, is logged when the tree controller's selection changes")
}
}
// A button is wired to this; its purpose is to set off the
// KVO notifications for foo
#IBAction func changeFoo(_: Any?) {
self.foo = "Bar"
}
}
In addition, the following setup is done in the storyboard:
Add a tree controller, and connect the view controller's treeController outlet to it.
Bind the tree controller's "Content Array" binding to thingies on the view controller.
Set the tree controller's "Children Key Path" to children.
Create an outline view, and bind its "Content" and "Selection Index Paths" bindings to arrangedObjects and selectionIndexPaths respectively on the tree controller.
Create a button, and point it at the view controller's changeFoo: method.
If you'd like to try it yourself, I've uploaded a sample project here.
The behavior is as follows:
The notification for observer2 is always fired whenever the outline view's (and thus the tree controller's) selection changes, as one would expect.
However, the notification for observer1 is not fired when the outline view's selection changes.
However, observer1's notification is fired when the button is clicked, and foo is changed. This suggests that the property's dependencies are being considered, but just not for this one particular key path.
Using the old-school method with an observeValue(forKeyPath:bla:bla:bla:) override instead of the swank Swift 4 closure-based system seems to behave the same way.
EDIT: Well, it's not Swift's fault! Same thing happens when I write this program in Objective-C:
#interface Thingy: NSObject
#property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
#end
#implementation Thingy
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self == nil) {
return nil;
}
self->_name = name;
return self;
}
- (NSArray *)children { return #[]; }
#end
void *ctxt1 = &ctxt1;
void *ctxt2 = &ctxt2;
#interface ViewController()
#property (nonatomic, strong) IBOutlet NSTreeController *treeController;
#property (nonatomic, copy) NSString *foo;
#property (nonatomic, copy) NSArray *thingies;
#end
#implementation ViewController
+ (NSSet *)keyPathsForValuesAffectingSelectionIndexPaths {
return [NSSet setWithObjects:#"treeController.selectionIndexPaths", #"foo", nil];
}
- (NSArray *)selectionIndexPaths {
return self.treeController.selectionIndexPaths;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.thingies = #[[[Thingy alloc] initWithName:#"Foo"], [[Thingy alloc] initWithName:#"Bar"]];
[self addObserver:self forKeyPath:#"selectionIndexPaths" options:0 context:ctxt1];
[self addObserver:self forKeyPath:#"treeController.selectionIndexPaths" options:0 context:ctxt2];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == ctxt1) {
NSLog(#"This only gets logged when I click the button");
} else if (context == ctxt2) {
NSLog(#"This gets logged whenever the selection changes");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (IBAction)changeFoo:(__unused id)sender {
self.foo = #"Bar";
}
#end
I've been staring at this for a while, and I cannot figure out why directly observing treeController.selectionIndexPaths works, but observing a property that depends on treeController.selectionIndexPaths does not. And since I've generally felt like I had a pretty good handle on KVO and its workings, it is really bugging me that I can't explain this.
Does anyone know the reason for this discrepancy?
Thanks!

Pass self.property to the function

I have a problem with get property. When I pass _videoRect to the drawArea function that nothing happens. But if I am change self.videoRect that work perfect.
My code:
#property (nonatomic, assign) BOOL isRecording;
#property (nonatomic) NSValue* videoRect;
#implementation Interactor
#synthesize videoRect = _videoRect;
- (id)init {
_isRecording = NO;
[self createVideoObserver];
return self;
}
- (void)record {
_isRecording = !_isRecording;
if (!_isRecording) {
[self stop];
[_presenter animateRecordButton:_isRecording];
} else {
[self drawArea:&(_videoRect) completion:nil];
}
}
- (void)drawArea:(NSValue* __strong *)rect completion:(void (^ __nullable)(void))completion {
if (!_isFullScreen) {
*rect = [NSValue valueWithRect:[self fullScreenRect]];
} else {
[self drawScreenRect];
}
if (completion != NULL) {
completion();
}
}
Setter and getter:
- (void)setVideoRect:(NSValue*)videoRect {
_videoRect = videoRect;
}
- (NSValue*)videoRect {
return _videoRect;
}
Create video observer:
- (void)createVideoObserver {
[[RACObserve(self, videoRect) skip:1] subscribeNext:^(id _) {
[self start];
[_presenter animateRecordButton:_isRecording];
}];
}
I don’t understand why observer doesn’t work. How can I pass self.videoRect to the drawArea function?
Your -drawArea:completion: method is taking a pointer to an NSValue, whereas self.videoRect is a property to an NSValue. In Swift, you can pass a property like this by reference, but in Objective-C, a property is really nothing more than a couple of methods, one of which sets an instance variable and one which returns it. So just passing the property into the method will not work. What you need to do is to read the property, pass a pointer to the resulting value, and then modify the property with the new value:
NSValue *videoRect = self.videoRect;
[self drawArea:&videoRect completion:^{
...
self.videoRect = videoRect;
}];

Is it possible to call the original delegate from a delegate?

Out of curiosity, is it possible to call the original delegate method implementation in a custom delegate implementation. like [super delegateMethod]; Or is that not possible. There are some scenarios where'd id like to add customizations to the behavior if certain conditions are met. Thank you in advance!
Yes, this can be achieved via a wrapper object which intercepts messages and forwards them to another delegate object. The following class intercepts calls to the scrollViewDidScroll: method before forwarding it (and any other delegate method invocations) to another UIScrollViewDelegate.
#import UIKit;
#interface ScrollHandler : NSObject <UIScrollViewDelegate>
- (instancetype)initWithScrollViewDelegate:(id<UIScrollViewDelegate>)delegate;
#end
#implementation ScrollHandler {
id<UIScrollViewDelegate> _scrollViewDelegate;
}
- (instancetype)initWithScrollViewDelegate:(id<UIScrollViewDelegate>)delegate {
self = [super init];
if (self) {
_scrollViewDelegate = delegate;
}
return self;
}
// Intercept specific method invocations:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Implement custom behavior here before forwarding the invocation to _scrollViewDelegate
[_scrollViewDelegate scrollViewDidScroll:scrollView];
}
// Forward unintercepted method invocations:
- (BOOL)respondsToSelector:(SEL)selector
{
// Ask _scrollViewDelegate if it responds to the selector (and ourself as well)
return [_scrollViewDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
// Forward the invocation to _scrollViewDelegate
[invocation invokeWithTarget:_scrollViewDelegate];
}
#end
Example use:
_scrollView = [[ScrollHandler alloc] init];
_scrollController = [[ScrollHandler alloc] initWithScrollViewDelegate:self];
_scrollView.delegate = _scrollController;

objective c message forwarding with forwardingTargetForSelector not always working

I have a view controller which defines a protocol which itself inherits another protocol.
I want any object that implements my protocol to also implement the inherited protocol.
I want to set my class to intercept some of the messages in the inherited protocol in order to configure some things internally but eventually would like to forward all of the messages to the delegate of my class
I could write a lot of boiler plate code to stub all of the protocol and intern call the delegate but I see that it breaks a lot of the time - any time the "super" protocol changes I need to restub this class once again.
I see that this is very predominant in custom UI controls. When reusing existing components - for instance tables or collection views you would like your data source to respond to all of the common protocols but some instances you need to configure the view according to the index or save a particular state.
I've tried using forwardingTargetForSelector in order to forward the messages I do not respond to , but it isn't always forwarding...
Here is a contrived code example:
Class A: (the top most protocol)
#
protocol classAProtocol <NSObject>
-(void)method1;
-(void)method2;
-(void)method3;
#end
My Class
#protocol MyClassProtocol <classAProtocol>
-(void)method4;
#end
#interface MyClass
#property (nonatomic,weak> id <MyClassProtocol> delegate;
#end
#interface MyClass (privateInterface)
#property (nonatomic,strong) ClassA *classAObject;
#end
#implementation MyClass
-(init)
{
self = [super init];
if (self)
{
_classAObject = [[ClassA alloc] init];
_classAObject.delegate = self; // want to answer some of the delegate methods but not all
}
}
-(void)method1
{
// do some internal configuration
// call my delegate with
[self.delegate method1];
}
-(id)forwardingTargetForSelector:(SEL)aSelector
{
if ([self respondsToSelector:aSelector])
{
return self;
}
if ([self.delegate respondsToSelector:aSelector])
{
return self.delegate;
}
return nil;
}
-(void)setDelegate:(id <MyClassProtocol>)delegate
{
self.delegate = delegate; // will forward some of the messages
}
#end
Returning self from forwardingTargetForSelector: makes no sense because it would never be called if self responded to the selector.
You need to implement these three methods:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([self.delegate respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:self.delegate];
else
[super forwardInvocation:anInvocation];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [super respondsToSelector:aSelector] || [self.delegate respondsToSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [self.delegate methodSignatureForSelector:selector];
}
return signature;
}
You shouldn't ever return self; from forwardingTargetForSelector:. Your check should mean it never is but if you ever did return self it would cause an infinite loop.
You need to be sure that a super class isn't implementing the method as this will prevent forwardingTargetForSelector: from being called. Check if the method is actually called.
forwardingTargetForSelector: is also only called when a method is called on your controller that it doesn't respond to. In your example you aren't calling [self ...];, you're calling [self.delegate ...]; so forwardingTargetForSelector: will not be called.

Reaching the members of subclass from super class

[sorry for my weak english]
I have got superclass and it's subclass (subclasses) - subclass is the view with some
sprites - superclass has some helper functions for example function to fire an animation.
I want to call 'fire the animation' in super from subclass but it shows that in superclass.
I need an access the subclass view (to add animated viewsprite to self.view in subclass)
How can I reach the subclass members from superclass ??? :-/
#interface MONBase : NSObject
// example action. required override
- (void)performSomeAction;
// example accessor. required override
- (MONThing *)thing;
#end
#implementation MONBase
- (void)performSomeAction
{
assert(0 && "required override");
}
- (MONThing *)thing
{
assert(0 && "required override");
return nil;
}
- (void)example
{
MONThing * thing = [self thing];
[self configureThing:thing];
[self performSomeAction];
}
#end
#interface MONSubclass : MONBase
#end
#implementation MONSubclass
- (void)performSomeAction
{
[self doStuff];
}
- (MONThing *)thing
{
return self.something;
}
#end