Starter Step

Custom UIAlertView with UITableView

Posted on: March 24, 2009

I have been working on a story: “As a user, I should be able to search for hotels around a given location so that I can add the hotel to my trip.”.

I built my basic form for the user to fill out the location information and provided my awesome continue button to get hotels:

locationform

I quickly realized that the user’s location could be resolved to more than a single place.  If the user entered in county: US and then city: Greenville, there would be 10 or so matches.  I would need the user to choose which was the correct city.  One way to do this would have been to take the user to a new UITableViewController with all the matches and ask the user to choose a match, then pop then continue on to find the hotels around the selected location.  While this would work, I dont like it.  What if it worked like Google Maps?  The Google Maps application shows the user a UIAlertView with an embedded TableView. It’s pretty clean.  It works well because the user does not need to go to a separate page to choose a location match.  I want my user to have this same experience.

If you have used the UIAlertView much, you know it is very limited on custimization opportunity.  I decided to subclass UIAlertView to build my new location selector.  I ran into a few “a ha” moments when trying to mimic the Google Apps look and feel.  First, their UIAlertView has rouned top and bottom corners that are independent of the TableView.  They are actually using a PlainStyle TableView and when you scroll the table in the alert, the table looks like it is sitting inside of a rounded rect UIScrollView.  Tricky Indeed!

Our solution was to use some transparent rounded corners with a dropshadow on the top image.  We also wanted the height of the custom alert to be dynamic based on the size of the embedded UITableView.  However, UIAlertViews do not have a frame or height property. The height of the UIAlertView is based on the length of its message property.  Our solution to this problem was to identify the number of rows in the table and add return characters to the message property of the UIAlertView.

Here is what it ended up looking like:

locationresults

In case you wanna see some code, Here ya go:

@protocol AlertTableViewDelegate

-(void)didSelectRowAtIndex:(NSInteger)row withContext:(id)context;

@end

@interface AlertTableView : UIAlertView <UITableViewDelegate, UITableViewDataSource>{
    UITableView *myTableView;
    id<AlertTableViewDelegate> caller;
    id context;
    NSArray *data;
	int tableHeight;
}
-(id)initWithCaller:(id<AlertTableViewDelegate>)_caller data:(NSArray*)_data title:(NSString*)_title andContext:(id)_context;
@property(nonatomic, retain) id<AlertTableViewDelegate> caller;
@property(nonatomic, retain) id context;
@property(nonatomic, retain) NSArray *data;
@end

@interface AlertTableView(HIDDEN)
-(void)prepare;
@end
#import "AlertTableView.h"



@implementation AlertTableView

@synthesize  caller, context, data;

-(id)initWithCaller:(id)_caller data:(NSArray*)_data title:(NSString*)_title andContext:(id)_context{
    NSMutableString *messageString = [NSMutableString stringWithString:@"\n"];
    tableHeight = 0;
    if([_data count] &lt; 6){
        for(int i = 0; i &lt; [_data count]; i++){
            [messageString appendString:@"\n\n"];
            tableHeight += 53;
        }
    }else{
        messageString = @"\n\n\n\n\n\n\n\n\n\n";
        tableHeight = 207;
    }
    
    if(self = [super initWithTitle:_title message:messageString delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil]){
        self.caller = _caller;
        self.context = _context;
        self.data = _data;
        [self prepare];
    }
    return self;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [self.caller didSelectRowAtIndex:-1 withContext:self.context];
}

-(void)show{
    self.hidden = YES;
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myTimer:) userInfo:nil repeats:NO];
    [super show];
}

-(void)myTimer:(NSTimer*)_timer{
    self.hidden = NO;
    [myTableView flashScrollIndicators];
}

-(void)prepare{
    myTableView = [[UITableView alloc] initWithFrame:CGRectMake(11, 50, 261, tableHeight) style:UITableViewStylePlain];
    if([data count] &lt; 5){
        myTableView.scrollEnabled = NO;
    }
    myTableView.delegate = self;
    myTableView.dataSource = self;
    [self addSubview:myTableView];
    
    UIImageView *imgView = [[[UIImageView alloc] initWithFrame:CGRectMake(11, 50, 261, 4)] autorelease];
    imgView.image = [UIImage imageNamed:@"top.png"];
    [self addSubview:imgView];
    
    imgView = [[[UIImageView alloc] initWithFrame:CGRectMake(11, tableHeight+46, 261, 4)] autorelease];
    imgView.image = [UIImage imageNamed:@"bottom.png"];
    [self addSubview:imgView];

    
    CGAffineTransform myTransform = CGAffineTransformMakeTranslation(0.0, 10);
    [self setTransform:myTransform];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = (UITableViewCell*) [tableView dequeueReusableCellWithIdentifier:@"ABC"];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"ABC"] autorelease];
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
        cell.font = [UIFont boldSystemFontOfSize:14];
    }
    cell.text = [[data objectAtIndex:indexPath.row] description];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [self dismissWithClickedButtonIndex:0 animated:YES];
    [self.caller didSelectRowAtIndex:indexPath.row withContext:self.context];
}

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

-(void)dealloc{
    self.data = nil;
    self.caller = nil;
    self.context = nil;
    [myTableView release];
    [super dealloc];
}

@end
About these ads

28 Responses to "Custom UIAlertView with UITableView"

Hi. Thanks for the code. Could you provide the top image you created, with rounded corners and a dropshadow?

Here are links to the two images:
Top

Bottom

Hi, the code works perfect, you saved me a lot of time.

thanks,
sven

Hey I copied the code and tried to run it but it gives me this error:

“request for member ‘view’ is something not a structure or union.”

Anybody know what that means. Please!!!

Hi Jake,

Thanks for the solution. I am just a beginner and want to implement a alert as you have proposed.

I tried with your sample code. But I don’t know how to init the alert view and show it. Please if possible post me the code which utilizes this custom alert view and shows it.

Thanks in advance.
Raj

Just out of curiosity using this alertview was your app approved by the App Store? I hear they’re being rather strict on what type of hackery they allow.

Why would you call this hackery? Its a custom component.

Hi Jake,
Like Raj, I m in trouble = I can not manage to use your code :(
it s crashing when I init the AlertTableView (client side ) ! My fault, for sure =)
So could you post or send me a little example with a nsarray ?
many ( you can not count all of them ;) thanks in advance !!

If you are looking for a sample of this in use, look here: http://starterstep.wordpress.com/2009/09/16/custom-uialertview-with-tableview-part-2/

I ll test it this evening!
Many thanks for your support and quick reply =)

[...] here are part 1 and part 2 with sample project of how to do exactly [...]

great stuff
i didn’t think of adding images over top and bottom
nice work

to init/call use something link this

AlertTableView *dcListAlert = [[AlertTableView alloc] initWithCaller:self data:allSteps title:@”Double Check List” andContext:nil];
[dcListAlert show];
[dcListAlert release];

be sure to add AlertTableViewDelegate in caller interface
and
-(void)didSelectRowAtIndex:(NSInteger)row withContext:(id)context {
NSLog([NSString stringWithFormat:@"Index %d clicked", row]);
}
in implementation of caller

Your code is broken when you have less than 6 elements in your dataset.

I’d have to take a look…I thought I put something in the code for when there are fewer options.

First, thanks for this code. It’s *very* helpful!

I just hit the issue I think Stephen is describing. The row heights don’t seem to line up with the padding created by the message string for row sizes less than 6. (It works for 6 or more because you’ve hardcoded the table height.)

I think the problem has less to do with the code, and more to do with a change in the iPhone API. setFont: and setText: are now deprecated, and the suggested replacements are [textLabel setFont] and [textLabel setText]. I suspect that in the process of deprecation the implementation changed, which affects cell heights. That’s all that’s going on.

I’m fiddling with it, and I’ll post a fix if I find a clean one.

Got it. I twiddled the row height to 42 when calculating tableHeight, and implemented the UITableViewDataSource method tableView:heightForRowAtIndexPath: that returns 42 in UIAlertTableView.m, which makes the cells actually 42 pixels high. (I chose 42 because it’s approximately 207/5, so in line with the “6 or more” case.) Seems to work well.

I am having trouble implimenting your code. I keep getting issues with the alerttableview.m

The error says ‘lt’ undeclared.

Does anyone know a solution?

The lt is undeclared error occurs because &lt is actually a typo caused by HTML and your internet browser.
It should be the less than symbol.

Hi great examples !!! I try hard to find a way to put an image, (logo company) at the top of my tableview. But I didn’t find any solution. How did U managed to do it ?
(I don’t have any view, just a tableview, so addsubview doesn’t work)

Thanks.

Thanks for the code!

Hi! Great article! We use the idea for the bottom and top images to make a better looking textfields on our alerts. Any change you have Retina display enabled versions of them ?
Thank you

Great code. With Andy’s tips to fix table height for less than 6 elements and with some fix into code (deprecated methods and ‘<' character) it works fine!

Thanks a lot!

I’ve seen good posts. Thank you.
Your code has helped me a lot to learn :D

Great example. Thank you!

Thanks great article. Helped me alot saving my time!!!


Jai

Thanks great Article. You saved my time,.. but i am wondering whether subclassing UIAlertView is right approach for custom menu with image icons.

This blog post is quite old. There are probably better ways to achieve this now.

Hi, Thanks its working fine in iOS 6 but not in iOS 7.0.

Any Suggestion on this.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: