- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
背景
我搜索了 SO 和苹果论坛。很多人都在谈论 collection view cell with image 的性能。他们中的大多数人说,自从在主线程中加载图像以来,滚动就滞后了。
通过使用 SDWebImage
,图像应该在单独的线程中加载。但是,它仅在 iPad 模拟器的横向模式下存在延迟。
问题描述
在纵向模式下, Collection View 为每行加载 3 个单元格。而且它没有滞后或微不足道的延迟。在横向模式下, Collection View 为每行加载 4 个单元格。并且帧率有明显的滞后和下降。
我已经用核心动画检查了仪器工具。当出现新的单元格时,帧率下降到大约 8fps。我不确定是哪个行为导致 Collection View 的性能如此低下。
希望有人知道其中的技巧。
相关代码如下
在 View Controller 中
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ProductCollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"ProductViewCell" forIndexPath:indexPath];
Product *tmpProduct = (Product*)_ploader.loadedProduct[indexPath.row];
cell.product = tmpProduct;
if (cellShouldAnimate) {
cell.alpha = 0.0;
[UIView animateWithDuration:0.2
delay:0
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations:^{
cell.alpha = 1.0;
} completion:nil];
}
if(indexPath.row >= _ploader.loadedProduct.count - ceil((LIMIT_COUNT * 0.3)))
{
[_ploader loadProductsWithCompleteBlock:^(NSError *error){
if (nil == error) {
cellShouldAnimate = NO;
[_collectionView reloadData];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
cellShouldAnimate = YES;
});
} else if (error.code != 1){
#ifdef DEBUG_MODE
ULog(@"Error.des : %@", error.description);
#else
CustomAlertView *alertView = [[CustomAlertView alloc]
initWithTitle:@"Connection Error"
message:@"Please retry."
buttonTitles:@[@"OK"]];
[alertView show];
#endif
}
}];
}
return cell;
}
在collectionViewCell中PrepareForReuse
- (void)prepareForReuse
{
[super prepareForReuse];
CGRect bounds = self.bounds;
[_thumbnailImgView sd_cancelCurrentImageLoad];
CGFloat labelsTotalHeight = bounds.size.height - _thumbnailImgView.frame.size.height;
CGFloat brandToImageOffset = 2.0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
brandToImageOffset = 53.0;
}
CGFloat labelStartY = _thumbnailImgView.frame.size.height + _thumbnailImgView.frame.origin.y + brandToImageOffset;
CGFloat nameLblHeight = labelsTotalHeight * 0.46;
CGFloat priceLblHeight = labelsTotalHeight * 0.18;
_brandLbl.frame = (CGRect){{15, labelStartY}, {bounds.size.width - 30, nameLblHeight}};
CGFloat priceToNameOffset = 8.0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
priceToNameOffset = 18.0;
}
_priceLbl.frame = (CGRect){{5, labelStartY + nameLblHeight - priceToNameOffset}, {bounds.size.width-10, priceLblHeight}};
[_spinner stopAnimating];
[_spinner removeFromSuperview];
_spinner = nil;
}
覆盖 setProduct 方法
- (void)setProduct:(Product *)product
{
_product = product;
_spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
_spinner.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[self addSubview:_spinner];
[_spinner startAnimating];
_spinner.hidesWhenStopped = YES;
// Add a spinner
__block UIActivityIndicatorView *tmpSpinner = _spinner;
__block UIImageView *tmpImgView = _thumbnailImgView;
ProductImage *thumbnailImage = _product.images[0];
[_thumbnailImgView sd_setImageWithURL:[NSURL URLWithString:thumbnailImage.mediumURL]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
// dismiss the spinner
[tmpSpinner stopAnimating];
[tmpSpinner removeFromSuperview];
tmpSpinner = nil;
if (nil == error) {
// Resize the incoming images
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGFloat imageHeight = image.size.height;
CGFloat imageWidth = image.size.width;
CGSize newSize = tmpImgView.bounds.size;
CGFloat scaleFactor = newSize.width / imageWidth;
newSize.height = imageHeight * scaleFactor;
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *small = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(),^{
tmpImgView.image = small;
});
});
if (cacheType == SDImageCacheTypeNone) {
tmpImgView.alpha = 0.0;
[UIView animateWithDuration:0.2
delay:0
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations:^{
tmpImgView.alpha = 1.0;
} completion:nil];
}
} else {
// loading error
[tmpImgView setImage:[UIImage imageNamed:@"broken_image_small"]];
}
}];
_brandLbl.text = [_product.brand.name uppercaseString];
_nameLbl.text = _product.name;
[_nameLbl sizeToFit];
// Format the price
NSNumberFormatter * floatFormatter = [[NSNumberFormatter alloc] init];
[floatFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[floatFormatter setDecimalSeparator:@"."];
[floatFormatter setMaximumFractionDigits:2];
[floatFormatter setMinimumFractionDigits:0];
[floatFormatter setGroupingSeparator:@","];
_priceLbl.text = [NSString stringWithFormat:@"$%@ USD", [floatFormatter stringFromNumber:_product.price]];
if (_product.salePrice.intValue > 0) {
NSString *rawStr = [NSString stringWithFormat:@"$%@ $%@ USD", [floatFormatter stringFromNumber:_product.price], [floatFormatter stringFromNumber:_product.salePrice]];
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:rawStr];
// Change all the text to red first
[string addAttribute:NSForegroundColorAttributeName
value:[UIColor colorWithRed:157/255.0 green:38/255.0 blue:29/255.0 alpha:1.0]
range:NSMakeRange(0,rawStr.length)];
// find the first space
NSRange firstSpace = [rawStr rangeOfString:@" "];
// Change from zero to space to gray color
[string addAttribute:NSForegroundColorAttributeName
value:_priceLbl.textColor
range:NSMakeRange(0, firstSpace.location)];
[string addAttribute:NSStrikethroughStyleAttributeName
value:@2
range:NSMakeRange(0, firstSpace.location)];
_priceLbl.attributedText = string;
}
}
最佳答案
SDWebImage 非常令人钦佩,但 DLImageLoader 绝对令人难以置信,并且是许多大型生产应用程序的关键部分
https://stackoverflow.com/a/19115912/294884
它非常易于使用。
为了避免略读问题,基本上只是在开始下载图像之前引入延迟。所以,本质上就像这样……就这么简单
dispatch_after_secs_on_main(0.4, ^
{
if ( ! [urlWasThen isEqualToString:self.currentImage] )
{
// so in other words, in fact, after a short period of time,
// the user has indeed scrolled away from that item.
// (ie, the user is skimming)
// this item is now some "new" item so of course we don't
// bother loading "that old" item
// ie, we now know the user was simply skimming over that item.
// (just TBC in the preliminary clause above,
// since the image is already in cache,
// we'd just instantly load the image - even if the user is skimming)
// NSLog(@" --- --- --- --- --- --- too quick!");
return;
}
// a short time has passed, and indeed this cell is still "that" item
// the user is NOT skimming, SO we start loading the image.
//NSLog(@" --- not too quick ");
[DLImageLoader loadImageFromURL:urlWasThen
completed:^(NSError *error, NSData *imgData)
{
if (self == nil) return;
// some time has passed while the image was loading from the internet...
if ( ! [urlWasThen isEqualToString:self.currentImage] )
{
// note that this is the "normal" situation where the user has
// moved on from the image, so no need toload.
//
// in other words: in this case, not due to skimming,
// but because SO much time has passed,
// the user has moved on to some other part of the table.
// we pointlessly loaded the image from the internet! doh!
//NSLog(@" === === 'too late!' image load!");
return;
}
UIImage *image = [UIImage imageWithData:imgData];
self.someImage.image = image;
}];
});
这是“极其简单”的解决方案。
IMO,经过大量实验后,它实际上比更复杂的解决方案效果要好得多在滚动浏览时进行跟踪。
再一次,DLImageLoader 让这一切变得极其简单 https://stackoverflow.com/a/19115912/294884
请注意,上面的代码部分只是您在单元格中加载图像的“常规”方式。
这是执行此操作的典型代码:
-(void)imageIsNow:(NSString *)imUrl
{
// call this routine o "set the image" on this cell.
// note that "image is now" is a better name than "set the image"
// Don't forget that cells very rapidly change contents, due to
// the cell reuse paradigm on iOS.
// this cell is being told that, the image to be displayed is now this image
// being aware of scrolling/skimming issues, cache issues, etc,
// utilise this information to apprporiately load/whatever the image.
self.someImage.image = nil; // that's UIImageView
self.currentImage = imUrl; // you need that string property
[self loadImageInASecIfItsTheSameAs:imUrl];
}
-(void)loadImageInASecIfItsTheSameAs:(NSString *)urlWasThen
{
// (note - at this point here the image may already be available
// in cache. if so, just display it. I have omitted that
// code for simplicity here.)
// so, right here, "possibly load with delay" the image
// exactly as shown in the code above .....
dispatch_after_secs_on_main(0.4, ^
...etc....
...etc....
}
再一次,由于 DLImageLoader 非常棒,这一切都变得很容易。这是一个非常可靠的库。
关于ios - UICollectionView 使用 SDWebImage 滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26033470/
我正在使用iPhone应用程序。在这个应用程序中,我使用了许多图像,我需要将这些图像存储在高速缓存中。为此,我发现SDWebImage是正确的框架,但无法在我的iPhone项目中包括此框架。谁能建议我
我使用 SDWebImage 将图像下载到我的 UITableView 中: [cell.imageView setImageWithURL:[NSURL URLWithString:tempPhot
如何使用 UIImageView 在 SDWebImage 占位符中设置事件指示器 最佳答案 Swift 5 和 SDWebImage 5-beta //pod file pod 'SDWebImag
所以首先,我发现了这个: SDWebImage process images before caching 这有帮助,但我真的想使用SDWebImage,有没有办法在完成之前通过类外部的方法处理图像?
我在应用程序中使用SDWebImage从网络加载和缓存图像。它的工作原理就像是一种魅力,但是唯一的问题是,每当我退出应用程序时,即使是暂时退出,图像缓存也会被清除,所有图像都必须再次下载。 Inter
我正在使用 SDWebImage 将图像从服务器获取到我在 IOS 中的表格 View 应用程序。 但问题是,当我在表格 View 中向下滚动而不是等待图像加载时,将下载的图像放在表格 View 的前
我正在尝试使用 SDWebImage 来缓存从网络检索的图像。图像一直缓存到内存中。我已经看到默认保存到磁盘的文档。 Everything will be handled for you, from
我有一个带有背景图像的按钮,我使用 SDWebImage 下载该按钮。我想保持高度不变,并通过保持纵横比而不剪切图像来改变宽度。只是恒定的高度和动态宽度。这里出了什么问题? //CODE FROM F
我使用 SDWebImage pod 进行下载并显示来自 json 的图像,它运行良好,但我有一个大问题。 我正在使用collectionView,当加载单元格时,某些单元格显示错误的图像。我搜索了它
我一定是在做一些愚蠢的事情,但是 SDWebImage 永远不会执行以下的完成处理程序: episodeImageView?.sd_setImage(with: url, completed: { [
我正在使用 SDWebImage 库。当我使用 iPhone 时它工作正常,但当我使用模拟器编译时它不起作用。我收到此错误: Undefined symbols for architecture
我尝试了 100 多种使用 SDWebImage 制作图像动画的方法,但总是失败。我理解使用 block 好吗?我有 IBOutlet 属性图像名称 images1。我制作了数组来存储值并为 3 个图
我正在使用 SDWebImage 框架下载 iPhone 游戏中的个人资料图像。 [cell.playerImage setImageWithURL:url placeholderImage: Def
我在 UICollectionView 和 UITableView 中使用 SDWebImage。在这两种情况下,当行数很高时,内存使用率有时会非常高,我会收到内存警告。 以下是我的头像: 你看,有时
我正在使用 SDWebImage 库来缓存 UICollectionView 的图像,该图像是从 url 下载的,每次发送所有请求但只下载两个图像。有谁知道原因吗???提前致谢。 代码是: SDWeb
美好的一天! 从网络下载全尺寸图像时,我使用这两个库。 代码: -(void)viewDidAppear:(BOOL)animated { dispatch_async(dispatch_ge
是否可以使用SDWebImage下载AES加密图片?如果是这样,我在哪里可以访问下载的数据,手动解密它们? 最佳答案 不幸的是,无法使用 SDWebImage 下载加密图像。我只是看了他们的类图,没有
我正面临这个问题,下面是代码。它第一次显示错误,然后没有完成 block 调用。 [self.comicsImage setImageWithURL:[NSURL URLWithString:self
我检查了 NSBundle,但找不到 SDWebImage 保存图像的缓存。缓存是否启用(见下面的代码)?根据Docs它进行缓存管理。 如文档中所示: Just #import the UIImage
这个问题可能很奇怪,但它正在发生,而我却不明白。 我刚刚从 https://github.com/rs/SDWebImage 下载了代码并尝试运行示例项目。 它正在构建成功,但没有运行。为什么? 提前
我是一名优秀的程序员,十分优秀!