在 Objective-C 中使用 UIStackView 扩展 UITableViewCell

Extending UITableViewCell with UIStackView in Objective-C

提问人:DBSoft 提问时间:9/7/2022 更新时间:9/7/2022 访问量:115

问:

我有自己的跨平台小部件布局系统,我用它来做大多数事情,所以我以前从未使用过布局约束,所以这是我第一次深入研究它。我的代码是用 Objective-C 而不是 Swift 编写的,可悲的是,我发现的所有示例都是用 Swift 编写的。默认情况下,我的 UITableView 只会显示图标和文本。但是,它具有额外的“列”图标和文本,这些图标和文本将位于 MacOS 或其他桌面平台上的单独列中。我有一个切换开关,允许在水平或垂直 UIStackView 中显示这些附加信息。因此,如果切换,我想将 UIStackView 添加到放置在 textLabel 下的 UITableViewCell 中,将 UITableViewCell 扩展为 UIStackView 所需的任何大小。我的想法是,我可以通过添加 3 个将 UIStackView、contentView 和 textLabel 连接在一起的约束来做到这一点。这没有产生预期的结果。前两个约束正确地将 UIStackView 定位在 textLabel 下方,但未展开 UITableViewCell。添加第三个(底部)约束以展开单元格,则会将 UIStackView 完全剪裁出单元格。有人可以指出我做错了什么吗?

NSLayoutConstraint *constraint;

stack = [[[UIStackView alloc] init] retain];
[stack setTranslatesAutoresizingMaskIntoConstraints:NO];
[stack setSpacing:5.0];
[[self contentView] addSubview:stack];

/* Leading */
constraint = [NSLayoutConstraint constraintWithItem:stack attribute:NSLayoutAttributeLeft
                                          relatedBy:NSLayoutRelationEqual toItem:[self contentView]
                                          attribute:NSLayoutAttributeLeft multiplier: 1.0 constant:0.0];
[[self contentView] addConstraint:constraint];

/* Top */
constraint = [NSLayoutConstraint constraintWithItem:stack attribute:NSLayoutAttributeTop
                                          relatedBy:NSLayoutRelationEqual toItem:[self textLabel]
                                          attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0];
[[self contentView] addConstraint:constraint];

/* Bottom */
constraint = [NSLayoutConstraint constraintWithItem:[self contentView] attribute:NSLayoutAttributeBottom
                                          relatedBy:NSLayoutRelationEqual toItem:stack
                                          attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0];
[[self contentView] addConstraint:constraint];
iOS Objective-C UITableView

评论

0赞 The Dreams Wind 9/7/2022
您是否对单元格使用动态高度?即您是否设置了表格视图的(例如)和(例如)?UITableViewrowHeighttableView.rowHeight = UITableViewAutomaticDimensionestimatedRowHeighttableView.estimatedRowHeight = 40
0赞 The Dreams Wind 9/7/2022
顺便说一句,在分配对象时,您不需要它(无论是否启用了 ARC)retain
0赞 The Dreams Wind 9/7/2022
+如果没记错的话,你不需要为已经参与关系的视图添加约束,但约束本身需要被激活(例如通过属性)isActive
0赞 DBSoft 9/7/2022
我没有将rowHeight或estimatedRowHeight设置为任何内容,我假设UITableViewCell中的默认方法会返回。我也没有设置 isActive 属性,但它似乎遵守了前两个约束而没有激活......第三个约束也有效果,只是不是我所希望的效果。我会研究是否需要在某处将 rowHeight 设置为 UITableViewAutomaticDimension......我甚至没有意识到这个常数。
0赞 DBSoft 9/7/2022
显然我的调用addConstraint:正在激活它......来自 Apple isActive 属性文档:“激活或停用约束会在视图上调用 addConstraint: 和 removeConstraint:,该视图是此约束所管理的项的最接近的共同祖先。使用此属性,而不是直接调用 addConstraint: 或 removeConstraint:。

答:

0赞 DonMag 9/7/2022 #1

通过使用“现代”约束语法,您可以节省大量键入工作,并使代码更具可读性。

例如,如果我们创建了标签和堆栈视图,并将它们添加到单元格的内容视图中:

    // let's use the default margins
    UILayoutGuide *g = self.contentView.layoutMarginsGuide;

    [NSLayoutConstraint activateConstraints:@[

        // constrain label Top/Leading/Trailing to content margins guide
        [label.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [label.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [label.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        //  no Height, because we'll use the label's intrinsic size

        // constrain stack view Top to label bottom plus 8-points
        [stack.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:8.0],
        // Leading/Trailing/Bottom to content margins guide
        [stack.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [stack.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        [stack.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
        //  no Height, because we'll use the arranged subviews heights

    ]];

因此,我们可以像这样编写一个单元格类:

堆栈TableViewCell.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface StackTableViewCell : UITableViewCell
@end
NS_ASSUME_NONNULL_END

堆栈表视图Cell.m

#import "StackTableViewCell.h"

@interface StackTableViewCell()
{
    UIStackView *stack;
    UILabel *label;
}
@end

@implementation StackTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self commonInit];
    }
    return self;
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self commonInit];
    }
    return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {

    // create a label
    label = [UILabel new];
    [label setBackgroundColor:UIColor.greenColor];
    [label setNumberOfLines:0];

    // create a vertical stack view
    stack = [UIStackView new];
    [stack setAxis:UILayoutConstraintAxisVertical];
    [stack setSpacing:5.0];

    // add label and stack view to content view
    [label setTranslatesAutoresizingMaskIntoConstraints:NO];
    [stack setTranslatesAutoresizingMaskIntoConstraints:NO];
    [[self contentView] addSubview:label];
    [[self contentView] addSubview:stack];

    // let's use the default margins
    UILayoutGuide *g = self.contentView.layoutMarginsGuide;

    [NSLayoutConstraint activateConstraints:@[

        // constrain label Top/Leading/Trailing to content margins guide
        [label.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [label.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [label.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        //  no Height, because we'll use the label's intrinsic size

        // constrain stack view Top to label bottom plus 8-points
        [stack.topAnchor constraintEqualToAnchor:label.bottomAnchor constant:8.0],
        // Leading/Trailing/Bottom to content margins guide
        [stack.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
        [stack.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
        [stack.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
        //  no Height, because we'll use the arranged subviews heights

    ]];

    // let's give the stack view a border so we can see its frame
    stack.layer.borderColor = UIColor.redColor.CGColor;
    stack.layer.borderWidth = 1.0;

}

@end

以及一个基本的控制器......

堆栈TableViewController.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface StackTableViewController : UITableViewController
@end
NS_ASSUME_NONNULL_END

堆栈TableViewController.m

#import "StackTableViewController.h"
#import "StackTableViewCell.h"

@interface StackTableViewController ()

@end

@implementation StackTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.tableView registerClass:StackTableViewCell.class forCellReuseIdentifier:@"c"];
}

#pragma mark - Table view data source

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

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


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    StackTableViewCell *cell = (StackTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"c" forIndexPath:indexPath];

    // Configure the cell...
    return cell;
}

@end

此时,由于我们没有为单元格提供任何数据 - 并且标签和堆栈视图在空时没有固有高度 - 它将如下所示:

enter image description here

因此,让我们添加一个方法:fillData

堆栈TableViewCell.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface StackTableViewCell : UITableViewCell
// add this line
- (void)fillData:(NSInteger)n;
@end
NS_ASSUME_NONNULL_END

堆栈表视图Cell.m

// add this method
- (void)fillData:(NSInteger)n {

    label.text = [NSString stringWithFormat:@"Cell %ld", (long)n];
    
    // cells are reused, so first clear any existing labels in the stack view
    //  this is inefficient, but we're just demonstrating the cell sizing
    for (UIView *v in stack.arrangedSubviews) {
        [v removeFromSuperview];
    }
    
    // now add n number of labels
    for (int i = 0; i < n; i++) {
        UILabel *v = [UILabel new];
        v.text = [NSString stringWithFormat:@"Stack Label %ld", (long)i];
        v.backgroundColor = UIColor.yellowColor;
        [stack addArrangedSubview:v];
    }
    
}

并修改控制器以设置一些单元格数据:

堆栈TableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    StackTableViewCell *cell = (StackTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"c" forIndexPath:indexPath];

    // Configure the cell...
    [cell fillData:(indexPath.row % 4) + 1];
    
    return cell;
}

现在输出如下所示:

enter image description here

评论

0赞 DBSoft 9/7/2022
谢谢我很欣赏这个例子,这是我第一次看到 Objective-C 中约束的“现代语法”(我一直在寻找......到目前为止,只在 Swift 中看到过它)。是否可以不创建新的 UILabel 而是使用 UITableViewCell 的内置 textLabel?
0赞 DonMag 9/8/2022
@DBSoft - 你的意思是你想使用“内置的textLabel”添加其他子视图吗?如果是这样,不,你不想那样做。默认单元格已经设置了自己的约束来填充 - 您必须删除至少一个这些约束,这比添加自己的标签需要更多的工作(并且更容易引入新的布局问题)。.textLabelcontentView
0赞 DBSoft 9/8/2022
我看到这很令人失望......但这可以解释我的方法的一些麻烦。谢谢。
0赞 DonMag 9/8/2022
@DBSoft - 作为一般规则......您只想将默认值与其默认属性一起使用...尝试添加子视图 - 甚至修改“内置”子视图的属性 - 几乎总是会导致问题。UITableViewCell
0赞 DBSoft 9/9/2022
我基本上希望在大多数情况下只默认使用它,然后在必要时在底部添加单个堆栈视图以显示一些额外的信息......这似乎是最简单的。所以似乎我必须创建一个自定义单元格,重新创建大部分默认行为,以便我偶尔可以在底部添加堆栈视图?您是否有一种简单的方法来设计约束,以便我可以根据需要添加堆栈视图?或者我应该始终创建堆栈视图并仅在必要时将内容放入其中?你有什么建议?