Frage

Ich habe eine Unterklasse von UiscrollView, in der ich intern auf das Scrolling -Verhalten reagieren muss. Der ViewController muss jedoch weiterhin auf Rückrufe des Scrolls anhören, daher kann ich den Delegierten in meiner Komponente nicht direkt stehlen.

Gibt es eine Möglichkeit, die Eigenschaft "Delegate" mit dem Namen "Delegate" zu halten und einfach Nachrichten anzuhören, oder entführen Sie die Eigenschaft der Delegierten irgendwie innen und leiten Sie Nachrichten nach dem Ausführen von Code nach außen weiter?

War es hilfreich?

Lösung

Ja, aber Sie müssen jede Delegierte -Methode in überschreiben Die Dokumente. Machen Sie im Grunde genommen eine zweite Delegiereigenschaft und implementieren Sie das Delegierprotokoll. Wenn Ihre Delegiermethoden aufgerufen werden, kümmern Sie sich um Ihr Unternehmen und rufen Sie dann die gleiche Methode auf Ihrem zweiten Delegierten aus der Delegierten -Methode an, die gerade ausgeführt wurde. Z.B

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // Do stuff here
    if ([self.delegate2 respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.delegate2 scrollViewDidScroll:scrollView];
    }
}

Andere Tipps

Um zu vermeiden, dass alle Delegierten -Methoden manuell überschrieben werden, können Sie eine Nachrichtenweiterleitung verwenden. Ich habe gerade das Gleiche mit einer Zwischenproxy -Klasse implementiert wie folgt:

MessageInterceptor.h

@interface MessageInterceptor : NSObject {
    id receiver;
    id middleMan;
}
@property (nonatomic, assign) id receiver;
@property (nonatomic, assign) id middleMan;
@end

MessageInterceptor.m

@implementation MessageInterceptor
@synthesize receiver;
@synthesize middleMan;

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([middleMan respondsToSelector:aSelector]) { return middleMan; }
    if ([receiver respondsToSelector:aSelector]) { return receiver; }
    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([middleMan respondsToSelector:aSelector]) { return YES; }
    if ([receiver respondsToSelector:aSelector]) { return YES; }
    return [super respondsToSelector:aSelector];
}

@end

MyScrollview.h

#import "MessageInterceptor.h"

@interface MyScrollView : UIScrollView {
    MessageInterceptor * delegate_interceptor;
    //...
}

//...

@end

MyScrollview.m (Bearbeitet, mit Dank an Jhabbott):

@implementation MyScrollView

- (id)delegate { return delegate_interceptor.receiver; }

- (void)setDelegate:(id)newDelegate {
    [super setDelegate:nil];
    [delegate_interceptor setReceiver:newDelegate];
    [super setDelegate:(id)delegate_interceptor];
}

- (id)init* {
    //...
    delegate_interceptor = [[MessageInterceptor alloc] init];
    [delegate_interceptor setMiddleMan:self];
    [super setDelegate:(id)delegate_interceptor];
    //...
}

- (void)dealloc {
    //...
    [delegate_interceptor release];
    //...
}

// delegate method override:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 1. your custom code goes here
    // 2. forward to the delegate as usual
    if ([self.delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.delegate scrollViewDidScroll:scrollView];
    }
}

@end

Mit diesem Ansatz die MessageInterceptor Das Objekt leitet automatisch alle Delegiertennachrichten an das reguläre Delegierungsobjekt weiter. außer Für diejenigen, die Sie in Ihrer benutzerdefinierten Unterklasse überschreiben.

Der Beitrag von E.James ergab eine hervorragende Lösung für die meisten Ansichten. Für Tastaturansichten wie Uitextfield und UitextView führt dies jedoch häufig zu einer Situation der unendlichen Schleife. Um es loszuwerden, habe ich es mit einem zusätzlichen Code behoben, was überprüft, ob der Selektor in bestimmten Protokollen enthalten ist oder nicht.

WzProtocolinterceptor.h

#import <Foundation/Foundation.h>

@interface WZProtocolInterceptor : NSObject
@property (nonatomic, readonly, copy) NSArray * interceptedProtocols;
@property (nonatomic, weak) id receiver;
@property (nonatomic, weak) id middleMan;

- (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol;
- (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ... NS_REQUIRES_NIL_TERMINATION;
- (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols;
@end

WzProtocolinterceptor.m

#import  <objc/runtime.h>

#import "WZProtocolInterceptor.h"

static inline BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol);

@implementation WZProtocolInterceptor
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if ([self.middleMan respondsToSelector:aSelector] &&
        [self isSelectorContainedInInterceptedProtocols:aSelector])
        return self.middleMan;

    if ([self.receiver respondsToSelector:aSelector])
        return self.receiver;

    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ([self.middleMan respondsToSelector:aSelector] &&
        [self isSelectorContainedInInterceptedProtocols:aSelector])
        return YES;

    if ([self.receiver respondsToSelector:aSelector])
        return YES;

    return [super respondsToSelector:aSelector];
}

- (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol
{
    self = [super init];
    if (self) {
        _interceptedProtocols = @[interceptedProtocol];
    }
    return self;
}

- (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ...;
{
    self = [super init];
    if (self) {
        NSMutableArray * mutableProtocols = [NSMutableArray array];
        Protocol * eachInterceptedProtocol;
        va_list argumentList;
        if (firstInterceptedProtocol)
        {
            [mutableProtocols addObject:firstInterceptedProtocol];
            va_start(argumentList, firstInterceptedProtocol);
            while ((eachInterceptedProtocol = va_arg(argumentList, id))) {
                [mutableProtocols addObject:eachInterceptedProtocol];
            }
            va_end(argumentList);
        }
        _interceptedProtocols = [mutableProtocols copy];
    }
    return self;
}

- (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols
{
    self = [super init];
    if (self) {
        _interceptedProtocols = [arrayOfInterceptedProtocols copy];
    }
    return self;
}

- (void)dealloc
{
    _interceptedProtocols = nil;
}

- (BOOL)isSelectorContainedInInterceptedProtocols:(SEL)aSelector
{
    __block BOOL isSelectorContainedInInterceptedProtocols = NO;
    [self.interceptedProtocols enumerateObjectsUsingBlock:^(Protocol * protocol, NSUInteger idx, BOOL *stop) {
        isSelectorContainedInInterceptedProtocols = selector_belongsToProtocol(aSelector, protocol);
        * stop = isSelectorContainedInInterceptedProtocols;
    }];
    return isSelectorContainedInInterceptedProtocols;
}

@end

BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol)
{
    // Reference: https://gist.github.com/numist/3838169
    for (int optionbits = 0; optionbits < (1 << 2); optionbits++) {
        BOOL required = optionbits & 1;
        BOOL instance = !(optionbits & (1 << 1));

        struct objc_method_description hasMethod = protocol_getMethodDescription(protocol, selector, required, instance);
        if (hasMethod.name || hasMethod.types) {
            return YES;
        }
    }

    return NO;
}

Und hier ist die Swift 2 -Version:

//
//  NSProtocolInterpreter.swift
//  Nest
//
//  Created by Manfred Lau on 11/28/14.
//  Copyright (c) 2014 WeZZard. All rights reserved.
//

import Foundation

/**
`NSProtocolInterceptor` is a proxy which intercepts messages to the middle man 
which originally intended to send to the receiver.

- Discussion: `NSProtocolInterceptor` is a class cluster which dynamically
subclasses itself to conform to the intercepted protocols at the runtime.
*/
public final class NSProtocolInterceptor: NSObject {
    /// Returns the intercepted protocols
    public var interceptedProtocols: [Protocol] { return _interceptedProtocols }
    private var _interceptedProtocols: [Protocol] = []

    /// The receiver receives messages
    public weak var receiver: NSObjectProtocol?

    /// The middle man intercepts messages
    public weak var middleMan: NSObjectProtocol?

    private func doesSelectorBelongToAnyInterceptedProtocol(
        aSelector: Selector) -> Bool
    {
        for aProtocol in _interceptedProtocols
            where sel_belongsToProtocol(aSelector, aProtocol)
        {
            return true
        }
        return false
    }

    /// Returns the object to which unrecognized messages should first be 
    /// directed.
    public override func forwardingTargetForSelector(aSelector: Selector)
        -> AnyObject?
    {
        if middleMan?.respondsToSelector(aSelector) == true &&
            doesSelectorBelongToAnyInterceptedProtocol(aSelector)
        {
            return middleMan
        }

        if receiver?.respondsToSelector(aSelector) == true {
            return receiver
        }

        return super.forwardingTargetForSelector(aSelector)
    }

    /// Returns a Boolean value that indicates whether the receiver implements 
    /// or inherits a method that can respond to a specified message.
    public override func respondsToSelector(aSelector: Selector) -> Bool {
        if middleMan?.respondsToSelector(aSelector) == true &&
            doesSelectorBelongToAnyInterceptedProtocol(aSelector)
        {
            return true
        }

        if receiver?.respondsToSelector(aSelector) == true {
            return true
        }

        return super.respondsToSelector(aSelector)
    }

    /**
    Create a protocol interceptor which intercepts a single Objecitve-C 
    protocol.

    - Parameter     protocols:  An Objective-C protocol, such as
    UITableViewDelegate.self.
    */
    public class func forProtocol(aProtocol: Protocol)
        -> NSProtocolInterceptor
    {
        return forProtocols([aProtocol])
    }

    /**
    Create a protocol interceptor which intercepts a variable-length sort of
    Objecitve-C protocols.

    - Parameter     protocols:  A variable length sort of Objective-C protocol,
    such as UITableViewDelegate.self.
    */
    public class func forProtocols(protocols: Protocol ...)
        -> NSProtocolInterceptor
    {
        return forProtocols(protocols)
    }

    /** 
    Create a protocol interceptor which intercepts an array of Objecitve-C 
    protocols.

    - Parameter     protocols:  An array of Objective-C protocols, such as
    [UITableViewDelegate.self].
    */
    public class func forProtocols(protocols: [Protocol])
        -> NSProtocolInterceptor
    {
        let protocolNames = protocols.map { NSStringFromProtocol($0) }
        let sortedProtocolNames = protocolNames.sort()
        let concatenatedName = sortedProtocolNames.joinWithSeparator(",")

        let theConcreteClass = concreteClassWithProtocols(protocols,
            concatenatedName: concatenatedName,
            salt: nil)

        let protocolInterceptor = theConcreteClass.init()
            as! NSProtocolInterceptor
        protocolInterceptor._interceptedProtocols = protocols

        return protocolInterceptor
    }

    /**
    Return a subclass of `NSProtocolInterceptor` which conforms to specified 
        protocols.

    - Parameter     protocols:          An array of Objective-C protocols. The
    subclass returned from this function will conform to these protocols.

    - Parameter     concatenatedName:   A string which came from concatenating
    names of `protocols`.

    - Parameter     salt:               A UInt number appended to the class name
    which used for distinguishing the class name itself from the duplicated.

    - Discussion: The return value type of this function can only be
    `NSObject.Type`, because if you return with `NSProtocolInterceptor.Type`, 
    you can only init the returned class to be a `NSProtocolInterceptor` but not
    its subclass.
    */
    private class func concreteClassWithProtocols(protocols: [Protocol],
        concatenatedName: String,
        salt: UInt?)
        -> NSObject.Type
    {
        let className: String = {
            let basicClassName = "_" +
                NSStringFromClass(NSProtocolInterceptor.self) +
                "_" + concatenatedName

            if let salt = salt { return basicClassName + "_\(salt)" }
                else { return basicClassName }
        }()

        let nextSalt = salt.map {$0 + 1}

        if let theClass = NSClassFromString(className) {
            switch theClass {
            case let anInterceptorClass as NSProtocolInterceptor.Type:
                let isClassConformsToAllProtocols: Bool = {
                    // Check if the found class conforms to the protocols
                    for eachProtocol in protocols
                        where !class_conformsToProtocol(anInterceptorClass,
                            eachProtocol)
                    {
                        return false
                    }
                    return true
                    }()

                if isClassConformsToAllProtocols {
                    return anInterceptorClass
                } else {
                    return concreteClassWithProtocols(protocols,
                        concatenatedName: concatenatedName,
                        salt: nextSalt)
                }
            default:
                return concreteClassWithProtocols(protocols,
                    concatenatedName: concatenatedName,
                    salt: nextSalt)
            }
        } else {
            let subclass = objc_allocateClassPair(NSProtocolInterceptor.self,
                className,
                0)
                as! NSObject.Type

            for eachProtocol in protocols {
                class_addProtocol(subclass, eachProtocol)
            }

            objc_registerClassPair(subclass)

            return subclass
        }
    }
}

/**
Returns true when the given selector belongs to the given protocol.
*/
public func sel_belongsToProtocol(aSelector: Selector,
    _ aProtocol: Protocol) -> Bool
{
    for optionBits: UInt in 0..<(1 << 2) {
        let isRequired = optionBits & 1 != 0
        let isInstance = !(optionBits & (1 << 1) != 0)

        let methodDescription = protocol_getMethodDescription(aProtocol,
            aSelector, isRequired, isInstance)

        if !objc_method_description_isEmpty(methodDescription)
        {
            return true
        }
    }
    return false
}

public func objc_method_description_isEmpty(
    var methodDescription: objc_method_description)
    -> Bool
{
    let ptr = withUnsafePointer(&methodDescription) { UnsafePointer<Int8>($0) }
    for offset in 0..<sizeof(objc_method_description) {
        if ptr[offset] != 0 {
            return false
        }
    }
    return true
}

Eigentlich hat das für mich funktioniert:

@implementation MySubclass {
    id _actualDelegate;
}

// There is no need to set the value of _actualDelegate in an init* method
- (void)setDelegate:(id)newDelegate {
    [super setDelegate:nil];
    _actualDelegate = newDelegate;
    [super setDelegate:(id)self];
}

- (id)delegate {
    return self;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([_actualDelegate respondsToSelector:aSelector]) { return _actualDelegate; }
    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [super respondsToSelector:aSelector] || [_actualDelegate respondsToSelector:aSelector];
}
@end

... Machen Sie die Unterklasse zu dem Nachrichten -Interceptor in der fantastischen Antwort von E.James.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top