web-dev-qa-db-ja.com

UITableViewCellをクリックして展開

カスタムUITableViewCellがあるとします

そのため、セルのカスタムボタンをクリックするたびに、それはある程度拡大する必要があり(さらに40の高さを言うことができます...)、同じカスタムボタンをもう一度クリックすると、前の高さに折りたたまれます。

開発者は私を案内してください..どうすればこのタスクを達成できますか

52
Tariq

HeightForRowAtIndexPathを実装して、適切な高さを計算します。次に、ボタンのコードで、テーブルに強制的にbeginUpdatesとendUpdatesを使用して各セルの高さを再評価させます。

[self.tableView beginUpdates];
[self.tableView endUpdates];

テーブルビューセルの高さの変更はheightForRowAtIndexPathを使用して自動的に計算され、変更もアニメーション化されます。

実際、これを行うセルのボタンの代わりに、didSelectRowAtIndexPathでセルの選択を行うだけでもかまいません。

67
kris

ここで、完全に正しいことを考慮して、受け入れられた答えと矛盾することは何も言いません。ただし、これをどのように達成するかについて詳しく説明します。これらすべてを読みたくなく、作業中のプロジェクトでソースコードを使用することに興味がある場合は、 GitHubへのサンプルプロジェクト をアップロードしました。

基本的な考え方は、メソッド-tableView: heightForRowAtIndexPath:内に、現在のセルを展開するかどうかを決定する条件を設定することです。これは、-tableView: didSelectRowAtIndexPath:内からテーブルの更新の開始/終了を呼び出すことでトリガーされます。この例では、一度に1つのセルを展開できるテーブルビューを作成する方法を示します。

最初に行う必要があるのは、 NSIndexPath オブジェクトへの参照を宣言することです。これは自由に行うことができますが、次のようなプロパティ宣言を使用することをお勧めします。

@property (strong, nonatomic) NSIndexPath *expandedIndexPath;

注:このインデックスパスをviewDidLoadまたは他の同様のメソッド内に作成する必要はありません。インデックスが最初はnilであるという事実は、テーブルが最初に展開された行を持たないことを意味します。テーブルを選択した行を展開して開始する場合は、viewDidLoadメソッドに次のようなものを追加できます。

NSInteger row = 1;
NSInteger section = 2;
self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section];

次のステップは、ユーザーの選択に基づいて展開されたセルインデックスを変更するロジックを追加するために ITableViewDelegate method -tableView: didSelectRowAtIndexPath:に進むことです。ここでの考え方は、選択されたばかりのインデックスパスを、expandedIndexPath変数内に格納されているインデックスパスと照合することです。 2つが一致する場合、ユーザーが展開されたセルの選択を解除しようとしていることがわかります。その場合、変数をnilに設定します。それ以外の場合は、expandedIndexPath変数を選択されたばかりのインデックスに設定します。これはすべて、beginUpdates/endUpdatesの呼び出しの間に行われ、Table Viewが遷移アニメーションを自動的に処理できるようにします。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView beginUpdates]; // tell the table you're about to start making changes

    // If the index path of the currently expanded cell is the same as the index that
    // has just been tapped set the expanded index to nil so that there aren't any
    // expanded cells, otherwise, set the expanded index to the index that has just
    // been selected.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        self.expandedIndexPath = nil;
    } else {
        self.expandedIndexPath = indexPath;
    }

    [tableView endUpdates]; // tell the table you're done making your changes
}

最後のステップは別のステップです ITableViewDelegate method -tableView: heightForRowAtIndexPath:。このメソッドは、テーブルで更新が必要であると判断されたインデックスパスごとにbeginUpdatesを1回トリガーした後に呼び出されます。ここで、expandedIndexPathを現在再評価中のインデックスパスと比較します。

2つのインデックスパスが同じである場合、これは展開するセルです。それ以外の場合、高さは通常のはずです。 100と44の値を使用しましたが、ニーズに合ったものを使用できます。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Compares the index path for the current cell to the index path stored in the expanded
    // index path variable. If the two match, return a height of 100 points, otherwise return
    // a height of 44 points.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        return 100.0; // Expanded height
    }
    return 44.0; // Normal height
}
94
Mick MacCallum

このためのオープンソースライブラリを作成しました。コードにデリゲートを展開して展開するだけで、voilà!描画やアニメーションも実行できます。 this を確認してください。

enter image description here

9
Hamid Vakilian

あなたが話していることを正確に行う再利用可能なコンポーネントを作成しました。使い方はとても簡単で、デモプロジェクトがあります。

GCRetractableSectionController GitHubで。

8
gcamp

[tableView beginUpdates][tableView endUpdates]を使用する代わりに、didSelectRowAtIndexPathメソッド内で[tableView reloadRowsAtIndexPath:... withRowAnimation:...]メソッドを使用しています。

これは、UITableViewCellを展開したとき、更新の開始および終了メソッドを使用したときに表示される要素に問題があったためです。もう1つのポイントは、上、下、左、右などのアニメーションから選択できることです。

8
Mike_NotGuilty

これはMickの答えですが、Swift 4.の場合(IndexPathはNSIndexPathを置き換えます。NSIndexPathは、nilがSwiftをクラッシュさせるため空のIndexPathに付属します。また、==を使用してIndexPathの2つのインスタンスを比較できます))

ExpandedIndexPathプロパティを宣言します。

var expandedIndexPath = IndexPath()

オプションのviewDidLoadパーツ。

expandedIndexPath = IndexPath(row: 1, section: 2)

次に、didSelectRowパーツ。

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.beginUpdates()

    if indexPath == expandedIndexPath {
        expandedIndexPath = IndexPath()
    } else {
        expandedIndexPath = indexPath
    }

    tableView.endUpdates()
}

次に、heightForRowパーツ。

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath == expandedIndexPath {
        return 100
    }

    return 44
}
3
David H

Gcampのソースコードを使用して、独自のバージョンを作成しました。

1)loadViewメソッドで、可変配列を初期化し、セクションの展開状態または非展開状態を保存します。展開されたステータスを別の配列に保存することが重要です。これは、テーブルビューのスクロール中に破棄されません(たとえば、headerViewに保存すると、再描画され、展開されたかどうかを忘れます)。私の場合は、_sectionStatuses配列です。

- (void)loadView
{
     // At the beginning all sections are expanded
    _sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections];
    for (int i = 0; i < self.tableView.numberOfSections; i++) {
        _sectionStates[i] = [NSNumber numberWithBool:YES];
    }
}

2)展開用のボタンを持つセクションのカスタムheaderViewを作成します。委任パターンを使用して、headerViewのボタンからTableViewControllerにアクションを委任します。 Gcampのソースコードで適切な画像を見つけることができます。

3)行を削除または追加するアクションを作成します。ここで、_foldersArrayはすべてのデータを含む私の構造です。私のセクションのheaderView-MCExpandableAccountHeaderViewは自身のセクション番号を知っています-各セクションのヘッダービューを作成するときにそこに転送します。現在どのセクションが展開または拡大されているかを知る必要があるため、このメソッドに転送することが重要です。

- (void)expandClicked:(MCAccountHeaderView *)sender
{
MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender;

// Finding a section, where a button was tapped
NSInteger section = expandableAccountHeaderView.section;

// Number of rows, that must be in a section when it is expanded
NSUInteger contentCount = [_foldersArray[section - 1][@"folders"] count];

// Change a saved status of a section
BOOL expanded = [_sectionStates[section] boolValue];
expanded = ! expanded;
expandableAccountHeaderView.expanded = expanded;
_sectionStates[section] = [NSNumber numberWithBool:expanded];

// Animation in a table
[self.tableView beginUpdates];

NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < contentCount; i++) {
    NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section];
    [modifiedIndexPaths addObject:indexPath];
}

if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];

[self.tableView endUpdates];

// Scroll to the top of current expanded section
if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

4)セクションが展開されているかどうかに応じて、セクション内の正しい数または行を返すことも重要です。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
     BOOL expanded = [_sectionStates[section] boolValue];

     return expanded ? [_foldersArray[section - 1][@"folders"] count] : 0;   
}
2
wzbozon

これに続いて 中記事 ボタンのタップに基づいてセルを展開し、特定のラベルにnumbersOfLineを設定する方法について、以下を使用してアニメーションを実行できました。

tableView.beginUpdates()
tableView.performBatchUpdates({
  cell.description.numberOfLines = !expanded ? 0 : 3
}, completion: nil)
tableView.endUpdates()

performBatchUpdatesはiOS 11でのみ利用可能ですNotice️

0

私にとっては、それが使用するように機能します:

  1. uITableViewDelegateで

    func tableView(_ tableView:UITableView、didSelectRowAt indexPath:IndexPath){

        print("Did select row: \(indexPath.row).")
    
        tableView.beginUpdates()
        tableView.endUpdates()
    }
    
  2. 選択可能/拡張可能なUITableViewCell

    func setSelected(_ selected:Bool、animated:Bool)をオーバーライドします{super.setSelected(selected、animated:animated)

       configStyle(selected)
    }
    
  3. 重要! tableView.rowHeight.automaticおよびUITableViewCellは、自動高さ計算を有効にするための制約です。つまり、高さ制約は、上/下の制約または追加された高さ制約のように明確に定義されるか、ラベル固有のコンテンツサイズが使用されます。

0
Michał Ziobro
initialize iSelectedIndex = -1; and declare
UITableView *urTableView;

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{

return 10;    //Section count

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

return 3; //row 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] autorelease];

}

[cell.textLabel setText:[NSString stringWithFormat:@"sec:%d,row:%d",indexPath.section,indexPath.row]];

return cell;

}


- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{

// adding a label with the tap gesture to the header in each section

headerLabel = [[UILabel alloc]init]; 

headerLabel.tag = section;

headerLabel.userInteractionEnabled = YES;

headerLabel.backgroundColor = [UIColor greenColor];

headerLabel.text = [NSString stringWithFormat:@"Header No.%d",section];

headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(gestureTapped:)];

[headerLabel addGestureRecognizer:tapGesture];

return headerLabel;

}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{

return 50.0; //adjust the height as you need

}

- (void)gestureTapped:(UITapGestureRecognizer *)sender{

UIView *theSuperview = self.view; // whatever view contains 

CGPoint touchPointInSuperview = [sender locationInView:theSuperview];

UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil];

if([touchedView isKindOfClass:[UILabel class]])
{

    if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand

        iSelectedIndex = touchedView.tag;

    }else{   // if the header is already expanded , need to collapse

        iSelectedIndex = -1;

    }

    [urTableView beginUpdates];

    [urTableView endUpdates];

}

}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

// Show or hide cell

float height = 0.0;

if (indexPath.section == iSelectedIndex) {

    height = 44.0; // Show the cell - adjust the height as you need

}

return height;

}
0
archana

x7fffffff's answerに追加するには、didSelectRowAtIndexPath内のifステートメントに追加条件が必要であることがわかりました。

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

   [tableView beginUpdates];

   if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
       self.expandedIndexPath = nil;
   } else {
       self.expandedIndexPath = indexPath;
   }

   [tableView endUpdates];

}
0
RossP