如何在iPhone SDK应用中实现手风琴视图?

13

有人看过iPhone上“手风琴”(也许被称为“动画轮廓线”)视图的实现吗?我找到了一个Cocoa的示例项目,但在尝试移植之前,我希望有人已经发明了这个轮子。

为了清楚起见,在UIView中,请考虑一堆包含标题和一些内容的部分。当用户触摸标题(或通过某些消息/事件),如果部分已经打开=>关闭它;如果部分关闭=>打开它并关闭任何其他打开的部分。 jQuery中的一个示例看起来像: http://docs.jquery.com/UI/Accordion

在我的情况下,我想能够在每个部分中放置任何UIView内容。

我只是想看看一些真正实现了这一点的应用程序-只是为了知道它是可能的!


请查看 https://github.com/nacho4d/Accordion 它实现了iPad上的手风琴式导航,您可以在那里看到一切是如何工作的! - myell0w
@Jacob 并不是所有的事情都是可能的!请参考https://dev59.com/t1TTa4cB1Zd3GeqPqTdx。;) - jakev
这个手风琴示例有真正的潜力。我试过了,发现它使用 UITableView 单元格提供了非常平滑的转换和手风琴效果。我建议在 - (void)fileAccordionDatasourceManager:(N4FileAccordionDatasourceManager *)manager didInsertRowsAtIndexPaths:(NSArray *)indexPaths { 方法中更改转换样式://[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft]; [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop]; - Newbyman
这是完美的答案。非常好用... - Jyotsna
6个回答

18

我建议使用UITableView,根据单元格是否“打开”来调整每个单元格的高度,然后进行操作。很容易调整行高,并且可以使组合单元格的总高度成为UITableView中可用的高度,这样它看起来更像手风琴而不仅仅是一个表格。

这是一个快速的技巧,应该能够运行,但在您的UITableViewController子类的.h文件中:

bool sectionopen[4]; ///or some other way of storing the sections expanded/closed state

在.m文件中,可以添加类似以下的内容:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 4;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  {
    if (sectionopen[indexPath.row]) {
        return 240;///it's open
    } else {
        return 45;///it's closed
    }

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *mycell = [[[UITableViewCell alloc] init] autorelease];
    mycell.textLabel.text= @"Section Name";
    return mycell;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    ///turn them all off
    sectionopen[0]=NO;
    sectionopen[1]=NO;
    sectionopen[2]=NO;
    sectionopen[3]=NO;

    ///open this one
    sectionopen[indexPath.row]=YES;

    ///animate the opening and expand the row
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}

这基本上将4行转换为可折叠的部分,选择一行会将其展开到240像素,并将所有其他行折叠到40。您可以更改所有这些数字,找出各个部分并对其进行任何其他想做的操作。

我已经尝试过这个,在我的代码中添加其他内容以向部分添加任何所需的内容(包括可能是滚动的UITextView)即可完成它。


16

我在stackoverflow上找到的所有解决方案都使用了UITableView,但这对我没有用,因为我没有显示表格数据。这就是为什么我创建了AccordionView控件。使用非常简单:

AccordionView *accordion = [[AccordionView alloc] initWithFrame:CGRectMake(0, 0, 320, 420)];
[self addSubview:accordion];

// Only height is taken into account, so other parameters are just dummy
UIButton *header1 = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 0, 30)];
[header1.titleLabel setText:@"First row"];

UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 200)];
// ... add subviews to view1

[accordion addHeader:header1 withView:view1];

// ... add more panels

[accordion setSelectedIndex:0];

在现实生活中,它看起来像这样:

enter image description here

黑色条是标题栏,你可以自定义它们(我正在使用Three20库)。


嗨Suda,感谢你提供的控件。在使用它时,我遇到了一个小问题。我已经在项目的Github上开了一个问题这里,你能看一下吗? - Isuru
嗨suda,非常感谢你的工作。这里我开了一个小问题,请看一下。https://github.com/appsome/AccordionView/issues/23 - Narasimha Nallamsetty

4

2

我刚刚偶然发现了这个问题,并且发现mjdth的解决方案非常直接和有用。不过,你可能需要使用

[self.tableView reloadRowsAtIndexPaths: paths withRowAnimation:UITableViewRowAnimationBottom];

相比于建议中的reloadSections方法,重新加载行可以给你更加流畅的过渡效果。


1
这是我目前正在使用的 CollapsingTableViewDelegate 类来实现此功能。这仅适用于静态表格内容。
您需要向此类提供 CollapsingTableCellDelegate 实现,该实现必须知道如何计算每行的折叠和展开大小,以及如何为每行创建一个 UIView。视图在折叠或展开时保持不变,因此每行视图的顶部薄片作为该行的可点击标题。
然后,您将此类设置为 UITableView 的数据源和代理。
头文件 CollapsingTableViewDelegate.h:
#import <UIKit/UIKit.h>

@protocol CollapsingTableCellDelegate<NSObject>

@required
- (CGFloat)collapsingCellHeightForRow:(int)row expanded:(BOOL)expanded;
- (UIView *)collapsingCellViewForRow:(int)row;

@optional
- (BOOL)collapsingCellAllowCollapse:(int)row;

@end

struct cell;

@interface CollapsingTableViewDelegate : NSObject <UITableViewDelegate, UITableViewDataSource> {
    id<CollapsingTableCellDelegate> cellDelegate;
    int numCells;
    int currentSelection;
    struct cell *cells;
}

@property (nonatomic, retain, readonly) id<CollapsingTableCellDelegate> cellDelegate;
@property (nonatomic, assign, readonly) int numCells;
@property (nonatomic, assign) int currentSelection;
@property (nonatomic, assign, readonly) struct cell *cells;

- (CollapsingTableViewDelegate *)initWithCellDelegate:(id<CollapsingTableCellDelegate>)delegate numCells:(int)numCells;
- (void)tableView:(UITableView *)tableView touchRow:(int)newSelection;

@end

以及源文件 CollapsingTableViewDelegate.m

#import "CollapsingTableViewDelegate.h"

@implementation CollapsingTableViewDelegate

struct cell {
    u_char expanded;
    u_char collapsable;
};

@synthesize cellDelegate;
@synthesize currentSelection;
@synthesize cells;
@synthesize numCells;

#pragma mark -
#pragma mark Setup and Teardown

- (CollapsingTableViewDelegate *)initWithCellDelegate:(id<CollapsingTableCellDelegate>)delegate numCells:(int)num {
    if ([super init] == nil)
        return nil;
    if ((cells = calloc(num, sizeof(*cells))) == NULL) {
        [self autorelease];
        return nil;
    }
    cellDelegate = [delegate retain];
    numCells = num;
    for (int row = 0; row < self.numCells; row++) {
        struct cell *const cell = &self.cells[row];

        cell->collapsable = ![self.cellDelegate respondsToSelector:@selector(collapsingCellAllowCollapse:)]
          || [self.cellDelegate collapsingCellAllowCollapse:row];
        cell->expanded = !cell->collapsable;
    }
    currentSelection = -1;
    return self;
}

- (void)dealloc {
    [cellDelegate release];
    free(cells);
    [super dealloc];
}

- (void)tableView:(UITableView *)tableView reloadRow:(int)row fade:(BOOL)fade {
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:0]]
                     withRowAnimation:fade ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
}

- (void)tableView:(UITableView *)tableView touchRow:(int)newSelection {

    // Sanity check
    if (newSelection < -1 || newSelection >= self.numCells) {
        NSLog(@"CollapsingTableViewDelegate: invalid row %d not in the range [-1..%d)", newSelection, self.numCells);
        return;
    }

    // Gather info
    int oldSelection = self.currentSelection;
    BOOL sameCellSelected = newSelection == oldSelection;
    struct cell *const oldCell = oldSelection != -1 ? &self.cells[oldSelection] : NULL;
    struct cell *const newCell = newSelection != -1 ? &self.cells[newSelection] : NULL;

    // Mark old cell as collapsed and new cell as expanded
    if (newCell != NULL)
        newCell->expanded = TRUE;
    if (oldCell != NULL)
        oldCell->expanded = FALSE;
    self.currentSelection = sameCellSelected ? -1 : newSelection;

    // Update table view
    if (oldSelection >= newSelection) {
        if (oldSelection != -1)
            [self tableView:tableView reloadRow:oldSelection fade:sameCellSelected];
        if (newSelection != -1 && !sameCellSelected)
            [self tableView:tableView reloadRow:newSelection fade:TRUE];
    } else {
        if (newSelection != -1 && !sameCellSelected)
            [self tableView:tableView reloadRow:newSelection fade:TRUE];
        if (oldSelection != -1)
            [self tableView:tableView reloadRow:oldSelection fade:sameCellSelected];
    }

    // If expanding a cell, scroll it into view
    if (newSelection != -1 && !sameCellSelected) {
        [tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:newSelection inSection:0]
                         atScrollPosition:UITableViewScrollPositionTop
                                 animated:TRUE];
    }
}

#pragma mark -
#pragma mark Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.numCells;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    struct cell *const cell = &self.cells[row];
    return [self.cellDelegate collapsingCellHeightForRow:row expanded:cell->expanded];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    UIView *cellView = [self.cellDelegate collapsingCellViewForRow:row];
    [cellView removeFromSuperview];
    UITableViewCell *tvcell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
    [tvcell.contentView addSubview:cellView];
    tvcell.clipsToBounds = TRUE;
    tvcell.selectionStyle = UITableViewCellSelectionStyleNone;
    return tvcell;
}

#pragma mark -
#pragma mark Table view delegate

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    struct cell *const cell = &self.cells[row];
    return cell->collapsable ? indexPath : nil;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newSelection {
    [tableView deselectRowAtIndexPath:newSelection animated:TRUE];
    [self tableView:tableView touchRow:[newSelection row]];
}

@end

并不完美,但对我来说基本上可以工作。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接