addzo
addzo

Reputation: 867

UITableview/UIView Delegate Confusion

I am making my own drop down menu because I am not able to find an open source one that does exactly what I need. I have implemented the dropdown as a UIView and am adding it to the superview of the button that is tapped in order to show it.

Code:

ViewController.m

#import "ViewController.h"
#import "MenuView.h"

@interface ViewController () <MenuViewDelegate>
@property (weak, nonatomic) IBOutlet UIView *fakeHeader;
@property (nonatomic, strong) MenuView *menuView;
@end

@implementation ViewController

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

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)btnTapped:(id)sender {
    NSArray *array = @[@"Item 1", @"Item 2", @"Item 3", @"Item 4"];
    NSArray *imgArray = nil;

    if (self.menuView == nil) {
        self.menuView = [[MenuView alloc] showDropDownWith:sender txtArr:array     imgArr:imgArray direction:@"down" delegate:self];

        self.menuView.delegate = self;
    } else {
        [self.menuView hideDropDown:sender];

        self.menuView = nil;
    }
}

- (void) menuDelegateMethod:(MenuView *)sender {
    self.menuView = nil;
}

@end

MenuView.h

#import <UIKit/UIKit.h>

@class MenuView;

@protocol MenuViewDelegate
- (void)menuDelegateMethod:(MenuView *)sender;
@end

@interface MenuView : UIView
@property (nonatomic, retain) id <MenuViewDelegate> delegate;

- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate;
- (void)hideDropDown:(UIButton *)button;
@end

MenuView.m

#import "MenuView.h"
#import "QuartzCore/QuartzCore.h"

@interface MenuView () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *table;
@property (nonatomic, strong) UIButton *btnSender;
@property (nonatomic, retain) NSString *animationDirection;
@property (nonatomic, retain) NSArray *list;
@property (nonatomic, retain) NSArray *imageList;
@end

@implementation MenuView

- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate {
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGFloat origin = [UIScreen mainScreen].bounds.origin.x;
    CGFloat realHeight = 40 * txtArr.count;

    self.btnSender = button;
    self.animationDirection = direction;
    self.table = (UITableView *)[super init];

    if (self) {
        // Initialization code
        CGRect btn = button.frame;
        self.list = [NSArray arrayWithArray:txtArr];
        self.imageList = [NSArray arrayWithArray:imgArr];

        if ([direction isEqualToString:@"up"]) {
            self.frame = CGRectMake(origin, (btn.origin.y - btn.size.height) , width, 0);

            self.layer.shadowOffset = CGSizeMake(0, 1);
        } else if ([direction isEqualToString:@"down"]) {
            self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, 0);

            self.layer.shadowOffset = CGSizeMake(0, 1);
        }

        self.layer.masksToBounds = YES;
        self.layer.shadowOpacity = 0.2;

        self.table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
        self.table.delegate   = delegate;
        self.table.dataSource = self;
        self.table.backgroundColor = [UIColor colorWithRed:0.239 green:0.239 blue:0.239 alpha:1];
        self.table.separatorStyle  = UITableViewCellSeparatorStyleSingleLine;
        self.table.separatorColor  = [UIColor lightGrayColor];
        self.table.backgroundColor = [UIColor whiteColor];
        self.table.userInteractionEnabled = YES;

        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.5];

        if ([direction isEqualToString:@"up"]) {
            self.frame = CGRectMake(origin, (btn.origin.y - realHeight), width, realHeight);
        } else if([direction isEqualToString:@"down"]) {
            self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, realHeight);
        }

        self.table.frame = CGRectMake(0, 0, width, realHeight);
        [UIView commitAnimations];
        [button.superview addSubview:self];

        self.table.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];

        [self addSubview:self.table];
    }

    return self;
}

- (void)hideDropDown:(UIButton *)button {
    CGRect btn = button.frame;
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGFloat origin = [UIScreen mainScreen].bounds.origin.x;

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5];

    if ([self.animationDirection isEqualToString:@"up"]) {
        self.frame = CGRectMake(origin, btn.origin.y, width, 0);
    } else if ([self.animationDirection isEqualToString:@"down"]) {
        self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10),     width, 0);
    }

    self.table.frame = CGRectMake(0, 0, width, 0);

    [UIView commitAnimations];
}

#pragma mark - Table View DataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView     dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.textLabel.font = [UIFont systemFontOfSize:15];
        cell.textLabel.textAlignment = NSTextAlignmentLeft;
    }

    cell.textLabel.text = [self.list objectAtIndex:indexPath.row];

    cell.backgroundColor = [UIColor colorWithRed:48.0f/255.0f green:48.0f/255.0f blue:48.0f/255.0f alpha:1.0f];
    cell.textLabel.textColor = [UIColor lightTextColor];

    return cell;
}

#pragma mark - Table View Delegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 40;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [self hideDropDown:self.btnSender];

    [self myDelegate];
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    // Remove seperator inset
    if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
        [cell setSeparatorInset:UIEdgeInsetsZero];
    }

    // Prevent the cell from inheriting the Table View's margin settings
    if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
        [cell setPreservesSuperviewLayoutMargins:NO];
    }

    // Explictly set your cell's layout margins
    if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
        [cell setLayoutMargins:UIEdgeInsetsZero];
    }
}

#pragma mark - View Delegate
- (void)myDelegate {
    [self.delegate menuDelegateMethod:self];
}

@end

Shows up perfectly but the didSelect method is never called.

I don't have any views over the top of it that would be stealing the touch events.

It seems that UIViews might not be able to be UITableviewDelegates. If that's true I don't know why, when I make the calling view controller the delegate, it still fails to didSelect.

NOTE: I am aware of the animation faux pas by not using newer methods. This is based on old example code. I will update the animation after I get this issue worked out.

Questions:

  1. Is it true that UIViews can not be UITableView Delegates?

  2. If so, how does one make a calling view controller the delegate for the table view that resides in the UIView? Other than the process of setting it up as a UITableViewDelegate and assigning the calling view controller as the delegate at the time of the creation of the table.

  3. Did I miss something in the way I set this up that steals the cell taps so that didSelect does not get called, either in the view or the viewController?

Thanks for the help.

Upvotes: 1

Views: 208

Answers (2)

danh
danh

Reputation: 62676

Agree with @Piotr that the menu must be the table's delegate, so replace self.table.delegate = delegate; with self.table.delegate = self; in MenuView.m.

But additionally, MenuView.m never invokes its delegate, which it should upon selection in the tableview....

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [self hideDropDown:self.btnSender];

    // remove this, since it does nothing
    //[self myDelegate];

    // replace it with
    [self.myDelegate menuDelegateMethod:self];
}

That last line says, tell the delegate of the menu that something happened.

Another problem is that the menu doesn't really tell the delegate what happened. Certainly the delegate will be interested in which item is selected. Consider changing the protocol to something like:

@protocol MenuViewDelegate
- (void)menuView:(MenuView *)sender didSelectOptionAtIndex:(NSInteger)index;
@end
// calling it
[self.myDelegate menuView:self didSelectOptionAtIndex:indexPath.row];

Another alternative is to hand back the selected string to the delegate. This can be found in the tableview's datasource at the indexPath.row.

Finally, its good practice to not-retain your delegate since the customer of the Menu might retain it, resulting in a retain cycle. Instead, declare the delegate:

// notice "weak"
@property (nonatomic, weak) id <MenuViewDelegate> delegate;

Upvotes: 2

Piotr
Piotr

Reputation: 1366

As I can see, you are passing ViewController (it is MenuViewDelegate) to showDropDownWith method, and then use it as table delegate. This is not correct.

You should pass self there (same as with data source), because you want MenuView to be delegate of table, not ViewController, right?

self.table.delegate = self;

Upvotes: 2

Related Questions