3

I'm sorry if this seems like a basic question but it seems my Googling skills aren't up to the task of finding my answer, and I hope you can help!

Basically, I want my app to have either a button, or a label and onclick it reveals some text underneath itself (in the form of an FAQ, as in this link here: http://aptow.com/faq) and pushes everything down with it. I'm aware of how to click and show an element which was previously hidden, but I'm not sure how to do that slide down effect, at the same time as pushing everything else further down the screen on click, and then re-arranging it near the top of the screen once the user clicks again to re-hide the previously hidden content. The link I've given shows exactly what I'm looking to do.

Any ideas? Thanks in advance.

Creights
  • 907
  • 1
  • 12
  • 25
  • 1
    This can be easily achieved with the UIView methods beginAnimations and commitAnimations which you call before and after you change the frames etc. accoringly. Holwever since iOS4 the framework provides block based animations. Unfortunately I am not yet experienced in using block based animations. That is why I do not post this as an answer but as a comment. But you can certainly google for animation blocks or uianimation blocks and find some tutorials and other resources. – Hermann Klecker Aug 22 '12 at 08:26
  • If you use a UITableView object to display your text items, then you may get the animation for free using the methods `insertRowsAtIndexPaths:withRowAnimation:`, `beginUpdates` and `endUpdates and ``deleteRowsAtIndexPaths:withRowAnimation:` respectively. (Just to provide an alternative solution) – Hermann Klecker Aug 22 '12 at 08:30
  • You want a scroll-down menu basically? – tiguero Aug 22 '12 at 09:00

3 Answers3

1

As @hermannKlecker said, you can do this with a block using the method animateWithDuration:animations:

[view animateWithDuration:duration animations:^{ //Block code here
/*set the new positions of the view either move up or down*/
}];

This code should do it.

geminiCoder
  • 2,918
  • 2
  • 29
  • 50
  • so with this, I'd have to declare my blocks in code to therefore know their location on the screen? Or is there something with is the equivalent of 'moveDownTheScreen' and then you state how many pixels? – Creights Aug 22 '12 at 09:44
  • Well, you do not move the blocks. You move the views :) And yes, you will have to re-locate every view that is affected by his animation. However, a smart view hierarchy may reduce the number of views to be moved. You could come down to moving one view only if it contains all views below as subviews (of subviews of subviews of...) – Hermann Klecker Aug 22 '12 at 09:49
  • okay, so would I need to know a view's position on the screen in that case, or can you point me at a method which is something like 'moveDownScreen' so I can state the number of pixels and then I'm done? – Creights Aug 22 '12 at 10:01
1

If you want to do something similar to the FAQ page you posted the link of, you can use a UITableView using dynamic cell heights.

A simple UITableViewController could look like this:

#import "ViewController.h"

@interface ViewController () {
    NSArray *data;
    int selected;
}
@end

@implementation ViewController

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

    data = [NSArray arrayWithObjects:
                     [NSDictionary dictionaryWithObjectsAndKeys:@"Title 1", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 2", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 3", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 4", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 5", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 6", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"Title 7", @"title", @"Lorem ipsum dolor bla blab bla", @"details", nil],nil ];

}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == selected) {
        return 130;
    } else
        return tableView.rowHeight;
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"ci";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }
    NSDictionary *tmp = [data objectAtIndex:indexPath.row];

    cell.textLabel.text = nil;


    static int TITLE_LABEL_TAG  = 122;
    UILabel *titleLabel = (UILabel *)[cell.contentView viewWithTag:TITLE_LABEL_TAG];
    if (!titleLabel) {
        titleLabel = [[UILabel alloc] initWithFrame:CGRectNull];
        titleLabel.tag = TITLE_LABEL_TAG;
        [cell.contentView addSubview:titleLabel];
        titleLabel.backgroundColor = [UIColor clearColor];
        titleLabel.numberOfLines = 1;
        titleLabel.font = [UIFont boldSystemFontOfSize:16];
        [cell.contentView addSubview:titleLabel];
    }
    titleLabel.frame = (CGRectMake(8, 8, cell.contentView.frame.size.width-16, 20));
    titleLabel.text = [tmp objectForKey:@"title"];

    static int DETAIL_LABEL_TAG  = 123;
    UILabel *detailLabel = (UILabel *)[cell.contentView viewWithTag:DETAIL_LABEL_TAG];
    if (!detailLabel) {
        detailLabel = [[UILabel alloc] initWithFrame:CGRectNull];
        detailLabel.tag = DETAIL_LABEL_TAG;
        [cell.contentView addSubview:detailLabel];
        detailLabel.backgroundColor = [UIColor clearColor];
        detailLabel.numberOfLines = 0;
    }
    if (indexPath.row == selected) {
        detailLabel.frame = (CGRectMake(8, 30, cell.contentView.frame.size.width-16, 100));

        detailLabel.hidden = NO;
        detailLabel.text = [tmp objectForKey:@"details"];
    } else {
        detailLabel.hidden = YES;
    }

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    NSMutableArray *ip2reload = [NSMutableArray array];
    if (selected > -1 && selected != indexPath.row)
        [ip2reload addObject:[NSIndexPath indexPathForRow:selected inSection:0]];
    [ip2reload addObject:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
    selected = indexPath.row;
    [tableView reloadRowsAtIndexPaths:ip2reload withRowAnimation:UITableViewRowAnimationAutomatic];
}


@end

You would surely have to calculate the actual row height of the selected row based on the content (e.g. like explained here: Iphone - when to calculate heightForRowAtIndexPath for a tableview when each cell height is dynamic?).

And you can use scrollToRowAtIndexPath:atScrollPosition:animated to make sure, the selected cell is right on top of your screen.

Community
  • 1
  • 1
Steffen Blass
  • 276
  • 1
  • 4
1

You could do this by animating a view's frame property.

Use a UIView (or a subclass such as UITextView) initialised with a frame height appropriate for its initial content (i.e. the label, or nothing if you use a separate button or label to toggle the visibility).

Then determine the frame height required to display the expanded content you desire, and animate to this new height. The other views below (if any) must also have their position changed by the same amount.

As an example example:

[UIView beginAnimations:@"expand" context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[myView setFrame:CGRectMake(myView.frame.origin.x, myView.frame.origin.y, myView.frame.size.width, expandedHeight)]; // set expandedHeight to the height you require
for (UIView *view in myViews) { // myViews is an array containing each view that can be expanded or moved.
    if (view.frame.origin.y > myView.frame.origin.y) // check if view is below the one to be expanded
        [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y + expandedHeight, view.frame.size.width, view.frame.size.height)]; // animate the move
}
[UIView commitAnimations];

There are various other methods for animating the frame of UIView and its subclasses, check Apples UIView Reference.

danVnest
  • 959
  • 10
  • 11
  • Performance wise it is better to make a view hierarchy and dealing with one view instead of having an array of views. – tiguero Aug 22 '12 at 09:02
  • How would you implement this with a single view? The array `myViews` would be obtained with something like `[mySuperview subviews]`. – danVnest Aug 22 '12 at 09:29
  • You don't need to access your sub views: that's the whole point. If you move your "master" view all subview will follow without having to specify the new frame for each. – tiguero Aug 22 '12 at 09:34
  • Are you defining this "master" view as the superview containing all of the expandable views? If so, moving this view results in ALL of its subviews following (as you said), but not all of these views SHOULD move - only the ones below the expanded subview. – danVnest Aug 22 '12 at 10:07
  • I meant to say that all views he want to reveal below a button or label should be put under one view you will animate. – tiguero Aug 22 '12 at 10:33
  • I see! Well, that is the best way to do it, and is implied in my answer. Each view in myViews can contain any other subviews that should be contained within (and inherently animate with) that expandable view. – danVnest Aug 22 '12 at 12:41