【问题标题】:When did UITableViewCell done layout subview?UITableViewCell 什么时候完成布局子视图?
【发布时间】:2020-09-09 13:20:21
【问题描述】:

如下图所示,我想在 UITableViewCell 中添加一个与 UIStackView 左对齐的提示(最大 UIView 的子视图)。 UITableViewCell 中 UIStackView 的宽度是动态的,所以我需要将提示视图放在 UITableViewCell 布局其子视图之后。

我尝试在 willDisplayCell 函数中添加提示视图,但我到达的位置不是最终位置。

另外,我尝试在 UITableViewCell 的 layoutSubviews 函数中设置提示视图的位置。由于这个函数被多次调用,所以提示最终会到达预期的位置,但在开始时会闪烁。

那么我应该在哪个函数中添加提示视图呢?

--------已编辑--------

添加一个代码 sn-p 使其更清晰。我只想知道下面的代码应该放在哪里,这样rect.origin.x就是stackview的最终位置。

UIView *view = self.view;
CGRect rect = [cell.stackView convertRect:cell.stackView.bounds toView:view];
[self.tipView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:rect.origin.x].active = YES;
[self.tipView.trailingAnchor constraintLessThanOrEqualToAnchor:view.trailingAnchor constant:-marginConstant].active = YES;
[self.tipView.bottomAnchor constraintEqualToAnchor:self.tableView.bottomAnchor constant:-TSkBubbleClickAreaHeight-marginConstant].active = YES;
[view layoutIfNeeded];

【问题讨论】:

  • 您是否将tipView 添加为单元格的子视图,但位于单元格边界之外?或者它是表格视图的子视图,相对于特定单元格的内容定位?如果您显示您当前正在处理的代码,这将有所帮助 - 请参阅:minimal reproducible example
  • @DonMag 如上所述,tipView 被添加为外部最大 UIView 的子视图。
  • 好的 - 虽然还是有点不清楚......你是否为每一行显示“提示视图”?只有一排?应该与单元格一起滚动?
  • @DonMag 我只显示一行的tipView,最大的UIView 中只有一个tipView。当单元格滚动时,tipView 将被关闭。我在上面添加了一些代码,希望这可以使我的问题更清楚。

标签: ios objective-c swift uitableview uistackview


【解决方案1】:

编辑

要尝试直接回答您的问题,您知道当您对单元格执行的任何操作都已完成时,单元格内容的布局何时完成。因此,要准确了解单元格的堆栈视图何时布局,您可以将堆栈视图子类化并在该子类中实现 layoutSubviews


由于仍不清楚究竟你在做什么,我将提供几种方法......

如果你在表格视图上调用reloadData,你可以试试这个:

[tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
    [self showTipView:pth];
});

如果你打电话给insertRowsAtIndexPaths,你可以试试这个:

[CATransaction begin];

[CATransaction setCompletionBlock:^{
    [self showTipView:pth];
}];

[self->tableView insertRowsAtIndexPaths:@[pth] withRowAnimation:UITableViewRowAnimationRight];

[CATransaction commit];

这是一个例子(我尽量保持简单,并提供清晰的 cmets):

ExampleTableViewCell.h

//
//  ExampleTableViewCell.h
//  Created by Don Mag on 9/15/20.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface ExampleTableViewCell : UITableViewCell
- (void) configureCell:(NSString *)s;
- (UIStackView *)getStackRef;
@end
NS_ASSUME_NONNULL_END

ExampleTableViewCell.m

//
//  ExampleTableViewCell.m
//  Created by Don Mag on 9/15/20.
//

#import "ExampleTableViewCell.h"

@interface ExampleTableViewCell ()
{
    UIStackView *stackView;
    UILabel *label;
}
@end

@implementation ExampleTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void) commonInit {
    label = [UILabel new];
    label.backgroundColor = [UIColor yellowColor];

    stackView = [UIStackView new];
    stackView.translatesAutoresizingMaskIntoConstraints = NO;
    
    [stackView addArrangedSubview:label];
    
    [self.contentView addSubview:stackView];
    
    UILayoutGuide *g = self.contentView.layoutMarginsGuide;
    
    [NSLayoutConstraint activateConstraints:@[
        
        // constrain stack view Top: 0 / Trailing: 0 / Bottom: 0
        [stackView.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [stackView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        [stackView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],

    ]];

}
- (void) configureCell:(NSString *)s {
    label.text = s;
}

- (UIStackView *)getStackRef {
    return stackView;
}

@end

ExampleViewController.h

//
//  ExampleViewController.h
//  Created by Don Mag on 9/15/20.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface ExampleViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

ExampleViewController.m

//
//  ExampleViewController.m
//  Created by Don Mag on 9/15/20.
//

#import "ExampleViewController.h"
#import "ExampleTableViewCell.h"

@interface ExampleViewController () <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>
{
    NSMutableArray *myData;
    NSMutableArray *sampleStrings;
    
    UIView *tipView;
    
    UIButton *reloadButton;
    UIButton *insertButton;
    UITableView *tableView;
    
    NSInteger idx;
    NSInteger insertRow;
}
@end

@implementation ExampleViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // row number to insert and show the tipView
    insertRow = 7;
    
    // init some sample data
    myData = [NSMutableArray new];
    for (int i = 0; i < 30; i++) {
        NSString *s = [NSString stringWithFormat:@"Row %i", i];
        [myData addObject:s];
    }
    
    // a few example strings
    sampleStrings = [NSMutableArray new];
    [sampleStrings addObject:@"Short text example."];
    [sampleStrings addObject:@"A little more text example."];
    [sampleStrings addObject:@"Considerably longer text for this example."];
    
    // index for sampleStrings array
    idx = -1;

    // create a "tip view"
    //  red background view with
    //  green background label
    //  label is inset 4-pts on each side
    tipView = [UIView new];
    tipView.backgroundColor = [UIColor redColor];
    UILabel *tipLabel = [UILabel new];
    tipLabel.backgroundColor = [UIColor greenColor];
    tipLabel.text = @"This is the tip!";
    
    tipView.translatesAutoresizingMaskIntoConstraints = NO;
    tipLabel.translatesAutoresizingMaskIntoConstraints = NO;
    
    [tipView addSubview:tipLabel];
    [NSLayoutConstraint activateConstraints:@[
        [tipLabel.topAnchor constraintEqualToAnchor:tipView.topAnchor constant:4.0],
        [tipLabel.leadingAnchor constraintEqualToAnchor:tipView.leadingAnchor constant:4.0],
        [tipLabel.bottomAnchor constraintEqualToAnchor:tipView.bottomAnchor constant:-4.0],
        [tipLabel.trailingAnchor constraintEqualToAnchor:tipView.trailingAnchor constant:-4.0],
    ]];
    
    // init buttons
    reloadButton = [UIButton new];
    [reloadButton setTitle:@"Reload" forState:UIControlStateNormal];
    [reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [reloadButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
    [reloadButton setBackgroundColor:[UIColor blueColor]];

    insertButton = [UIButton new];
    [insertButton setTitle:@"Insert" forState:UIControlStateNormal];
    [insertButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [insertButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
    [insertButton setBackgroundColor:[UIColor blueColor]];
    
    // init table view
    tableView = [UITableView new];
    
    for (UIView *v in @[reloadButton, insertButton, tableView]) {
        v.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:v];
    }
    
    UILayoutGuide *g = self.view.safeAreaLayoutGuide;
    
    [NSLayoutConstraint activateConstraints:@[
        
        // buttons at top
        [reloadButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
        [reloadButton.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:24.0],
        
        [insertButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
        [insertButton.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-24.0],
        
        [insertButton.leadingAnchor constraintEqualToAnchor:reloadButton.trailingAnchor constant:20.0],
        [insertButton.widthAnchor constraintEqualToAnchor:reloadButton.widthAnchor],
        
        // constrain tableView Top: 20-pts from buttons / Leading: 0 / Trailing: 0 / Bottom: 0
        [tableView.topAnchor constraintEqualToAnchor:reloadButton.bottomAnchor constant:20.0],
        [tableView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [tableView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        [tableView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
        
    ]];
    
    [tableView registerClass:[ExampleTableViewCell class] forCellReuseIdentifier:@"exCell"];

    tableView.delegate = self;
    tableView.dataSource = self;
    
    [reloadButton addTarget:self action:@selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
    [insertButton addTarget:self action:@selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [tipView removeFromSuperview];
}
- (void)btnTap:(UIButton *)btn {
    
    // remove tipView if it's showing
    [tipView removeFromSuperview];

    NSString *s = sampleStrings[++idx % 3];

    if (btn == reloadButton) {
        [self reloadMethod:s];
    } else {
        [self insertMethod:s];
    }
}
- (void)insertMethod:(NSString *)s {
    
    // IndexPath for cell you want
    __block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];
    
    [myData insertObject:s atIndex:pth.row];
    
    [CATransaction begin];
    
    [CATransaction setCompletionBlock:^{
        [self showTipView:pth];
    }];

    [self->tableView insertRowsAtIndexPaths:@[pth] withRowAnimation:UITableViewRowAnimationRight];
    
    [CATransaction commit];
}
- (void)reloadMethod:(NSString *)s {

    // IndexPath for cell you want
    __block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];

    [myData insertObject:s atIndex:pth.row];

    [tableView reloadData];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self showTipView:pth];
    });
    
}
- (void)showTipView:(NSIndexPath *)pth {
    
    // get a reference to the cell
    ExampleTableViewCell *cell = [tableView cellForRowAtIndexPath:pth];
    
    // if row is not visible
    if (!cell) {
        return;
    }
    
    // get a reference to the cell's stack view
    UIStackView *stack = [cell getStackRef];
    
    // add tipView to self.view
    [self.view addSubview:tipView];
    
    // constrain tipView
    //  Leading to stack view's Leading
    //  Bottom to cell's Top - 4
    [NSLayoutConstraint activateConstraints:@[
        [tipView.leadingAnchor constraintEqualToAnchor:stack.leadingAnchor constant:0.0],
        [tipView.bottomAnchor constraintEqualToAnchor:cell.topAnchor constant:-4.0],
    ]];
    
    // if we want to "fade-in" the tipView
    [tipView setAlpha:0.0];
    [UIView animateWithDuration:0.3
                     animations:^{
        [self->tipView setAlpha:1.0];
    }];
    
}

- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    ExampleTableViewCell *c = (ExampleTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"exCell" forIndexPath:indexPath];
    [c configureCell:myData[indexPath.row]];
    return c;
}

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

@end

【讨论】:

  • 不,表格视图不是静态的,单元格是在viewDidAppear之后插入的,所以我们不能放在那里。
  • @ArgenBarbie - 好的......如果你编辑你的问题并清楚地解释你想要做什么 - 最好,如果你可以包含示例代码来重现问题 - 我会尝试和帮助(否则,正如我们在这里看到的,我试图回答一个不同的问题)。
  • 我一开始说过cell中的stack view是动态的,我要找的是UITableViewCell做子视图布局的地方。
  • @ArgenBarbie - 好吧,一般来说,当有人说“动态”时,他们的意思是当它显示时它将是可变大小(例如,基于标签中的文本)......这是我在示例代码中所做的。您是说,基于用户交互,您正在更改红色堆栈视图的内容,而它已经显示?还是您要插入行并重新加载表格?或者在performBatchUpdates 块中插入行?
  • 是的,你可以认为我在插入行并重新加载表格。
猜你喜欢
  • 2016-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-01
  • 2017-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多