UITableViewCell 什么时候做layout subview 的?

When did UITableViewCell done layout subview?

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

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

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

那么我应该在哪个功能中添加提示视图?

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

添加代码片段使其更清晰。我只想知道我应该把下面的代码放在哪里,这样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];

编辑

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


由于还不清楚确切你在做什么,我将提供一些方法...

如果您在 table 视图上调用 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];

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

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