jeudi 2 octobre 2014

UITableView choppy scrolling WITHOUT images or content fetching


Vote count:

0




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). Each cell has its height dynamically calculated based on the amount of text in a UITextView and Autolayout. The cells are loaded from a Nib and reusing cells is working properly (at least I hope so). 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.


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.


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). This happens on every device, even on a 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 ...



asked 1 min ago







UITableView choppy scrolling WITHOUT images or content fetching

Aucun commentaire:

Enregistrer un commentaire