简体   繁体   English

iOS表/集合视图中的单元间布局逻辑

[英]Inter-cell layout logic in iOS table/collection views

What is the idiomatic way to align portions of a separate cells in a UITableView or UICollectionView with a separate row for headers? UITableViewUICollectionView单独单元格的部分与标题的单独行对齐的惯用方法是什么?

For example, suppose I want to display something like this: 例如,假设我想显示如下内容:

Name       Number
Bob        34587
Jane       32489
Barbara    23766
Montgomery 34892

The "Name" and "Number" bits would be in a header cell of some sort. “Name”和“Number”位将位于某种标题单元格中。 I guess I could use a section header for this (with a single section). 我想我可以使用一个节头(有一个部分)。

Each cell below the header would need to size intelligently to its content, but that size would need to effect the layout of all other cells. 标题下方的每个单元格都需要智能地调整其内容大小,但该大小需要影响所有其他单元格的布局。

How is this usually achieved in iOS? 这通常是如何在iOS中实现的?

If I understand you correctly, then you'd like the second column to move as close as you can to the first column without braking the content in the first column. 如果我理解正确,那么您希望第二列尽可能靠近第一列移动而不会在第一列中制动内容。

As I know it this is not commonly used on iOS because it is much easier to use fixed width space for both columns and usually there is no need to compress them horizontally. 据我所知,这在iOS上并不常用,因为对两列使用固定宽度空间要容易得多,而且通常不需要水平压缩它们。

However if I'd really need to accomplish this then: 但是,如果我真的需要完成这个:

  1. I'd run in cycle through all of my cells content and determine the size of the left content by calling NSString method sizeWithFont:contrainedToSize: (if using before iOS7 or boundingRectWithSize:options:attributes:context: if on iOS7). 我将循环遍历所有单元格内容并通过调用NSString方法sizeWithFont:contrainedToSize:来确定左侧内容的大小sizeWithFont:contrainedToSize:如果在iOS7之前使用或者使用boundingRectWithSize:options:attributes:context: if on iOS7)。
  2. I'd store the largest value in instance variable (for example _maxLeftContentWidth ). 我将最大值存储在实例变量中(例如_maxLeftContentWidth )。
  3. In my -tableView:cellForRowAtIndexPath: I'd set the frames for my left cell content and right cell content according to my stored instance variable ( _maxLeftContentWidth ). 在我的-tableView:cellForRowAtIndexPath:我根据我存储的实例变量( _maxLeftContentWidth )设置左侧单元格内容和右侧单元格内容的帧。
  4. Whenever the content changed for my cells I'd run the left cell width calculation method again and then call [self.tableView reloadData] to redraw the cells. 每当我的单元格的内容发生变化时,我再次运行左单元格宽度计算方法,然后调用[self.tableView reloadData]重绘单元格。

Even though it is called a 'table view' it is not really a table how we usually think of tables ( ie , multiple rows with multiple columns). 即使它被称为“表视图”,它实际上并不是我们通常如何看待表的表( ,具有多列的多行)。 If you really want multiple columns it is up to you. 如果你真的想要多列,那就取决于你。 To line them up you have to do the work of figuring out the needed width, or choose a width that is big enough for all cases. 为了排列它们,你必须完成确定所需宽度的工作,或选择足够大的宽度以适应所有情况。

I think I would do something like this: 我想我会这样做:

#define COLUMN_SPACE 10
- (UITableViewCell) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: @"cell"];
    // Layout the cell
    NSString *name = [myDataStore nameForIndexPath: indexPath];
    NSNumber *number = [myDataStore numberforIndexPath: indexPath];

    CGRect frame = cell.nameLabel.frame;
    frame.size.width = [self nameColumnWidth];
    cell.nameLabel.frame = frame;
    frame = cell.numberLabel.frame;
    frame.origin.x = cell.nameLabel.origin.x + cell.nameLabel.size.width + COLUMN_SPACE;
    frame.size.width = [self numberColumnWidth];
    cell.numberLabel.frame = frame;

    cell.nameLabel.text = name;
    // You would probably want to use an NSNumberFormatter here
    cell.numberLabel.text = [NSString stringWithFormat: @"%d", number.intValue];

    return cell;
}

- (CGFloat) nameColumnWidth {
    if( _nameColumnWidth <= 0.0 ) {
        _nameColumnWidth = 0.0;
        for( NSInteger s = 0; s < [myDataStore numberOfSections]; ++s ) {
            for( NSInteger r = 0; r < [myDataStore numberOfRowsInSection: s]; ++r ) {
                NSIndexPath *path = [NSIndexPath indexPathForRow: r inSection: s];
                NSString *name = [myDataStore nameForIndexPath: indexPath];
                CGFloat widthForName = [name widthNeeded];
                if( widthForName > _nameColumnWidth )
                    _nameColumnWidth = widthForName;
            }
        }
    }
    return _nameColumnWidth;
}

- (CGFloat) numberColumnWidth {
    if( _numberColumnWidth <= 0.0 ) {
        _numberColumnWidth = 0.0;
        for( NSInteger s = 0; s < [myDataStore numberOfSections]; ++s ) {
            for( NSInteger r = 0; r < [myDataStore numberOfRowsInSection: s]; ++r ) {
                NSIndexPath *path = [NSIndexPath indexPathForRow: r inSection: s];
                NSNumber *number = [myDataStore numberforIndexPath: indexPath];
                // You would probably want to use an NSNumberFormatter here
                // (in fact you want to do the same is you do in cellForRowAtIndexPath)
                NSString *numberAsString = [NSString stringWithFormat: @"%d", number.intValue];
                CGFloat widthForNumber = [numberAsString widthNeeded];
                if( widthForNumber > _numberColumnWidth )
                    _numberColumnWidth = widthForNumber;
            }
        }
    }
    return _numberColumnWidth;
}

I would use the same [self nameColumnWidth] and [self numberColumnWidth] when generating the view for the section header, or I would just make the cell at row 0 of the section have the Name and Number headers. 我会用相同的[self nameColumnWidth][self numberColumnWidth]生成用于节头中的视图时,或者我只想使细胞在所述部分的第0行具有的姓名号码标头。

For the width I would extend NSString with this: 对于宽度,我会用这个扩展NSString

@implementation NSString (WithWidthNeeded)
- (CGFloat) widthNeeded {
    // Either set the font you will use here, add it as a parameter, etc.
    UIFont *font = [UIFont systemFontOfSize: 18];
    CGSize maximumLabelSize = CGSizeMake( 9999, font.lineHeight );
    CGSize expectedLabelSize;
    if( [self respondsToSelector: @selector(boundingRectWithSize:options:attributes:context:)] ) {
        CGRect expectedLabelRect = [self boundingRectWithSize: maximumLabelSize options: NSStringDrawingUsesLineFragmentOrigin attributes: @{ NSFontAttributeName: font } context: nil];
            expectedLabelRect = CGRectIntegral( expectedLabelRect );
            expectedLabelSize = expectedLabelRect.size;
    } else {
        NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString: self attributes: @{ NSFontAttributeName: font }];
        if( [attributedText respondsToSelector: @selector(boundingRectWithSize:options:context:)] ) {
            CGRect expectedLabelRect = [attributedText boundingRectWithSize: maximumLabelSize options: NSStringDrawingUsesLineFragmentOrigin context: nil];
            expectedLabelRect = CGRectIntegral( expectedLabelRect );
            expectedLabelSize = expectedLabelRect.size;
    } else {
            expectedLabelSize = [self sizeWithFont: font
                                 constrainedToSize: maximumLabelSize
                                     lineBreakMode: NSLineBreakByTruncatingTail];

        }
    }
    return expectedLabelSize.width;
}
@end

When the data are changed the _nameColumnWidth and _numberColumnWidth can be changed to 0.0 and they will be recalculated so the column widths would be adjusted. 更改数据后, _nameColumnWidth_numberColumnWidth可以更改为0.0,并将重新计算它们,以便调整列宽。

An alternative is to keep track of the maximum widths of the name and number when the cells are layed out in cellForRowAtIndexPath . 另一种方法是在cellForRowAtIndexPathcellForRowAtIndexPath单元格时跟踪名称数字的最大宽度。 If they increase, trigger the visible cells to be redrawn. 如果它们增加,则触发重绘的可见单元格。 This would have better performance, since it would not have to traverse the entire data set to find the longest name and biggest number . 这将具有更好的性能,因为它不必遍历整个数据集以找到最长的名称和最大的数字 This would, however, result in the columns shifting as you scrolled the view down through the data and longer names or bigger numbers are encountered, unless the longest name and biggest number happened to be in the top row. 但是,当您向下滚动视图时,这会导致列移动,并且会遇到更长的名称或更大的数字 ,除非最长的名称和最大的数字恰好位于顶行。

It would also be possible to do a kind of hybrid where the nameColumnWidth and numberColumnWidth are set to the maximum visible (sort of a local maximum) but then a background thread is started to go through the rest of the data and find the absolute maximums. 这也将是可以做到一种在混合动力车的nameColumnWidthnumberColumnWidth被设置为最大可见(排序当地最大的),但是一个后台线程开始要经过数据的其余部分,并找到绝对最大值。 When the absolute maximums are found the visible cells are reloaded. 当找到绝对最大值时,重新加载可见单元格。 Then there is only one shift or change in the column width. 然后,列宽只有一个移位或变化。 (The background thread might not be safe if sizeWithFont: is being called since it is in UIKit . With iOS 6 or 7 I think it would be OK) (如果调用sizeWithFont:因为它在UIKit中 ,后台线程可能不安全。对于iOS 6或7,我认为它会没问题)

Again, since a UITableView is really a UIColumnView it leaves quite a bit of work for you to make multiple columns that can adjust in width for their content. 同样,由于UITableView实际上是一个UIColumnView因此为您制作多个可以调整其内容宽度的列留下了相当多的工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM