0

我想创建一个继承自 UITableView 的新类 MYTableView。MYTableView 将有一个 NSArray 属性来存储它的所有数据。这意味着我不再需要担心实现numberOfRowsand cellForRow,因为 MYTableView 将是它自己的数据源并提供必要的数据。另外,我希望 MYTableView 在didSelectRow方法中有一些自定义逻辑,所以我也将它设置为它自己的委托。

现在问题来了……我有一个 TVC(一个 UITableViewController)对象,它有一个 MYTableView 实例。我想覆盖didSelectRow方法中的逻辑。也就是说,除了 TVC 的逻辑之外,我还希望 MYTableView 的逻辑发生。

解决方案1: tableView.delegate = TVC
显然这行不通。如果我尝试将 tableView 的委托设置回 TVC,那么是的,TVC 的 didSelectRow 方法被调用,但是 MYTableView 的逻辑永远不会被调用,因为我们劫持了委托。

解决方案 2: tableView.delegate2 = TVC
我可以创建第二个委托(参见下面的代码)并将 TVC 设置为委托 2。然后,我将从 tableView.delegate 的 didSelectRow 方法中调用 delegate2 的 didSelectRow 方法。

问题:

  • 拥有delegatedelegate2是丑陋的。此外,如果用户将委托设置为其他内容,事情就会中断。
  • 我必须转发 TVC 可能想要实施的每一个事件。

问题:

因为这不是最佳解决方案。你能想出更好的解决方案吗?

如果我无法控制继承的类(例如,如果我从 UITableView 继承),我有什么选择?

如果我确实可以控制继承的类(例如,我正在使用我拥有其源代码的假设 UITableView),那么实现它以允许这种功能的最佳方法是什么?

笔记:

  • 目前didSelectRow在这些示例中只有 1 行代码。显然,如果它只有一行代码,那么这不会是一个大问题,我会找到解决它的方法。真正的代码是多行代码。

完整代码如下:(对于解决方案2)

// ---------------------------------------------------
// VC.m
// ---------------------------------------------------

@implementation VC

- (MYTableView *)myTableView {
    return (MYTableView *)self.tableView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;
    self.myTableView.delegate2 = self;
}

- (void)insertNewObject:(id)sender
{
    static NSInteger i = 0;
    if (i % 3 == 0) {
        [self.myTableView insertObject:[NSNull null] inDataArrayAtIndex:0];
    } else {
        [self.myTableView insertObject:[NSDate date] inDataArrayAtIndex:0];
    }
    i++;
}

#pragma mark - MYTableViewDelegate

- (void)myTableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"Selected row: %@", indexPath);
}

@end


// ---------------------------------------------------
// MYTableView.h
// ---------------------------------------------------

@class MYTableView;

@protocol MYTableViewDelegate <UITableViewDelegate>
@optional
- (void)myTableView:(MYTableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface MYTableView : UITableView <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, weak) id<MYTableViewDelegate> delegate2;
- (void)insertObject:(NSObject *)object inDataArrayAtIndex:(NSUInteger)index;
@end

// ---------------------------------------------------
// MYTableView.m
// ---------------------------------------------------

@interface MYTableView ()
@property (nonatomic, strong) NSMutableArray *dataArray;
@end

@implementation MYTableView

- (void)commonInit {
    self.dataArray = [NSMutableArray array];
    self.dataSource = self;
    self.delegate = self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)insertObject:(NSObject *)object inDataArrayAtIndex:(NSUInteger)index {
    [self.dataArray insertObject:object atIndex:index];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    [self insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.dataArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString * const identifier = @"String Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }

    cell.textLabel.text = [self.dataArray[indexPath.row] description];
    return cell;
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSObject *object = self.dataArray[indexPath.row];
    if ([NSNull null] == object) {
        [self deselectRowAtIndexPath:indexPath animated:YES];
    }

    if ([self.delegate2 respondsToSelector:@selector(myTableView:didSelectRowAtIndexPath:)]) {
        [self.delegate2 myTableView:self didSelectRowAtIndexPath:indexPath];
    }
}


@end
4

3 回答 3

0

如果您创建了一个单独的类,该类仅基于您的数据源实现 UITableViewDataSource 协议怎么办。这可以是一个 NSObject 或者我想你的 MyTableView 如果你真的想这样做的话。这是一个高度可重用的类,它将使您将来的生活更轻松。

至于双委托问题,如果您在 MyTableView 中通过 UITableViewDelegate 处理行的选择,但设置一个基于块的系统来选择视图控制器,该怎么办。

void(^RowSelected)(void)(NSObject *selectedObject, NSIndexPath *indexPath)
于 2013-10-17T01:36:19.183 回答
0

您可以使用两个委托,但重写 setDelegate 方法以使事情至少看起来更干净。我在 UITextView 的子类中做了类似的事情:https ://github.com/mbachrach/GCPTextView

于 2013-10-17T00:56:15.520 回答
0

Fixing UITextView on iOS 7中,作者提到他会自动转发 PSPDFTextView 源代码中的所有委托方法

他通过执行以下操作来实现这一点:

  1. 通过调用将自己设置为代表[super setDelegate:self]
  2. 重写setDelegate:以在名为 的属性中捕获外部委托realDelegate
  3. 在您实现的任何委托方法中,将它们转发给realDelegate(如果它响应该选择器):

    - (void)textViewDidBeginEditing:(UITextView *)textView {
        id<UITextViewDelegate> delegate = self.realDelegate;
        if ([delegate respondsToSelector:_cmd]) {
            [delegate textViewDidBeginEditing:textView];
        }
    
        // custom code goes here
    }
    
  4. 实现respondsToSelector:, methodSignatureForSelector:, 并forwardInvocation:转发其余方法:

    - (BOOL)respondsToSelector:(SEL)aSelector {
        return [super respondsToSelector:aSelector] || [self.realDelegate respondsToSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        return [super methodSignatureForSelector:aSelector] ?: [(id)self.realDelegate methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        id delegate = self.realDelegate;
        if ([delegate respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:delegate];
        }
    }
    

这种方法的一个缺点是它会将任何调用转发给 realDelegate;不仅仅是那些在 UITextFieldDelegate 中实现的。例如,如果您的委托是 UIViewController 并且您执行以下代码:

self.textView.delegate = self;
[(id)self.textView viewDidLoad];

理想情况下,您希望它崩溃(因为您的 UITextView 子类没有实现viewDidLoad)。但是,因为在子类上调用的任何方法都会被转发给delegate,文本视图会看到它不知道如何处理viewDidLoad,所以它会询问delegate;委托将报告它确实知道如何执行此代码,因此它将调用委托的 viewDidLoad 方法!

于 2015-08-15T18:18:42.940 回答