简体   繁体   English

UITableView不连贯滚动没有图像或内容提取

[英]UITableView choppy scrolling WITHOUT images or content fetching

So I've got this UITableView which gets its data from memory (already preloaded, there are no requests going on while it is scrolling, everything is loaded before the view is being layouted). 所以我有这个UITableView从内存中获取数据(已经预加载,滚动时没有请求,所有内容都在视图布局之前加载)。 Each cell has its height dynamically calculated based on the amount of text in a UITextView and Autolayout. 每个单元格的高度根据UITextView和Autolayout中的文本量动态计算。 The cells are loaded from a Nib and reusing cells is working properly (at least I hope so). 从Nib加载细胞并重复使用细胞正常工作(至少我希望如此)。 I use UITableViewAutomaticDimension when calculating row height, so I do not force cells to layout twice like you had to do that prior to iOS 8. 我在计算行高时使用UITableViewAutomaticDimension,因此我不强制单元格布局两次,就像在iOS 8之前必须这样做。

Here is the relevant methods where I populate the cells and calculate the heights: 以下是我填充单元格并计算高度的相关方法:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];

    if ([cellType isEqualToString:kLoadingCell])
        return kLoadingCellHeight;
    else if ([cellType isEqualToString:kOfflineCell])
        return kOfflineCellHeight;
    else if ([cellType isEqualToString:kFootprintListHeaderCell])
        return kHeaderCellHeight;
    else if ([cellType isEqualToString:kFootprintCellUnsynced])
        return kUnsyncedCellHeight;
    else if ([cellType isEqualToString:kShowFullTripCell])
        return kShowFullTripCellHeight;
    else if ([cellType isEqualToString:kFootprintOnMapCell])
        return kFootprintOnMapCellHeight;
    else
    {
        return UITableViewAutomaticDimension;
    }
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];

    if ([cellType isEqualToString:kLoadingCell])
        return kLoadingCellHeight;
    else if ([cellType isEqualToString:kOfflineCell])
        return kOfflineCellHeight;
    else if ([cellType isEqualToString:kFootprintListHeaderCell])
        return kHeaderCellHeight;
    else if ([cellType isEqualToString:kFootprintCellUnsynced])
        return kUnsyncedCellHeight;
    else if ([cellType isEqualToString:kShowFullTripCell])
        return kShowFullTripCellHeight;
    else if ([cellType isEqualToString:kFootprintOnMapCell])
        return kFootprintOnMapCellHeight;
    else
    {
        return UITableViewAutomaticDimension;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];

    if ([cellType isEqualToString:kLoadingCell])
    {
        UITableViewCell *loadingCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
        loadingCell.tag = kLoadingCellTag;
        loadingCell.selectionStyle = UITableViewCellSelectionStyleNone;
        loadingCell.backgroundColor = loadingCell.contentView.backgroundColor = [UIColor clearColor];

        UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
        activityIndicatorView.center = CGPointMake(tableView.frame.size.width / 2, 20);
        [loadingCell.contentView addSubview:activityIndicatorView];

        [activityIndicatorView startAnimating];

        return loadingCell;
    }
    else if ([cellType isEqualToString:kOfflineCell])
    {
        FPOfflineCell *offlineCell = [tableView dequeueReusableCellWithIdentifier:kOfflineCell];
        return offlineCell;
    }
    else if ([cellType isEqualToString:kFootprintListHeaderCell])
    {
        FPFootprintListHeaderCell *headerCell = [tableView dequeueReusableCellWithIdentifier:kFootprintListHeaderCell];
        [headerCell.syncButton addTarget:self action:@selector(syncButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        return headerCell;
    }
    else if ([cellType isEqualToString:kFootprintCellUnsynced])
    {
        FPFootprintCellUnsynced *unsyncedCell = [tableView dequeueReusableCellWithIdentifier:kFootprintCellUnsynced];
        unsyncedCell.footprint = self.map.footprintsNonSynced[[self unsyncedFootprintIndexForIndexPath:indexPath]];
        return unsyncedCell;
    }
    else if ([cellType isEqualToString:kShowFullTripCell])
    {
        FPShowFullTripCell *showFullTripCell = [tableView dequeueReusableCellWithIdentifier:kShowFullTripCell];
        return showFullTripCell;
    }
    else if ([cellType isEqualToString:kFootprintOnMapCell])
    {
        FPFootprintOnMapCell *footprintOnMapCell = [tableView dequeueReusableCellWithIdentifier:kFootprintOnMapCell];
        footprintOnMapCell.footprint = self.map.footprints[0];
        return footprintOnMapCell;
    }
    else
    {
        FPFootprint *footprint = self.map.footprints[[self footprintIndexForIndexPath:indexPath]];
        FootprintCell *cell = [tableView dequeueReusableCellWithIdentifier:kFootprintCell];
        cell.titleLabel.text = footprint.name;
        cell.dateLabel.text = footprint.displayDate;
        cell.textView.text = nil;
        if (footprint.text && footprint.text.length > 0) {
            if ([self.readmoreCache[@(footprint.hash)] boolValue]) {
                cell.textView.text = footprint.text;
            } else {
                cell.textView.text = [footprint.text stringByAppendingReadMoreAndLimitingToCharacterCount:300 screenWidth:tableView.frame.size.width];
            }
        } else {
            cell.hasText = NO;
        }
        cell.textView.markdownLinkTextViewDelegate = self;
        [cell.textView setNeedsDisplay];
        cell.isPrivate = footprint.isPrivate;
        [cell.likesAndCommentsView setLikesCount:footprint.likes andCommentsCount:footprint.comments];
        [cell.likesAndCommentsView setLiked:footprint.liked];
        [cell.likesAndCommentsView.likeButton addTarget:self action:@selector(likeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [cell.likesAndCommentsView.likesTextButton addTarget:self action:@selector(likesTextButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [cell.likesAndCommentsView.commentButton addTarget:self action:@selector(commentButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [cell.likesAndCommentsView.commentsTextButton addTarget:self action:@selector(commentsTextButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [cell.detailButton addTarget:self action:@selector(detailButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [cell.translateButton addTarget:self action:@selector(translateButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
        if (footprint.canBeTranslated) {
            cell.translationStatus = footprint.translationState;
            if (footprint.translationState == FPFootprintTranslationStateTranslated) {
                cell.translatedTextView.text = footprint.translatedText;
            }
        } else {
            cell.translationStatus = FPFootprintTranslationStateNotAvailible;
        }
        cell.numberOfImages = 2;

        return cell;
    }
}

And this is my cell: 这是我的手机:

import UIKit

@objc class FootprintCell: UITableViewCell {

    var translationStatus: FPFootprintTranslationState = .NotTranslated {
        didSet {
            translateButton.hidden = true
            translateLoader.stopAnimating()
            translatedTextView.hidden = true
            translatedTextView.text = nil

            translatedTextView.addConstraint(translatedTextViewHeightConstraint)
            translationButtonHeightConstraint.constant = 0
            loaderHeightConstraint.constant = 0

            switch translationStatus {
            case .NotAvailible:
                break
            case .NotTranslated:
                translateButton.hidden = false
                translationButtonHeightConstraint.constant = translationButtonHeightConstraintConstant
            case .Translating:
                translateLoader.startAnimating()
                loaderHeightConstraint.constant = loaderHeightConstraintConstant
                translatedTextView.text = nil
            case .Translated:
                translatedTextView.hidden = false
                translatedTextView.removeConstraint(translatedTextViewHeightConstraint)
            }
        }
    }

    var isPrivate: Bool = false {
        didSet {
            privacyBar.hidden = !isPrivate
            privacyIcon.image = UIImage(named: isPrivate ? "ic_lock" : "ic_globe")
        }
    }

    var hasText: Bool = true {
        didSet {
            if hasText {
                textView.removeConstraint(textViewHeightConstraint)
            } else {
                textView.addConstraint(textViewHeightConstraint)
            }
        }
    }

    var numberOfImages: Int = 0 {
        didSet {
            if numberOfImages == 0 {
                imagesContainer.subviews.map { $0.removeFromSuperview() }
            } else if numberOfImages == 2 {
                twoImagesContainer = NSBundle.mainBundle().loadNibNamed("FootprintCellTwoImagesContainer", owner: nil, options: nil)[0] as? FootprintCellTwoImagesContainer
                twoImagesContainer?.setTranslatesAutoresizingMaskIntoConstraints(false)
                imagesContainer.addSubview(twoImagesContainer!)
                let views = ["foo" : twoImagesContainer!] as [NSString : AnyObject]
                imagesContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[foo]|", options: .allZeros, metrics: nil, views: views))
                imagesContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[foo]|", options: .allZeros, metrics: nil, views: views))
            }
        }
    }

    @IBOutlet private(set) weak var titleLabel: UILabel!
    @IBOutlet private(set) weak var dateLabel: UILabel!
    @IBOutlet private(set) weak var textView: FPForwardingTextView!
    @IBOutlet private(set) weak var likesAndCommentsView: FPLikesAndCommentsView!
    @IBOutlet private weak var privacyBar: UIView!
    @IBOutlet private weak var privacyIcon: UIImageView!
    @IBOutlet private(set) weak var detailButton: UIButton!
    @IBOutlet private(set) weak var translateButton: UIButton!
    @IBOutlet private weak var translateLoader: UIActivityIndicatorView!
    @IBOutlet private(set) weak var translatedTextView: FPForwardingTextView!
    @IBOutlet private(set) weak var imagesContainer: UIView!

    private(set) var twoImagesContainer: FootprintCellTwoImagesContainer?

    @IBOutlet private weak var translationButtonHeightConstraint: NSLayoutConstraint!
    @IBOutlet private weak var loaderHeightConstraint: NSLayoutConstraint!
    @IBOutlet private var translatedTextViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet private var textViewHeightConstraint: NSLayoutConstraint!

    private var translationButtonHeightConstraintConstant: CGFloat!
    private var loaderHeightConstraintConstant: CGFloat!

    override func awakeFromNib() {
        super.awakeFromNib()

        textView.contentInset = UIEdgeInsets(top: -10, left: -5, bottom: 0, right: 0)
        textView.linkColor = UIColor(fromHexString: "0088CC")

        translatedTextView.contentInset = UIEdgeInsets(top: -10, left: -5, bottom: 0, right: 0)
        translatedTextView.linkColor = UIColor(fromHexString: "0088CC")

        privacyBar.backgroundColor = UIColor(patternImage: UIImage(named: "ic_privacy_bar"))

        translatedTextView.text = nil
        translatedTextView.hidden = true
        translateButton.hidden = true
        translationButtonHeightConstraintConstant = translationButtonHeightConstraint.constant
        loaderHeightConstraintConstant = loaderHeightConstraint.constant
        hasText = true
    }

    func layoutMargins() -> UIEdgeInsets {
        return UIEdgeInsetsZero
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        numberOfImages = 0
        translationStatus = .NotAvailible
        hasText = true
    }

}

FootprintCellTwoImagesContainer and FPLikesAndCommentsView are loaded from Nibs and currently do not contain any images or load anything, just some Autolayout. FootprintCellTwoImagesContainerFPLikesAndCommentsView是从Nib加载的,目前不包含任何图像或加载任何东西,只是一些Autolayout。

So the main problem is even when the whole tableView is loaded and every cell is displayed at least once (so there should be enough cells to reuse), after SLOWLY scrolling over a cell border up or down, I get a small jump (like 5 pixels up and down). 因此,主要的问题是即使整个tableView被加载并且每个单元格至少显示一次(因此应该有足够的单元格可以重复使用),在缓慢地向上或向下滚动单元格边框之后,我得到一个小跳跃(如5像素上下)。 This happens on every device, even on a 6 Plus. 这种情况发生在每台设备上,即使在6 Plus上也是如此。

Any ideas where the problem could be? 问题可能是什么想法? I hope it is not something with my constraints in the xibs, at least Interface Builder does not throw warnings there ... 我希望它不是我在xib中的约束,至少Interface Builder不会在那里发出警告......

I'm not so sure UITableViewAutomaticDimension is for table cells. 我不太确定UITableViewAutomaticDimension是用于表格单元格。 From the documentation... 从文档......

You return this value from UITableViewDelegate methods that request dimension metrics when you want UITableView to choose a default value. 当您希望UITableView选择默认值时,从UITableViewDelegate方法返回此值,该方法请求维度指标。 For example, if you return this constant in the tableView:heightForHeaderInSection: or tableView:heightForFooterInSection:, UITableView uses a height that fits the value returned from tableView:titleForHeaderInSection: or tableView:titleForFooterInSection: (if the title is not nil). 例如,如果在tableView:heightForHeaderInSection:或tableView:heightForFooterInSection:中返回此常量,则UITableView使用的高度适合从tableView返回的值:titleForHeaderInSection:或tableView:titleForFooterInSection :(如果标题不是nil)。

No mention of tableview cells. 没有提到tableview单元格。

So I did a search and found this... more discussion on UITableViewAutomaticDimension... 所以我做了一个搜索,发现了这个...... 关于UITableViewAutomaticDimension的更多讨论......

Where it says.. 在哪里说..

it will not work. 不起作用。 UITableViewAutomaticDimension is not intended to be used to set the row height. UITableViewAutomaticDimension不用于设置行高。 Use rowHeight and specify your value or implement: 使用rowHeight并指定您的值或实现:

So I think you may have that wrong. 所以我认为你可能有错。

OK before the code, the principle. OK之前的代码,原则。 I have custom cells with 4 labels in a column. 我有一个列中有4个标签的自定义单元格。 The top label (label1) always has text and the bottom label (label4) also always has text. 顶部标签(label1)始终具有文本,底部标签(label4)也始终具有文本。 Labels 2 and 3 may contain text, that's one may , or both may. 标签2和3可以包含文本,可以是一个,也可以是两个。 To achieve the resizing we use part auto layout and part delegate methods (not far from what you have) 为了实现调整大小,我们使用部分自动布局和部分委托方法(距离您的所有地方不远)

In Interface builder, we set the constraints for the prototype cell 在“接口”构建器中,我们设置原型单元的约束

Label1: Leading, Trailing, top, height, width 标签1:前导,尾随,顶部,高度,宽度

Label2: Leading, Trailing, top, bottom, height, width Label2:前导,尾随,顶部,底部,高度,宽度

Label3: Leading, Trailing, top, bottom, height, width Label3:前导,尾随,顶部,底部,高度,宽度

Label4: Leading, Trailing, top, bottom, height, width Label4:前导,尾随,顶部,底部,高度,宽度

For Labels 1 and 4 (top and bottom) we set the Content Compression Resistance Priority Vertical to 'required' (1000) Also for labels 2 and 3 we set the Content Compression Resistance Priority Vertical to 'Low' (250) 对于标签1和4(顶部和底部),我们将内容压缩电阻优先级垂直设置为“必需”(1000)对于标签2和3, 我们将内容压缩电阻优先级垂直设置为“低”(250)

This basically means if the height should decrease, collapse labels 2 and 3 first and above collapsing labels 1 and 4. (You may know all this already) You should have no warnings and you constraints all added correctly. 这基本上意味着如果高度应该减少,首先折叠标签2和3以及折叠标签1和4之上。(您可能已经知道所有这些)您应该没有警告并限制所有添加的正确。 (do not use constrain to margins unless you know what it does) (除非你知道它的作用,否则不要使用约束边距)

Now the code. 现在的代码。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Calls the sizing method to return a calculated height.
return [self heightForBasicCellAtIndexPath:indexPath];

} }

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
//taken from interface builder. If all 4 labels have strings and not collapsed, this is the height the cell will be.
return 123.0f;

} }

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Yours has lots of case logic - 
// Mine is similar but I configure the properties of the custom cell elsewhere mainly so it can be used for sizing.
[self configureCell:cell atIndexPath:indexPath];
return cell;

} }

- (void)configureCell:(MyJobCell *)cell atIndexPath:(NSIndexPath *)indexPath {

// my data source
MyCase *aCase = [_fetchedResultsController objectAtIndexPath:indexPath];

// setting the labels to match the case from data
cell.label1.text = aCase.name;
cell.label2.text = aCase.address;
cell.label3.text = aCase.postcode;
cell.label4.text = aCase.caseDescription;

} }

- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath {
// In here I create a cell and configure it with a cell identifier
static MyJobCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:MyJobCellIdentifier];
});

// This configures the sizing cell labels with text values
[self configureCell:sizingCell atIndexPath:indexPath];

// This line calls the calculation. It fires the Auto Layout constraints on the cell,
// If label 2 and / or label 3 are empty, they will be collapsed to 0 height.
return [self calculateHeightForConfiguredSizingCell:sizingCell];

} }

- (CGFloat)calculateHeightForConfiguredSizingCell:(MYJobCell *)sizingCell {

//Force the cell to update its constraints
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];

// Get the size of the 'squashed' cell and return it to caller
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;

} }

It's the best I can do at showing you a working method. 这是我向你展示工作方法的最好方法。 You will have to make adjustments to logically cater for all of the different types of custom cells you have. 您必须进行调整,以便逻辑地满足您拥有的所有不同类型的自定义单元格。 But other than that I think this should help. 但除此之外,我认为这应该有所帮助。 Let me know how you get on. 让我知道你是怎么办的。

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

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